Toast源码分析与学习

Posted 彼岸花you

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Toast源码分析与学习相关的知识,希望对你有一定的参考价值。

源码查看网址,这个是当前我所用的源码地址,自备梯子

知识点补充

android使用注解替代枚举
android 进程间通信使用aidl和Messenger类

源码分析

涉及源码有些长,下面只截取了部分分析,github toast相关源码

① 从Toast.java中的变量定义开始
我们都知道在实际使用中,Toast显示的时间只有两种情况。我们先从源码看看是怎么回事。

    static final String TAG = "Toast";
    static final boolean localLOGV = false;

    /** @hide */
    @IntDef(LENGTH_SHORT, LENGTH_LONG)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Duration 

    /**
     * Show the view or text notification for a short period of time.  This time
     * could be user-definable.  This is the default.
     * @see #setDuration
     */
    public static final int LENGTH_SHORT = 0;

    /**
     * Show the view or text notification for a long period of time.  This time
     * could be user-definable.
     * @see #setDuration
     */
    public static final int LENGTH_LONG = 1;

    final Context mContext;
    final TN mTN;
    int mDuration;
    View mNextView;

这里使用注解的方式规定了Duration的取值,只有两个值,LENGTH_SHORT和LENGTH_LONG。在看一下源码中对Duration的设置

    /**
     * Set how long to show the view for.
     * @see #LENGTH_SHORT
     * @see #LENGTH_LONG
     */
    public void setDuration(@Duration int duration) 
        mDuration = duration;
        mTN.mDuration = duration;
    

    /**
     * Return the duration.
     * @see #setDuration
     */
    @Duration
    public int getDuration() 
        return mDuration;
    

明了了吧,这里使用@Duration对传入的参数进行了规定,如果想传入其他的int值,在代码中是会有标记

② 看看Toast.java的构造函数

    /**
     * Construct an empty Toast object.  You must call @link #setView before you
     * can call @link #show.
     *
     * @param context  The context to use.  Usually your @link android.app.Application
     *                 or @link android.app.Activity object.
     */
    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);
    

由界面传入一个上下文实例context,后面的代码貌似是创建一个TN对象,我们现在来看看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;
        int mDuration;

        WindowManager mWM;

        static final long SHORT_DURATION_TIMEOUT = 5000;
        static final long LONG_DURATION_TIMEOUT = 1000;

        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;
        

        /**
         * 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();
                String packageName = mView.getContext().getOpPackageName();
                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;
                mParams.packageName = packageName;
                mParams.removeTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                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;
            
        
    

这个类继承ITransientNotification.stub,那么这个ITransientNotification是一个aidl文件
ITransientNotification.aidl(@hind)源码

20/** @hide */
21 oneway interface ITransientNotification 
22    void show();
23    void hide();
24

上面的TN继承这个类并提供了具体的方法show和hind,在具体的实现中使用Handler来post两个runnable(TN类开头定义的两个Runnable),两个Runnable中分别执行了handleShow()和handleHind()方法,推测是控制Toast显示和隐藏的具体方法。看具体的实现:
在handleShow()方法中,通过获取WindowManager将创建的View add到界面上,在handleHind()方法中获取WindowManager remove掉这个View.

④ 根据Toast.java的构造函数和上面对TN的分析 小结一下。
使用过程中,创建一个toast对象,会实例化一个内部类TN,这个TN类能够控制Toast显示和隐藏(内部由WindowManager add 和remove view来实现显示和隐藏)。
⑤ 构造toast view 之后,toast.show();

    /**
     * Show the view for the specified duration.
     */
    public void show() 
        if (mNextView == null) 
            throw new RuntimeException("setView must have been called");
        

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try 
            service.enqueueToast(pkg, tn, mDuration);
         catch (RemoteException e) 
            // Empty
        
    
    private static INotificationManager sService;

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

又出现了几个不认识的东西,看看具体是什么。
- ServiceManager.java(@hind)

26 /** @hide */
27public final class ServiceManager 
28    private static final String TAG = "ServiceManager";
29
30    private static IServiceManager sServiceManager;
31    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
32
33    private static IServiceManager getIServiceManager() 
34        if (sServiceManager != null) 
35            return sServiceManager;
36        
37
38        // Find the service manager
39        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
40        return sServiceManager;
41    
42
43    /**
44     * Returns a reference to a service with the given name.
45     *
46     * @param name the name of the service to get
47     * @return a reference to the service, or <code>null</code> if the service doesn't exist
48     */
49    public static IBinder getService(String name) 
50        try 
51            IBinder service = sCache.get(name);
52            if (service != null) 
53                return service;
54             else 
55                return getIServiceManager().getService(name);
56            
57         catch (RemoteException e) 
58            Log.e(TAG, "error in getService", e);
59        
60        return null;
61    
62
63    /**
64     * Place a new @a service called @a name into the service
65     * manager.
66     *
67     * @param name the name of the new service
68     * @param service the service object
69     */
70    public static void addService(String name, IBinder service) 
71        try 
72            getIServiceManager().addService(name, service, false);
73         catch (RemoteException e) 
74            Log.e(TAG, "error in addService", e);
75        
76    
    ...
139 

ServiceManager详解
文章重点:ServiceManager管理整个系统的Service,实际功能发面包括:
- 提供IBinder对象(在每个进程中,该IBinder对象是唯一的)就好比我获取一个闹钟的service,那在当前这个app进程中这个service就是唯一的。
- 让各个service注册到ServiceManager中。
简单来说ServiceManager提供getIBinder的方法和每个service都会注册到它。
INotificationManager.aidl(@hind)

/** @hide */
36interface INotificationManager
37 
38    void cancelAllNotifications(String pkg, int userId);
39
40    void enqueueToast(String pkg, ITransientNotification callback, int duration);
41    void cancelToast(String pkg, ITransientNotification callback);
    ...
100 

ITransientNotification.aidl(@hind)

20/** @hide */
21 oneway interface ITransientNotification 
22    void show();
23    void hide();
24

回到Toast.java 源码

我们操作中调用的Toast show方法在源码中的操作其实是获取这个INotificationManager的实例,然后enqueueToast和cancelToast.

源码分析总结

以上是关于Toast源码分析与学习的主要内容,如果未能解决你的问题,请参考以下文章

Toast源码分析(Android 11)

Toast源码深度分析

Android中的Toast源码分析和自定义Toast

Toast的window创建过程以及源码分析

Toast的window创建过程以及源码分析

Android源码分析Window添加