Android Toast 源碼分析

轉載請註明出處:王亟亟的大牛之路

這兩天 在看一些Window的實現,順便掃到了Toast,這一篇做一些Toast實現原理的分析
日常安利:https://github.com/ddwhan0123/Useful-Open-Source-Android


Toast位於package android.widget目錄下,跟各類系統提供的控件在一起
在沒看源碼之前以爲他是一個Viewpublic class Toast {},結果看只是一個普通的class,甚至連繼承都沒有,帶着疑問我們看下是如何實現,彈窗功能的


構造函數
我們平時其實幾乎不會new Toast來做一些操作,但是爲了介紹TN對象是什麼,所以還是來介紹下

public Toast(Context context) {
        this(context, null);
    }

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        //創建了一個TN對象
        mTN = new TN(context.getPackageName(), looper);
       //初始化位置    
        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);
    }

最常用的方法makeText

這方法最重要的是給Toast的mNextView對象進行了初始化,在後面的TN對象中也一直會使用這個View對象

 public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);
		//構建簡單的視圖
        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;
    }

TN
TN是Toast類的一個靜態內部類,是我們具體實現復層彈窗效果的類,他繼承於ITransientNotification.Stub,也就是說他是一個實用AIDL的方式實現IPC的操作

 private static class TN extends ITransientNotification.Stub {
       TN(String packageName, @Nullable Looper looper) {
           //構建是覺得相關屬性
            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;
			//因爲是跨進程通行,所以用包名作爲主Key
            mPackageName = packageName;
			//無論傳不傳都會構建起handler迴路
            if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
            //通過handler處理顯示,隱藏,取消等操作
            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
                        case HIDE: {
                            handleHide();
                            mNextView = null;
                            break;
                        }
                        case CANCEL: {
                            handleHide();
                            mNextView = null;
                            try {
                                getService().cancelToast(mPackageName, TN.this);
                            } catch (RemoteException e) {
                            }
                            break;
                        }
                    }
                }
            };
        }
}

handleShow()和handleHide()兩個方法是具體實現view這一層次的 方法,我們來解讀一下

  public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
         	//如果事物隊列裏有另外2個隱藏/取消的指令那麼就不顯示了
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            //不同的內容view纔會觸發刷新,這裏的mNextView就是makeText方法創建的view
            if (mView != mNextView) {
                handleHide();
                mView = mNextView;
                //獲取宿主最大生命週期的上下文對象(有可能拿不到值)
                Context context = mView.getContext().getApplicationContext();
                //獲取包名
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                //初始化WindowManager對象
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                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.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                //移除不該存在的View
                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);
                //添加有效的view以及他的一些配置參數
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }
    public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    //WindowManager移除view
                    mWM.removeViewImmediate(mView);
                }
                try {
                    getService().finishToken(mPackageName, this);
                } catch (RemoteException e) {
                }
				//強制回收
                mView = null;
            }
        }

TN內部的實現已經講了,現在 看看對外暴露的Toast那些方法的實現


Toast的方法

   public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
		//初始化屬性,屬性其實都是TN對象或容器構建的
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        try {
        //發送消息
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

cancel

  public void cancel() {
	    //調用的是TN對象的取消,通過AIDL+Handler實現
        mTN.cancel();
    }

setView

  public void setView(View view) {
  		//設置的是makeText方法構建的view
        mNextView = view;
    }

其他方法,就不一一介紹了


總結:

  1. Toast的構造函數會構建一個靜態內部類TN的實例
  2. TN對象通過INotificationManager和Handler實現IPC通信並實現,show()/hide()/cancel()方法,使用宿主Context和WindowManager對象構建UI
  3. Toast的show()方法在應用端使用INotificationManager給實現端發送消息

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章