前言

  Toast相信大家都不会陌生吧,如果对于Toast不甚了解,可以参考我的上一篇博客《Android:谈一谈安卓应用中的Toast情节》,里面有关于Toast基础比较详细的介绍。但是如果你想要看的是最原汁原味的Toast攻略,我非常建议你:出门右转,谷歌官网,据说是一个非常给力的地儿,一般人我还不告诉他呢。但是!如果官网的开发者指南都满足不了你的胃口的话,那你还是得准备点西瓜瓜子回来吧,搬个板凳坐前排来一起分析一下Toast的源码设计。

Toast的源代码世界

  这个故事要从哪里说起呢?话说很久很久以前,程序员菜鸟小明不小心搜索到了Toast这个java文件,顿时小明心跳加速、脸红耳赤的:“这可不是我经常用到的Toast吗?”。怀揣着程序员固有的好奇心的小明点进了这个代码文件,发现了这么一个函数

  1. public static Toast makeText(Context context, CharSequence text, int duration) {
  2. Toast result = new Toast(context);
  3.  
  4. LayoutInflater inflate = (LayoutInflater)
  5. context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  6. View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
  7. TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
  8. tv.setText(text);
  9.  
  10. result.mNextView = v;
  11. result.mDuration = duration;
  12.  
  13. return result;
  14. }

好眼熟,貌似昨天还刚刚跟它在代码上打过招呼呢。小明顿时有一种很高大上的感觉,这就是传说中的android源代码!

小明瞄了几眼代码,马上总结出两个信息:1、android源码真简单!2、Toast显示的布局文件是transient_notification.xml!

怀揣这洋洋得意的心思,小明在源代码中开始搜索transient_notification.xml,一顿卡死,终于在快放弃的时候给出了结果。

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:orientation="vertical"
  5. android:background="?android:attr/toastFrameBackground">
  6.  
  7. <TextView
  8. android:id="@android:id/message"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_weight="1"
  12. android:layout_gravity="center_horizontal"
  13. android:textAppearance="@style/TextAppearance.Toast"
  14. android:textColor="@color/bright_foreground_dark"
  15. android:shadowColor="#BB000000"
  16. android:shadowRadius="2.75"
  17. />
  18.  
  19. </LinearLayout>

这简单的不像话了!!小明愤怒了。但是愤怒归愤怒,小明还是继续往下看了,接下来看什么呢,肯定是show()方法了。

小明边念念叨叨的:“作为一个二十一世纪的优秀攻城狮,我们需要的是一种探索源代码的情怀。。。。。。”,一边定位到了show()的代码。

  1. public void show() {
  2.   if (mNextView == null) {
  3.     throw new RuntimeException("setView must have been called");
  4.   }
  5.  
  6.   INotificationManager service = getService();
  7.   String pkg = mContext.getPackageName();
  8.   TN tn = mTN;
  9.   tn.mNextView = mNextView;
  10.  
  11.   try {
  12.     service.enqueueToast(pkg, tn, mDuration);
  13.   } catch (RemoteException e) {
  14.     // Empty
  15.   }
    }

  这里好像是要先获取一个服务:INotificationManager,然后调用service.enqueueToast(pkg, tn, mDuration)好像是将Toast放到一个队列里面显示吧;小明这么底气不足的理解着。这个TN是个啥子玩意呢?没见过?那就来个第一次约会咯。代码搜索出炉:

  1. private static class TN extends ITransientNotification.Stub {
  2. final Runnable mShow = new Runnable() {
  3. @Override
  4. public void run() {
  5. handleShow();
  6. }
  7. };
  8.  
  9. final Runnable mHide = new Runnable() {
  10. @Override
  11. public void run() {
  12. handleHide();
  13. // Don't do this in handleHide() because it is also invoked by handleShow()
  14. mNextView = null;
  15. }
  16. };
  17.  
  18. private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
  19. final Handler mHandler = new Handler();
  20.  
  21. int mGravity;
  22. int mX, mY;
  23. float mHorizontalMargin;
  24. float mVerticalMargin;
  25.  
  26. View mView;
  27. View mNextView;
  28.  
  29. WindowManager mWM;
  30.  
  31. TN() {
  32. // XXX This should be changed to use a Dialog, with a Theme.Toast
  33. // defined that sets up the layout params appropriately.
  34. final WindowManager.LayoutParams params = mParams;
  35. params.height = WindowManager.LayoutParams.WRAP_CONTENT;
  36. params.width = WindowManager.LayoutParams.WRAP_CONTENT;
  37. params.format = PixelFormat.TRANSLUCENT;
  38. params.windowAnimations = com.android.internal.R.style.Animation_Toast;
  39. params.type = WindowManager.LayoutParams.TYPE_TOAST;
  40. params.setTitle("Toast");
  41. params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  42. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  43. | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  44. }
  45.  
  46. /**
  47. * schedule handleShow into the right thread
  48. */
  49. @Override
  50. public void show() {
  51. if (localLOGV) Log.v(TAG, "SHOW: " + this);
  52. mHandler.post(mShow);
  53. }
  54.  
  55. /**
  56. * schedule handleHide into the right thread
  57. */
  58. @Override
  59. public void hide() {
  60. if (localLOGV) Log.v(TAG, "HIDE: " + this);
  61. mHandler.post(mHide);
  62. }
  63.  
  64. public void handleShow() {
  65. if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
  66. + " mNextView=" + mNextView);
  67. if (mView != mNextView) {
  68. // remove the old view if necessary
  69. handleHide();
  70. mView = mNextView;
  71. Context context = mView.getContext().getApplicationContext();
  72. if (context == null) {
  73. context = mView.getContext();
  74. }
  75. mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  76. // We can resolve the Gravity here by using the Locale for getting
  77. // the layout direction
  78. final Configuration config = mView.getContext().getResources().getConfiguration();
  79. final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
  80. mParams.gravity = gravity;
  81. if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
  82. mParams.horizontalWeight = 1.0f;
  83. }
  84. if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
  85. mParams.verticalWeight = 1.0f;
  86. }
  87. mParams.x = mX;
  88. mParams.y = mY;
  89. mParams.verticalMargin = mVerticalMargin;
  90. mParams.horizontalMargin = mHorizontalMargin;
  91. if (mView.getParent() != null) {
  92. if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
  93. mWM.removeView(mView);
  94. }
  95. if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
  96. mWM.addView(mView, mParams);
  97. trySendAccessibilityEvent();
  98. }
  99. }
  100.  
  101. private void trySendAccessibilityEvent() {
  102. AccessibilityManager accessibilityManager =
  103. AccessibilityManager.getInstance(mView.getContext());
  104. if (!accessibilityManager.isEnabled()) {
  105. return;
  106. }
  107. // treat toasts as notifications since they are used to
  108. // announce a transient piece of information to the user
  109. AccessibilityEvent event = AccessibilityEvent.obtain(
  110. AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
  111. event.setClassName(getClass().getName());
  112. event.setPackageName(mView.getContext().getPackageName());
  113. mView.dispatchPopulateAccessibilityEvent(event);
  114. accessibilityManager.sendAccessibilityEvent(event);
  115. }
  116.  
  117. public void handleHide() {
  118. if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
  119. if (mView != null) {
  120. // note: checking parent() just to make sure the view has
  121. // been added... i have seen cases where we get here when
  122. // the view isn't yet added, so let's try not to crash.
  123. if (mView.getParent() != null) {
  124. if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
  125. mWM.removeView(mView);
  126. }
  127.  
  128. mView = null;
  129. }
  130. }
  131. }

乍一看,把小明给虚的,急忙找来大牛程序员帮忙讲解一下。大牛认真过了几眼,咦~其实也不是那么复杂的。这时大牛注意到了这个TN继承了ITransientNotification.Stub,这个类的形式不知道大家还熟悉吗?连小明好像在博客园里面介绍AIDL的文章时懵懵懂懂看到过这种形式的类,可是没等小明反应过来,大牛顺手就在源代码中搜索了一下:ITransientNotification

“果断是AIDL!!”小明惊叹。果然大神跟菜鸟就是不一样,大牛这时打开ITransientNotification瞄一瞄,发现了show()和hide()这两个方法。

  1. package android.app;
  2.  
  3. /** @hide */
  4. oneway interface ITransientNotification {
  5. void show();
  6. void hide();
  7. }

“那么应该回去TN看看他的实现了”,大牛跟小明说。

  1. @Override
  2. public void show() {
  3. if (localLOGV) Log.v(TAG, "SHOW: " + this);
  4. mHandler.post(mShow);
  5. }
  6.  
  7. @Override
  8. public void hide() {
  9. if (localLOGV) Log.v(TAG, "HIDE: " + this);
  10. mHandler.post(mHide);
  11. }

原来是使用handler机制,分别post一个nShow和一个mHide,再接再厉,追踪源码

  1. final Runnable mShow = new Runnable() {
  2.   @Override
  3.   public void run() {
  4.     handleShow();
  5.   }
  6. };
  7.  
  8. final Runnable mHide = new Runnable() {
  9.   @Override
  10.   public void run() {
  11.     handleHide();
  12.     mNextView = null;
  13.   }
  14. };

小明这次学聪明了,毕竟跟大牛学习比小明整天啃得那些《七天精通Android编程》之类的坑爹书靠谱多了,所以小明跟大牛说,我们应该看看handleShow()的实现,正解!

  1. public void handleShow() {
  2.   if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
  3.     + " mNextView=" + mNextView);
  4.   if (mView != mNextView) {
  5.   // remove the old view if necessary
  6.   handleHide();
  7.   mView = mNextView;
  8.   Context context = mView.getContext().getApplicationContext();
  9.   if (context == null) {
  10.     context = mView.getContext();
  11.   }
  12.   mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  13.   // We can resolve the Gravity here by using the Locale for getting
  14.   // the layout direction
  15.   final Configuration config = mView.getContext().getResources().getConfiguration();
  16.   final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
  17.   mParams.gravity = gravity;
  18.   if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
  19.     mParams.horizontalWeight = 1.0f;
  20.   }
  21.   if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
  22.     mParams.verticalWeight = 1.0f;
  23.   }
  24.   mParams.x = mX;
  25.   mParams.y = mY;
  26.   mParams.verticalMargin = mVerticalMargin;
  27.   mParams.horizontalMargin = mHorizontalMargin;
  28.   if (mView.getParent() != null) {
  29.     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
  30.       mWM.removeView(mView);
  31.   }
  32.   if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
  33.   mWM.addView(mView, mParams);
  34.   trySendAccessibilityEvent();
  35.   }
  36. }

原来是Toast的视图是通过WindowManager的addView来加载的,小明突然感觉自己向高级程序员迈进了一大步-----“怎么说哥现在也是了解实现原理的人了!”

他们接下来又把邪恶的目光定位在TN()这个构造方法上面

  1. TN() {
  2.   final WindowManager.LayoutParams params = mParams;
  3.   params.height = WindowManager.LayoutParams.WRAP_CONTENT;
  4.   params.width = WindowManager.LayoutParams.WRAP_CONTENT;
  5.   params.format = PixelFormat.TRANSLUCENT;
  6.   params.windowAnimations = com.android.internal.R.style.Animation_Toast;
  7.   params.type = WindowManager.LayoutParams.TYPE_TOAST;
  8.   params.setTitle("Toast");
  9.   params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  10.     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  11.     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  12. }

这就是设置Toast中的View的各种位置参数params。

但是小明还是有点不明白,大牛看到小明神游的样子,就给他解释道:

  其实Toast的原理是这样的,先通过makeText()实例化出一个Toast,然后调用toast.Show()方法,这时并不会马上显示Toast,而是会实例化一个TN变量,然后通过service.enqueueToast()将其加到服务队列里面去等待显示。在TN中进行调控Toast的显示格式以及里面的hide()、show()方法来控制Toast的出现以及消失,强调一下的是这个队列是系统维护的,我们并不能干涉。

小明若有所思的点点头。。。。。。

 自由控制Toast的显示时间

  时间就像水,干着干着就干了,撸着撸着就没了,吸着吸着就瘪了。两三天又过去了,突然有一天头儿给小明吩咐了一个活:给应用设置一个较长时间的Toast。这还不简单,小明偷偷在工位上打着瞌睡揉揉眼睛,Toast.setDuration()不就解决了嘛~要几秒就设几秒咯,这还是事儿?但是,谷歌又一次坑了他:因为小明不管怎么设置,Toast只能有显示2s和3.5s这两个情况,这时为啥呢?小明突然想起前些天翻了翻Toast的源码,赶紧去里面找答案

  1. private void scheduleTimeoutLocked(ToastRecord r) {
  2.   mHandler.removeCallbacksAndMessages(r);
  3.   Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
  4.   long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
  5.   mHandler.sendMessageDelayed(m, delay);
  6. }
  1. private static final int LONG_DELAY = 3500; // 3.5 seconds
  2. private static final int SHORT_DELAY = 2000; // 2 seconds

  我们呢看到这里是使用了handler中的延迟发信息来显示toast的,这里我们也看到了,延迟时间是duration,但是只有两个值:2s和3.5s这两个值,所以我们在之前说过我们设置toast的显示时间是没有任何效果的,所以小明又得去请教大牛了,果然活都不会是那么简单的。。。。。。。

大牛早有研究,他分析道:你还记得我们前些天分析的Toast源代码吗?Toast的显示是首先借助TN类,所有的显示逻辑在这个类中的show方法中,然后再实例一个TN类变量,将传递到一个队列中进行显示,所以我们要向解决这个显示的时间问题,那就从入队列这部给截断,说白了就两点:

1、不让Toast进入队列

2、调用TN类中的hide和show的方法自己控制Toast

但是第一点好实现,第二点让人抓狂了,因为我们看到TN这个类是私有的,所以我们也不能实例化他的对象,但是toast类中有一个实例化对象:tn

  1. final TN mTN;

竟然是包访问权限,大牛一脸淫笑的说,咱们得借助无比强大的反射技术,我们只需要反射出这个变量,然后强暴她一次即可,得到这个变量我们可以得到这个TN类对象了,然后再使用反射获取他的show和hide方法即可,代码如下:

方法一:

  1. public class ToastReflect {
  2.  
  3. private Toast mToast;
  4. private Field field;
  5. private Object obj;
  6. private Method showMethod, hideMethod;
  7. private double time;
  8.  
  9. private ToastReflect(Context context, String text, double time){
  10. this.time = time;
  11. mToast = Toast.makeText(context, text, Toast.LENGTH_LONG);
  12. reflectionTN();
  13. }
  14.  
  15. private void reflectionTN() {
  16. try{
  17. field = mToast.getClass().getDeclaredField("mTN");
  18. field.setAccessible(true);
  19. obj = field.get(mToast);
  20. showMethod = obj.getClass().getDeclaredMethod("show", null);
  21. hideMethod = obj.getClass().getDeclaredMethod("hide", null);
  22. }catch(Exception e){
  23. e.printStackTrace();
  24. }
  25. }
  26.  
  27. public static ToastReflect makeText(Context context, String text, double time){
  28. ToastReflect toastReflect = new ToastReflect(context, text, time);
  29. return toastReflect;
  30. }
  31.  
  32. private void showToast(){
  33. try{
  34. showMethod.invoke(obj, null);
  35. }catch(Exception e){
  36. e.printStackTrace();
  37. }
  38. }
  39.  
  40. private void hideToast(){
  41. try{
  42. hideMethod.invoke(obj, null);
  43. }catch(Exception e){
  44. e.printStackTrace();
  45. }
  46. }
  47.  
  48. public void show(){
  49. showToast();
  50. new Timer().schedule(new TimerTask() {
  51. @Override
  52. public void run() {
  53. hideToast();
  54. }
  55. }, (long)(time * 1000));
  56. }
  57. }

ps:利用反射来控制Toast的显示时间在高版本会有bug,Android 2.2实测实可以用的,Android 4.0则无法使用。具体原因大牛还在分析。。。。。。

方法二:

  但是作为一个通用性软件,对于任何版本都需要支持,所以小明还是只能采取其他办法,说实话,还真发现了一个比较傻瓜的实现。

就是可以利用handler.post结合timer来实现效果,兼容性较好。。利用定时重复show一个Toast就能达到根据特定时间来显示的功能。

  1. public class ToastSimple {
  2.  
  3. private double time;
  4. private static Handler handler;
  5. private Timer showTimer;
  6. private Timer cancelTimer;
  7.  
  8. private Toast toast;
  9.  
  10. private ToastSimple(){
  11. showTimer = new Timer();
  12. cancelTimer = new Timer();
  13. }
  14.  
  15. public void setTime(double time) {
  16. this.time = time;
  17. }
  18.  
  19. public void setToast(Toast toast){
  20. this.toast = toast;
  21. }
  22.  
  23. public static ToastSimple makeText(Context context, String text, double time){
  24. ToastSimple toast1= new ToastSimple();
  25. toast1.setTime(time);
  26. toast1.setToast(Toast.makeText(context, text, Toast.LENGTH_SHORT));
  27. handler = new Handler(context.getMainLooper());
  28. return toast1;
  29. }
  30.  
  31. public void show(){
  32. toast.show();
  33. if(time > 2){
  34. showTimer.schedule(new TimerTask() {
  35. @Override
  36. public void run() {
  37. handler.post(new ShowRunnable());
  38. }
  39. }, 0, 1900);
  40. }
  41. cancelTimer.schedule(new TimerTask() {
  42. @Override
  43. public void run() {
  44. handler.post(new CancelRunnable());
  45. }
  46. }, (long)(time * 1000));
  47. }
  48.  
  49. private class CancelRunnable implements Runnable{
  50. @Override
  51. public void run() {
  52. showTimer.cancel();
  53. toast.cancel();
  54. }
  55. }
  56.  
  57. private class ShowRunnable implements Runnable{
  58. @Override
  59. public void run() {
  60. toast.show();
  61. }
  62. }
  63. }

方法三:  

这时,大牛也琢磨出一个办法,因为Toast是基于windowManager来显示的,所以完全可以自己写一个自定义的Toast,代码如下

  1. package com.net168.toast;
  2.  
  3. import java.util.Timer;
  4. import java.util.TimerTask;
  5.  
  6. import android.content.Context;
  7. import android.graphics.PixelFormat;
  8. import android.view.Gravity;
  9. import android.view.View;
  10. import android.view.WindowManager;
  11. import android.widget.Toast;
  12.  
  13. public class ToastCustom {
  14.  
  15. private WindowManager wdm;
  16. private double time;
  17. private View mView;
  18. private WindowManager.LayoutParams params;
  19. private Timer timer;
  20.  
  21. private ToastCustom(Context context, String text, double time){
  22. wdm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  23. timer = new Timer();
  24.  
  25. Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
  26. mView = toast.getView();
  27.  
  28. params = new WindowManager.LayoutParams();
  29. params.height = WindowManager.LayoutParams.WRAP_CONTENT;
  30. params.width = WindowManager.LayoutParams.WRAP_CONTENT;
  31. params.format = PixelFormat.TRANSLUCENT;
  32. params.windowAnimations = toast.getView().getAnimation().INFINITE;
  33. params.type = WindowManager.LayoutParams.TYPE_TOAST;
  34. params.setTitle("Toast");
  35. params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  36. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  37. | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  38. params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
  39. params.y = -30;
  40.  
  41. this.time = time;
  42. }
  43.  
  44. public static ToastCustom makeText(Context context, String text, double time){
  45. ToastCustom toastCustom = new ToastCustom(context, text, time);
  46. return toastCustom;
  47. }
  48.  
  49. public void show(){
  50. wdm.addView(mView, params);
  51. timer.schedule(new TimerTask() {
  52. @Override
  53. public void run() {
  54. wdm.removeView(mView);
  55. }
  56. }, (long)(time * 1000));
  57. }
  58.  
  59. public void cancel(){
  60. wdm.removeView(mView);
  61. timer.cancel();
  62. }
  63.  
  64. }

PS:上面自定义Toast代码只实现了基本功能,其余功能由于时间关系没有全部实现。

测试代码如下:

  1. public class MainActivity extends ActionBarActivity implements View.OnClickListener{
  2.  
  3. private EditText edt_duration;
  4. private Button btn_toast_simple;
  5. private Button btn_toast_reflect;
  6. private Button btn_toast_custom;
  7.  
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12.  
  13. edt_duration = (EditText) findViewById(R.id.edt_duration);
  14. btn_toast_simple = (Button) findViewById(R.id.btn_toast_simple);
  15. btn_toast_reflect = (Button) findViewById(R.id.btn_toast_reflect);
  16. btn_toast_custom = (Button) findViewById(R.id.btn_toast_custom);
  17.  
  18. btn_toast_simple.setOnClickListener(this);
  19. btn_toast_reflect.setOnClickListener(this);
  20. btn_toast_custom.setOnClickListener(this);
  21. }
  22.  
  23. @Override
  24. public void onClick(View v) {
  25. double time = Double.parseDouble((edt_duration.getText().toString()));
  26. switch (v.getId()){
  27. case R.id.btn_toast_simple:
  28. ToastSimple.makeText(MainActivity.this, "简单Toast,执行时间为:" + time, time).show();
  29. break;
  30. case R.id.btn_toast_reflect:
  31. ToastReflect.makeText(MainActivity.this, "反射Toast,执行时间为" + time, time).show();
  32. break;
  33. case R.id.btn_toast_custom:
  34. ToastCustom.makeText(MainActivity.this, "反射Toast,执行时间为" + time, time).show();
  35. break;
  36. }
  37. }
  38. }

限于篇幅,也就懒得讲解了。。。。。。

作者:enjoy风铃
出处:http://www.cnblogs.com/net168/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则下次不给你转载了。

Android:剖析源码,随心所欲控制Toast显示的更多相关文章

  1. Android菜鸟的成长笔记(6)——剖析源码学自定义主题Theme

    原文:Android菜鸟的成长笔记(6)--剖析源码学自定义主题Theme 还记得在Android菜鸟的成长笔记(3)中我们曾经遇到了一个问题吗?"这个界面和真真的QQ界面还有点不同的就是上 ...

  2. 如何学习Android系统源码(转)

    一. Android系统的源代码非常庞大和复杂,我们不能贸然进入,否则很容易在里面迷入方向,进而失去研究它的信心.我们应该在分析它的源代码之前学习好一些理论知识,下面就介绍一些与Android系统相关 ...

  3. Android FrameWork 学习之Android 系统源码调试

    这是很久以前访问掘金的时候 无意间看到的一个关于Android的文章,作者更细心,分阶段的将学习步骤记录在自己博客中,我觉得很有用,想作为分享同时也是留下自己知识的一些欠缺收藏起来,今后做项目的时候会 ...

  4. Android FrameWork学习(二)Android系统源码调试

    通过上一篇 Android FrameWork学习(一)Android 7.0系统源码下载\编译 我们了解了如何进行系统源码的下载和编译工作. 为了更进一步地学习跟研究 Android 系统源码,今天 ...

  5. Android Choreographer 源码分析

    Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整, ...

  6. Android fragment源码全解析

    Fragment 相信基本上每个android developer都用过,但是知晓其原理 用的好的还是不多,今天就从源码的角度上来带着大家分析一下Fragment的源码,对fragment有了更深层次 ...

  7. Android SimpleAdapter源码详解

    一直没认真看过android的源码,也不太敢看,稀里糊涂也敲了一年的代码,现在想好好学习了,就把常用的源码都看了一下,小伙伴们来涨姿势吧,有错误的地方,直接指出,我脸厚不怕丢人.来吧. 刚开始学and ...

  8. Appium Android Bootstrap源码分析之启动运行

    通过前面的两篇文章<Appium Android Bootstrap源码分析之控件AndroidElement>和<Appium Android Bootstrap源码分析之命令解析 ...

  9. Android -- AsyncTask源码解析

    1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结.本来早就想写这篇文章的,当时写<Android -- 从源码解析Handle+ ...

随机推荐

  1. 2019工作计划idea

    2019.2.24 工作需求: 汇总 2008-2018年 销售订单数据; 分类历史订单数据, 并可能采取方法进行预测(预测只是一种行为不代表结果) 目前已知条件: 订单生产周期; 45天(标准天数) ...

  2. sublime text 3启动报错"swallow_startup_errors"解决方法

    启动sublime text 3报错: anaconda插件连接jsonserver服务出现错误 解决方法: 首选项 -- package settings -- Anaconda -- settin ...

  3. Akka.net 性能测试兼使用小技巧

    最近想研究一下分布式开发,先拿了akka.net 跑一下性能 参考自己写个网络实现,一般在本机通讯,300M每秒的传输率,作为参考 嗯,先说结果,用Akka.net直接发bytearray,最后也只有 ...

  4. 如何让pandas表格直接转换为markdown表格

    https://stackoverflow.com/questions/33181846/programmatically-convert-pandas-dataframe-to-markdown-t ...

  5. sqlalchemy 使用

    创建连接 # 参数: '数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名' from sqlalchemy import create_engine engine = crea ...

  6. Analysis CDI

    CDI是一组服务,它们一起使用,使开发人员可以轻松地在Web应用程序中使用企业bean和JavaServer Faces技术.CDI设计用于有状态对象,还有许多更广泛的用途,允许开发人员以松散耦合但类 ...

  7. 关于部署php遇到的坑

    业务突然要启动一个久不使用的PHP项目, 发现部署到centos7上后 各种报错 就是不行. 我怀疑是apache或者php问题 就重新安装 编译安装也试过就是不行. 只能按笨办法 在测试环境安装了a ...

  8. 【原创】.Net WebForm Calendar 日历控件常用方法

    微软官方地址 https://msdn.microsoft.com/en-us/library/add3s294.aspx 1.设置日历控件单个日期Table Cell样式 颜色/外观/边距 prot ...

  9. Linux shell编程— 命令替换

    有两种方法可以将命令输出赋给变量 反引号字符(`) $()格式 命令替换允许你将shell 命令的输出赋给变量 要么用一对反引号把整个命令行围起来: testing=`data` 要么使用$()格式 ...

  10. history.pushState()和history.replaceState()

    Html5 新增history对象的两个方法:history.pushState()和history.replaceState(),方法执行后,浏览器地址栏会变成你传的url,而页面并不会重新载入或跳 ...