zoukankan      html  css  js  c++  java
  • 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>
    

    如上,为我对Toast实现的浅显认识,表做记录。

    Enjoytoday,EnjoyCoding

  • 相关阅读:
    2018年3月至4月小结
    前端面试中,经常看到垂直居中与水平居中,实际排版用的多吗?
    Hbuilder配置识别逍遥安卓模拟器
    php静态变量与方法与phar的使用
    切面反射获取方法
    Spring:源码解读Spring IOC原理
    怎样批量提取JPG照片的文件名
    如何1秒批量提取电脑文件夹中的所有文件、文件夹名字到txt/excel
    用powermock 方法中new对象
    springboot单元测试自动回滚:@Transactional
  • 原文地址:https://www.cnblogs.com/amiko/p/7906219.html
Copyright © 2011-2022 走看看