轉載請註明出處:王亟亟的大牛之路
這兩天 在看一些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;
}
其他方法,就不一一介紹了
總結:
- Toast的構造函數會構建一個靜態內部類TN的實例
- TN對象通過INotificationManager和Handler實現IPC通信並實現,show()/hide()/cancel()方法,使用宿主Context和WindowManager對象構建UI
- Toast的show()方法在應用端使用INotificationManager給實現端發送消息