最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Android中的Toast源碼

 更新時(shí)間:2015年07月30日 16:27:30   作者:低調(diào)小一  
這篇文章主要介紹了詳解Android中的Toast源碼,Toast使用Java語(yǔ)言實(shí)現(xiàn),需要的朋友可以參考下

Toast源碼實(shí)現(xiàn)
Toast入口
    我們?cè)趹?yīng)用中使用Toast提示的時(shí)候,一般都是一行簡(jiǎn)單的代碼調(diào)用,如下所示:
[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); 

    makeText就是Toast的入口,我們從makeText的源碼來(lái)深入理解Toast的實(shí)現(xiàn)。源碼如下(frameworks/base/core/java/android/widget/Toast.java):

  public static Toast makeText(Context context, CharSequence text, int duration) { 
    Toast result = new Toast(context); 
   
    LayoutInflater inflate = (LayoutInflater) 
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); 
    TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); 
    tv.setText(text); 
     
    result.mNextView = v; 
    result.mDuration = duration; 
   
    return result; 
  } 

    從makeText的源碼里,我們可以看出Toast的布局文件是transient_notification.xml,位于frameworks/base/core/res/res/layout/transient_notification.xml:

  <?xml version="1.0" encoding="utf-8"?> 
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical" 
    android:background="?android:attr/toastFrameBackground"> 
   
    <TextView 
      android:id="@android:id/message" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_weight="1" 
      android:layout_gravity="center_horizontal" 
      android:textAppearance="@style/TextAppearance.Toast" 
      android:textColor="@color/bright_foreground_dark" 
      android:shadowColor="#BB000000" 
      android:shadowRadius="2.75" 
      /> 
   
  </LinearLayout> 

    系統(tǒng)Toast的布局文件非常簡(jiǎn)單,就是在垂直布局的LinearLayout里放置了一個(gè)TextView。接下來(lái),我們繼續(xù)跟到show()方法,研究一下布局形成之后的展示代碼實(shí)現(xiàn):

  

 public void show() { 
    if (mNextView == null) { 
      throw new RuntimeException("setView must have been called"); 
    } 
   
    INotificationManager service = getService(); 
    String pkg = mContext.getPackageName(); 
    TN tn = mTN; 
    tn.mNextView = mNextView; 
   
    try { 
      service.enqueueToast(pkg, tn, mDuration); 
    } catch (RemoteException e) { 
      // Empty 
    } 
  } 

    show方法中有兩點(diǎn)是需要我們注意的。(1)TN是什么東東?(2)INotificationManager服務(wù)的作用。帶著這兩個(gè)問(wèn)題,繼續(xù)我們Toast源碼的探索。
TN源碼
    很多問(wèn)題都能通過(guò)閱讀源碼找到答案,關(guān)鍵在與你是否有與之匹配的耐心和堅(jiān)持。mTN的實(shí)現(xiàn)在Toast的構(gòu)造函數(shù)中,源碼如下:

  public Toast(Context context) { 
    mContext = context; 
    mTN = new TN(); 
    mTN.mY = context.getResources().getDimensionPixelSize( 
        com.android.internal.R.dimen.toast_y_offset); 
    mTN.mGravity = context.getResources().getInteger( 
        com.android.internal.R.integer.config_toastDefaultGravity); 
  } 

    接下來(lái),我們就從TN類(lèi)的源碼出發(fā),探尋TN的作用。TN源碼如下:

 

  private static class TN extends ITransientNotification.Stub { 
    final Runnable mShow = new Runnable() { 
      @Override 
      public void run() { 
        handleShow(); 
      } 
    }; 
   
    final Runnable mHide = new Runnable() { 
      @Override 
      public void run() { 
        handleHide(); 
        // Don't do this in handleHide() because it is also invoked by handleShow() 
        mNextView = null; 
      } 
    }; 
   
    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); 
    final Handler mHandler = new Handler();   
   
    int mGravity; 
    int mX, mY; 
    float mHorizontalMargin; 
    float mVerticalMargin; 
   
   
    View mView; 
    View mNextView; 
   
    WindowManager mWM; 
   
    TN() { 
      // XXX This should be changed to use a Dialog, with a Theme.Toast 
      // defined that sets up the layout params appropriately. 
      final WindowManager.LayoutParams params = mParams; 
      params.height = WindowManager.LayoutParams.WRAP_CONTENT; 
      params.width = WindowManager.LayoutParams.WRAP_CONTENT; 
      params.format = PixelFormat.TRANSLUCENT; 
      params.windowAnimations = com.android.internal.R.style.Animation_Toast; 
      params.type = WindowManager.LayoutParams.TYPE_TOAST; 
      params.setTitle("Toast"); 
      params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 
          | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 
          | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 
      /// M: [ALPS00517576] Support multi-user 
      params.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 
    } 
   
    /** 
     * schedule handleShow into the right thread 
     */ 
    @Override 
    public void show() { 
      if (localLOGV) Log.v(TAG, "SHOW: " + this); 
      mHandler.post(mShow); 
    } 
   
    /** 
     * schedule handleHide into the right thread 
     */ 
    @Override 
    public void hide() { 
      if (localLOGV) Log.v(TAG, "HIDE: " + this); 
      mHandler.post(mHide); 
    } 
   
    public void handleShow() { 
      if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView 
          + " mNextView=" + mNextView); 
      if (mView != mNextView) { 
        // remove the old view if necessary 
        handleHide(); 
        mView = mNextView; 
        Context context = mView.getContext().getApplicationContext(); 
        if (context == null) { 
          context = mView.getContext(); 
        } 
        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 
        // We can resolve the Gravity here by using the Locale for getting 
        // the layout direction 
        final Configuration config = mView.getContext().getResources().getConfiguration(); 
        final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); 
        mParams.gravity = gravity; 
        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { 
          mParams.horizontalWeight = 1.0f; 
        } 
        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { 
          mParams.verticalWeight = 1.0f; 
        } 
        mParams.x = mX; 
        mParams.y = mY; 
        mParams.verticalMargin = mVerticalMargin; 
        mParams.horizontalMargin = mHorizontalMargin; 
        if (mView.getParent() != null) { 
          if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); 
          mWM.removeView(mView); 
        } 
        if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); 
        mWM.addView(mView, mParams); 
        trySendAccessibilityEvent(); 
      } 
    } 
   
    private void trySendAccessibilityEvent() { 
      AccessibilityManager accessibilityManager = 
          AccessibilityManager.getInstance(mView.getContext()); 
      if (!accessibilityManager.isEnabled()) { 
        return; 
      } 
      // treat toasts as notifications since they are used to 
      // announce a transient piece of information to the user 
      AccessibilityEvent event = AccessibilityEvent.obtain( 
          AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 
      event.setClassName(getClass().getName()); 
      event.setPackageName(mView.getContext().getPackageName()); 
      mView.dispatchPopulateAccessibilityEvent(event); 
      accessibilityManager.sendAccessibilityEvent(event); 
    }     
   
    public void handleHide() { 
      if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); 
      if (mView != null) { 
        // note: checking parent() just to make sure the view has 
        // been added... i have seen cases where we get here when 
        // the view isn't yet added, so let's try not to crash. 
        if (mView.getParent() != null) { 
          if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); 
          mWM.removeView(mView); 
        } 
   
        mView = null; 
      } 
    } 
  } 

    通過(guò)源碼,我們能很明顯的看到繼承關(guān)系,TN類(lèi)繼承自ITransientNotification.Stub,用于進(jìn)程間通信。這里假設(shè)讀者都有Android進(jìn)程間通信的基礎(chǔ)(不太熟的建議學(xué)習(xí)羅升陽(yáng)關(guān)于Binder進(jìn)程通信的一系列博客)。既然TN是用于進(jìn)程間通信,那么我們很容易想到TN類(lèi)的具體作用應(yīng)該是Toast類(lèi)的回調(diào)對(duì)象,其他進(jìn)程通過(guò)調(diào)用TN類(lèi)的具體對(duì)象來(lái)操作Toast的顯示和消失。
    TN類(lèi)繼承自ITransientNotification.Stub,ITransientNotification.aidl位于frameworks/base/core/java/android/app/ITransientNotification.aidl,源碼如下:

  package android.app; 
   
  /** @hide */ 
  oneway interface ITransientNotification { 
    void show(); 
    void hide(); 
  } 

    ITransientNotification定義了兩個(gè)方法show()和hide(),它們的具體實(shí)現(xiàn)就在TN類(lèi)當(dāng)中。TN類(lèi)的實(shí)現(xiàn)為:

  /** 
   * schedule handleShow into the right thread 
   */ 
  @Override 
  public void show() { 
    if (localLOGV) Log.v(TAG, "SHOW: " + this); 
    mHandler.post(mShow); 
  } 
   
  /** 
   * schedule handleHide into the right thread 
   */ 
  @Override 
  public void hide() { 
    if (localLOGV) Log.v(TAG, "HIDE: " + this); 
    mHandler.post(mHide); 
  } 

    這里我們就能知道,Toast的show和hide方法實(shí)現(xiàn)是基于Handler機(jī)制。而TN類(lèi)中的Handler實(shí)現(xiàn)是:

  final Handler mHandler = new Handler();   

    而且,我們?cè)赥N類(lèi)中沒(méi)有發(fā)現(xiàn)任何Looper.perpare()和Looper.loop()方法。說(shuō)明,mHandler調(diào)用的是當(dāng)前所在線程的Looper對(duì)象。所以,當(dāng)我們?cè)谥骶€程(也就是UI線程中)可以隨意調(diào)用Toast.makeText方法,因?yàn)锳ndroid系統(tǒng)幫我們實(shí)現(xiàn)了主線程的Looper初始化。但是,如果你想在子線程中調(diào)用Toast.makeText方法,就必須先進(jìn)行Looper初始化了,不然就會(huì)報(bào)出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 。Handler機(jī)制的學(xué)習(xí)可以參考我之前寫(xiě)過(guò)的一篇博客:http://blog.csdn.net/wzy_1988/article/details/38346637。
    接下來(lái),繼續(xù)跟一下mShow和mHide的實(shí)現(xiàn),它倆的類(lèi)型都是Runnable。

 

  final Runnable mShow = new Runnable() { 
    @Override 
    public void run() { 
      handleShow(); 
    } 
  }; 
   
  final Runnable mHide = new Runnable() { 
    @Override 
    public void run() { 
      handleHide(); 
      // Don't do this in handleHide() because it is also invoked by handleShow() 
      mNextView = null; 
    } 
  }; 

    可以看到,show和hide的真正實(shí)現(xiàn)分別是調(diào)用了handleShow()和handleHide()方法。我們先來(lái)看handleShow()的具體實(shí)現(xiàn):
   

 public void handleShow() { 
    if (mView != mNextView) { 
      // remove the old view if necessary 
      handleHide(); 
      mView = mNextView; 
      Context context = mView.getContext().getApplicationContext(); 
      if (context == null) { 
        context = mView.getContext(); 
      } 
      mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); 
      // We can resolve the Gravity here by using the Locale for getting 
      // the layout direction 
      final Configuration config = mView.getContext().getResources().getConfiguration(); 
      final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); 
      mParams.gravity = gravity; 
      if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { 
        mParams.horizontalWeight = 1.0f; 
      } 
      if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { 
        mParams.verticalWeight = 1.0f; 
      } 
      mParams.x = mX; 
      mParams.y = mY; 
      mParams.verticalMargin = mVerticalMargin; 
      mParams.horizontalMargin = mHorizontalMargin; 
      if (mView.getParent() != null) { 
        mWM.removeView(mView); 
      } 
      mWM.addView(mView, mParams); 
      trySendAccessibilityEvent(); 
    } 
  } 

    從源碼中,我們知道Toast是通過(guò)WindowManager調(diào)用addView加載進(jìn)來(lái)的。因此,hide方法自然是WindowManager調(diào)用removeView方法來(lái)將Toast視圖移除。
    總結(jié)一下,通過(guò)對(duì)TN類(lèi)的源碼分析,我們知道了TN類(lèi)是回調(diào)對(duì)象,其他進(jìn)程調(diào)用tn類(lèi)的show和hide方法來(lái)控制這個(gè)Toast的顯示和消失。
NotificationManagerService
    回到Toast類(lèi)的show方法中,我們可以看到,這里調(diào)用了getService得到INotificationManager服務(wù),源碼如下:

  private static INotificationManager sService; 
   
  static private INotificationManager getService() { 
    if (sService != null) { 
      return sService; 
    } 
    sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); 
    return sService; 
  } 

    得到INotificationManager服務(wù)后,調(diào)用了enqueueToast方法將當(dāng)前的Toast放入到系統(tǒng)的Toast隊(duì)列中。傳的參數(shù)分別是pkg、tn和mDuration。也就是說(shuō),我們通過(guò)Toast.makeText(context, msg, Toast.LENGTH_SHOW).show()去呈現(xiàn)一個(gè)Toast,這個(gè)Toast并不是立刻顯示在當(dāng)前的window上,而是先進(jìn)入系統(tǒng)的Toast隊(duì)列中,然后系統(tǒng)調(diào)用回調(diào)對(duì)象tn的show和hide方法進(jìn)行Toast的顯示和隱藏。
    這里INofiticationManager接口的具體實(shí)現(xiàn)類(lèi)是NotificationManagerService類(lèi),位于frameworks/base/services/java/com/android/server/NotificationManagerService.java。
    首先,我們來(lái)分析一下Toast入隊(duì)的函數(shù)實(shí)現(xiàn)enqueueToast,源碼如下:

  public void enqueueToast(String pkg, ITransientNotification callback, int duration) 
  { 
    // packageName為null或者tn類(lèi)為null,直接返回,不進(jìn)隊(duì)列 
    if (pkg == null || callback == null) { 
      return ; 
    } 
   
    // (1) 判斷是否為系統(tǒng)Toast 
    final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); 
   
    // 判斷當(dāng)前toast所屬的pkg是否為系統(tǒng)不允許發(fā)生Toast的pkg.NotificationManagerService有一個(gè)HashSet數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)了不允許發(fā)生Toast的包名 
    if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid()) && !areNotificationsEnabledForPackageInt(pkg)) { 
      if (!isSystemToast) { 
        return; 
      } 
    } 
   
    synchronized (mToastQueue) { 
      int callingPid = Binder.getCallingPid(); 
      long callingId = Binder.clearCallingIdentity(); 
      try { 
        ToastRecord record; 
        // (2) 查看該Toast是否已經(jīng)在隊(duì)列當(dāng)中 
        int index = indexOfToastLocked(pkg, callback); 
        // 如果Toast已經(jīng)在隊(duì)列中,我們只需要更新顯示時(shí)間即可 
        if (index >= 0) { 
          record = mToastQueue.get(index); 
          record.update(duration); 
        } else { 
          // 非系統(tǒng)Toast,每個(gè)pkg在當(dāng)前mToastQueue中Toast有總數(shù)限制,不能超過(guò)MAX_PACKAGE_NOTIFICATIONS 
          if (!isSystemToast) { 
            int count = 0; 
            final int N = mToastQueue.size(); 
            for (int i=0; i<N; i++) { 
               final ToastRecord r = mToastQueue.get(i); 
               if (r.pkg.equals(pkg)) { 
                 count++; 
                 if (count >= MAX_PACKAGE_NOTIFICATIONS) { 
                   Slog.e(TAG, "Package has already posted " + count 
                      + " toasts. Not showing more. Package=" + pkg); 
                   return; 
                 } 
               } 
            } 
          } 
   
          // 將Toast封裝成ToastRecord對(duì)象,放入mToastQueue中 
          record = new ToastRecord(callingPid, pkg, callback, duration); 
          mToastQueue.add(record); 
          index = mToastQueue.size() - 1; 
          // (3) 將當(dāng)前Toast所在的進(jìn)程設(shè)置為前臺(tái)進(jìn)程 
          keepProcessAliveLocked(callingPid); 
        } 
        // (4) 如果index為0,說(shuō)明當(dāng)前入隊(duì)的Toast在隊(duì)頭,需要調(diào)用showNextToastLocked方法直接顯示 
        if (index == 0) { 
          showNextToastLocked(); 
        } 
      } finally { 
        Binder.restoreCallingIdentity(callingId); 
      } 
    } 
  } 

    可以看到,我對(duì)上述代碼做了簡(jiǎn)要的注釋。代碼相對(duì)簡(jiǎn)單,但是還有4點(diǎn)標(biāo)注代碼需要我們來(lái)進(jìn)一步探討。
    (1) 判斷是否為系統(tǒng)Toast。如果當(dāng)前Toast所屬的進(jìn)程的包名為“android”,則為系統(tǒng)Toast,否則還可以調(diào)用isCallerSystem()方法來(lái)判斷。該方法的實(shí)現(xiàn)源碼為:

 

  boolean isUidSystem(int uid) { 
    final int appid = UserHandle.getAppId(uid); 
    return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); 
  } 
  boolean isCallerSystem() { 
    return isUidSystem(Binder.getCallingUid()); 
  } 

    isCallerSystem的源碼也比較簡(jiǎn)單,就是判斷當(dāng)前Toast所屬進(jìn)程的uid是否為SYSTEM_UID、0、PHONE_UID中的一個(gè),如果是,則為系統(tǒng)Toast;如果不是,則不為系統(tǒng)Toast。
    是否為系統(tǒng)Toast,通過(guò)下面的源碼閱讀可知,主要有兩點(diǎn)優(yōu)勢(shì):

    系統(tǒng)Toast一定可以進(jìn)入到系統(tǒng)Toast隊(duì)列中,不會(huì)被黑名單阻止。
    系統(tǒng)Toast在系統(tǒng)Toast隊(duì)列中沒(méi)有數(shù)量限制,而普通pkg所發(fā)送的Toast在系統(tǒng)Toast隊(duì)列中有數(shù)量限制。

    (2) 查看將要入隊(duì)的Toast是否已經(jīng)在系統(tǒng)Toast隊(duì)列中。這是通過(guò)比對(duì)pkg和callback來(lái)實(shí)現(xiàn)的,具體源碼如下所示:

 

  private int indexOfToastLocked(String pkg, ITransientNotification callback) 
  { 
    IBinder cbak = callback.asBinder(); 
    ArrayList<ToastRecord> list = mToastQueue; 
    int len = list.size(); 
    for (int i=0; i<len; i++) { 
      ToastRecord r = list.get(i); 
      if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) { 
        return i; 
      } 
    } 
    return -1; 
  } 

    通過(guò)上述代碼,我們可以得出一個(gè)結(jié)論,只要Toast的pkg名稱(chēng)和tn對(duì)象是一致的,則系統(tǒng)把這些Toast認(rèn)為是同一個(gè)Toast。
    (3) 將當(dāng)前Toast所在進(jìn)程設(shè)置為前臺(tái)進(jìn)程。源碼如下所示:

  private void keepProcessAliveLocked(int pid) 
  { 
    int toastCount = 0; // toasts from this pid 
    ArrayList<ToastRecord> list = mToastQueue; 
    int N = list.size(); 
    for (int i=0; i<N; i++) { 
      ToastRecord r = list.get(i); 
      if (r.pid == pid) { 
        toastCount++; 
      } 
    } 
    try { 
      mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0); 
    } catch (RemoteException e) { 
      // Shouldn't happen. 
    } 
  } 

    這里的mAm=ActivityManagerNative.getDefault(),調(diào)用了setProcessForeground方法將當(dāng)前pid的進(jìn)程置為前臺(tái)進(jìn)程,保證不會(huì)系統(tǒng)殺死。這也就解釋了為什么當(dāng)我們finish當(dāng)前Activity時(shí),Toast還可以顯示,因?yàn)楫?dāng)前進(jìn)程還在執(zhí)行。
    (4) index為0時(shí),對(duì)隊(duì)列頭的Toast進(jìn)行顯示。源碼如下:

 

  private void showNextToastLocked() { 
    // 獲取隊(duì)列頭的ToastRecord 
    ToastRecord record = mToastQueue.get(0); 
    while (record != null) { 
      try { 
        // 調(diào)用Toast的回調(diào)對(duì)象中的show方法對(duì)Toast進(jìn)行展示 
        record.callback.show(); 
        scheduleTimeoutLocked(record); 
        return; 
      } catch (RemoteException e) { 
        Slog.w(TAG, "Object died trying to show notification " + record.callback 
            + " in package " + record.pkg); 
        // remove it from the list and let the process die 
        int index = mToastQueue.indexOf(record); 
        if (index >= 0) { 
          mToastQueue.remove(index); 
        } 
        keepProcessAliveLocked(record.pid); 
        if (mToastQueue.size() > 0) { 
          record = mToastQueue.get(0); 
        } else { 
          record = null; 
        } 
      } 
    } 
  } 

    這里Toast的回調(diào)對(duì)象callback就是tn對(duì)象。接下來(lái),我們看一下,為什么系統(tǒng)Toast的顯示時(shí)間只能是2s或者3.5s,關(guān)鍵在于scheduleTimeoutLocked方法的實(shí)現(xiàn)。原理是,調(diào)用tn的show方法展示完Toast之后,需要調(diào)用scheduleTimeoutLocked方法來(lái)將Toast消失。(如果大家有疑問(wèn):不是說(shuō)tn對(duì)象的hide方法來(lái)將Toast消失,為什么要在這里調(diào)用scheduleTimeoutLocked方法將Toast消失呢?是因?yàn)閠n類(lèi)的hide方法一執(zhí)行,Toast立刻就消失了,而平時(shí)我們所使用的Toast都會(huì)在當(dāng)前Activity停留幾秒。如何實(shí)現(xiàn)停留幾秒呢?原理就是scheduleTimeoutLocked發(fā)送MESSAGE_TIMEOUT消息去調(diào)用tn對(duì)象的hide方法,但是這個(gè)消息會(huì)有一個(gè)delay延遲,這里也是用了Handler消息機(jī)制)。

 

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

    首先,我們看到這里并不是直接發(fā)送了MESSAGE_TIMEOUT消息,而是有個(gè)delay的延遲。而delay的時(shí)間從代碼中“l(fā)ong delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;”看出只能為2s或者3.5s,這也就解釋了為什么系統(tǒng)Toast的呈現(xiàn)時(shí)間只能是2s或者3.5s。自己在Toast.makeText方法中隨意傳入一個(gè)duration是無(wú)作用的。
    接下來(lái),我們來(lái)看一下WorkerHandler中是如何處理MESSAGE_TIMEOUT消息的。mHandler對(duì)象的類(lèi)型為WorkerHandler,源碼如下:

  private final class WorkerHandler extends Handler 
  { 
    @Override 
    public void handleMessage(Message msg) 
    { 
      switch (msg.what) 
      { 
        case MESSAGE_TIMEOUT: 
          handleTimeout((ToastRecord)msg.obj); 
          break; 
      } 
    } 
  } 

    可以看到,WorkerHandler對(duì)MESSAGE_TIMEOUT類(lèi)型的消息處理是調(diào)用了handlerTimeout方法,那我們繼續(xù)跟蹤handleTimeout源碼:

  private void handleTimeout(ToastRecord record) 
  { 
    synchronized (mToastQueue) { 
      int index = indexOfToastLocked(record.pkg, record.callback); 
      if (index >= 0) { 
        cancelToastLocked(index); 
      } 
    } 
  } 

    handleTimeout代碼中,首先判斷當(dāng)前需要消失的Toast所屬ToastRecord對(duì)象是否在隊(duì)列中,如果在隊(duì)列中,則調(diào)用cancelToastLocked(index)方法。真相就要浮現(xiàn)在我們眼前了,繼續(xù)跟蹤源碼:

  private void cancelToastLocked(int index) { 
    ToastRecord record = mToastQueue.get(index); 
    try { 
      record.callback.hide(); 
    } catch (RemoteException e) { 
      // don't worry about this, we're about to remove it from 
      // the list anyway 
    } 
    mToastQueue.remove(index); 
    keepProcessAliveLocked(record.pid); 
    if (mToastQueue.size() > 0) { 
      // Show the next one. If the callback fails, this will remove 
      // it from the list, so don't assume that the list hasn't changed 
      // after this point. 
      showNextToastLocked(); 
    } 
  } 

    哈哈,看到這里,我們回調(diào)對(duì)象的hide方法也被調(diào)用了,同時(shí)也將該ToastRecord對(duì)象從mToastQueue中移除了。到這里,一個(gè)Toast的完整顯示和消失就講解結(jié)束了。

相關(guān)文章

  • 如何對(duì)jdk版本升級(jí)或降級(jí)

    如何對(duì)jdk版本升級(jí)或降級(jí)

    這篇文章主要介紹了如何對(duì)jdk版本升級(jí)或降級(jí)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java直接插入排序算法實(shí)現(xiàn)

    Java直接插入排序算法實(shí)現(xiàn)

    這篇文章主要介紹了Java直接插入排序算法實(shí)現(xiàn),有需要的朋友可以參考一下
    2014-01-01
  • mybatis generator 使用方法教程(生成帶注釋的實(shí)體類(lèi))

    mybatis generator 使用方法教程(生成帶注釋的實(shí)體類(lèi))

    下面小編就為大家?guī)?lái)一篇mybatis generator 使用方法教程(生成帶注釋的實(shí)體類(lèi))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • springboot3整合遠(yuǎn)程調(diào)用的過(guò)程解析

    springboot3整合遠(yuǎn)程調(diào)用的過(guò)程解析

    遠(yuǎn)程過(guò)程調(diào)用主要分為:服務(wù)提供者,服務(wù)消費(fèi)者,通過(guò)連接對(duì)方服務(wù)器進(jìn)行請(qǐng)求交互,來(lái)實(shí)現(xiàn)調(diào)用效果,這篇文章主要介紹了springboot3整合遠(yuǎn)程調(diào)用,需要的朋友可以參考下
    2023-06-06
  • Java容器源碼LinkedList原理解析

    Java容器源碼LinkedList原理解析

    這篇文章主要介紹了Java容器源碼LinkedList原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • java實(shí)戰(zhàn)之猜字小游戲

    java實(shí)戰(zhàn)之猜字小游戲

    這篇文章主要介紹了java實(shí)戰(zhàn)之猜字小游戲,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好的幫助呦,需要的朋友可以參考下
    2021-04-04
  • springboot的@Value中#和$區(qū)別詳解

    springboot的@Value中#和$區(qū)別詳解

    這篇文章主要介紹了springboot的@Value中#和$區(qū)別詳解,@Value注解的作用主要可以給屬性直接賦值、也可以讀取配置文件中的值給屬性賦值,需要的朋友可以參考下
    2023-11-11
  • 詳解在SpringBoot應(yīng)用中獲取應(yīng)用上下文方法

    詳解在SpringBoot應(yīng)用中獲取應(yīng)用上下文方法

    本篇文章主要介紹了詳解在SpringBoot應(yīng)用中獲取應(yīng)用上下文方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-04-04
  • 5種Java中數(shù)組的拷貝方法總結(jié)分享

    5種Java中數(shù)組的拷貝方法總結(jié)分享

    這篇文章主要介紹了5種Java中數(shù)組的拷貝方法總結(jié)分享,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-07-07
  • MyBatis執(zhí)行動(dòng)態(tài)SQL的方法

    MyBatis執(zhí)行動(dòng)態(tài)SQL的方法

    今天小編就為大家分享一篇關(guān)于MyBatis執(zhí)行動(dòng)態(tài)SQL的方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12

最新評(píng)論

平凉市| 建阳市| 玛多县| 商都县| 湖北省| 日土县| 新昌县| 贵阳市| 资阳市| 江陵县| 长岭县| 南宫市| 定结县| 扎囊县| 长海县| 广宁县| 山西省| 澄城县| 许昌县| 临夏市| 无锡市| 广安市| 汉沽区| 舞阳县| 甘洛县| 盐池县| 星座| 孟津县| 泾源县| 兴国县| 无极县| 和林格尔县| 平塘县| 五莲县| 沂南县| 会宁县| 淮安市| 衡阳县| 赤水市| 海口市| 上高县|