胖蔡说技术
随便扯扯

Toast实现源码解析

说明

本篇文章用于介绍Android中Toast的实现原理。和简单实现一个自定义的Toast.

Toast实现

一般常用Toast格式为:

Toast.makeText(context,"text.",Toast.LENGTH_LONG).show();

就此,对Toast做一个了解.首先,Toast调用来了一个静态方法makeText(…),具体实现如下:

/**
   * Make a standard toast that just contains a text view.
   *
   * @param context  The context to use.  Usually your {@link android.app.Application}
   *                 or {@link android.app.Activity} object.
   * @param text     The text to show.  Can be formatted text.
   * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
   *                 {@link #LENGTH_LONG}
   *
   */
  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;
  }

这里设置了Toast的三个属性:mNextView、mDuration.mNextView为显示的Toast view,Toast中使用的是系统的一套资源layout,我们其实可以据此替换绘制的View.再看,这里的Toast 对象的生成,代码如下:

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);
 }

发现生成一个TN对象,发现其是处理Toast显示的一个服务处理类。再看Toast.show()方法,代码如下:

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
    }
}

通过代码发现,这里通过Binder方式将处理发送给了INotificationManager来处理,INotificationManager.aidl的实现类为com.android.server.NotificationManagerService,在源码中找到enqueueToast(…)实现如下:

@Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            ...........
            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, callback);
                    // If it's already in the queue, we update it in place, we don't
                    // move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        // Limit the number of toasts that any given package except the android
                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
                        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;
                                     }
                                 }
                            }
                        }

                        record = new ToastRecord(callingPid, pkg, callback, duration);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveLocked(callingPid);
                    }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

如上代码,发现Toast发送的enqueue会被保存在一个List列表中,最后显示操作如下:

void showNextToastLocked() {
      ToastRecord record = mToastQueue.get(0);
      while (record != null) {
          if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
          try {
              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;
              }
          }
      }
  }

其中,scheduleTimeoutLocked(record);控制显示的时间,Toast提供的显示时间有俩个。通过设置Toast的duration类型控制,如下:

static final int LONG_DELAY = 3500; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds

处理回调交由Toast中的ITransientNotification.Stub 处理,即TN对象,show方法启动mShow线程,线程执行代码如下:

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();
           }
       }

最后还是通过WindowManager来加载Toast界面。

自定义Toast

自定义Toast就简单了,两种思路:一个是通过自定义toast的view;一个是通过WindowManager来控制加载view实现一个Toast,这个是最终的解决办法.如下,为简单的替换一个view,代码如下:

private void showToast(Context context){
    Toast toast=new Toast(context.getApplicationContext());
    LayoutInflater inflate = (LayoutInflater)
            this.getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v = inflate.inflate(R.layout.test, null);
    toast.setView(v);
    toast.setDuration(Toast.LENGTH_LONG);
    toast.show();
}

布局文件代码为:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="5dip"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">


    <Button
        android:text="cancel"
        android:background="@drawable/aa"
        android:textColor="@android:color/holo_blue_bright"
        android:layout_width="150dip"
        android:layout_height="50dip" />


</LinearLayout>

背景控制xml为:


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke android:width="1dp" android:color="#63a219"></stroke>
    <corners android:radius="5dp" />
</shape>
赞(1) 打赏
转载请附上原文出处链接:胖蔡说技术 » Toast实现源码解析
分享到: 更多 (0)

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏