1、Toast概念和問題的引出
Toast 中文名"土司"回溺,應(yīng)該算是 Android 使用頻率很高的一個(gè) widget 了,一般使用它來做一些操作的提示信息,例如我們?cè)谔詫汓c(diǎn)擊收藏寶貝之后底部就會(huì)出現(xiàn)"收藏成功"這樣的提示信息累贤,這種就是 Toast 的功能了。
先拋出一個(gè)問題哦:那就是多個(gè)應(yīng)用或者多個(gè)線程都要在顯示 Toast 的時(shí)候系統(tǒng)是怎么去處理這個(gè)問題的少漆?是串行執(zhí)行還是并行執(zhí)行呢臼膏?
2、如何使用 Toast
Toast 的使用可以說是非常簡(jiǎn)單示损,簡(jiǎn)單到一句話就可以顯示一個(gè) Toast 了渗磅,下面是具體的代碼體現(xiàn)。
Toast.makeText(this, "hello Toast", Toast.LENGTH_SHORT).show();
3检访、構(gòu)造 Toast 對(duì)象
下面就依據(jù)上面的那句代碼分析 Toast 顯示到手機(jī)屏幕上是需要經(jīng)過什么過程始鱼。
主要入口為兩個(gè):
- public static Toast makeText(Context context, CharSequence text, int duration)
- public void show()
3.1、makeText 源碼分析
makeText 方法內(nèi)部主要做了這樣幾件事:
- 創(chuàng)建一個(gè) Toast 對(duì)象脆贵;
- 加載一個(gè)系統(tǒng)布局医清,這個(gè)布局就是用顯示這個(gè) Toast 的;
- 設(shè)置需要顯示的文本內(nèi)容丹禀,就是我們?cè)谏厦嬖O(shè)置的 "hello Toast" 和設(shè)置這個(gè) Toast 需要顯示的時(shí)間;
其實(shí)就根據(jù)傳入到 makeText 內(nèi)部的幾個(gè)屬性構(gòu)造出一個(gè) Toast 對(duì)象状勤。
public static Toast makeText(Context context, CharSequence text, @Duration 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;
}
3.2鞋怀、Toast 構(gòu)造方法
Toast 的構(gòu)造方法內(nèi)部很簡(jiǎn)單,主要是創(chuàng)建一個(gè) TN 對(duì)象持搜,并且賦值給 mTN密似,這個(gè) TN 對(duì)象非常重要,在下面分析中再慢慢說明它的作用葫盼。因?yàn)?TN 對(duì)象 是在 Toast 構(gòu)造中創(chuàng)建的残腌,因此一個(gè) Toast 對(duì)象的創(chuàng)建就對(duì)應(yīng)創(chuàng)建一個(gè) TN 對(duì)象,這句話也很重要贫导。
public Toast(Context context) {
mContext = context;
//創(chuàng)建一個(gè) TN 對(duì)象
mTN = new TN();
//設(shè)置顯示的位置
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);
}
4抛猫、TN 類的分析
看 TN 類先看它的聲明,學(xué)過 AIDL 的同學(xué)應(yīng)該都知道孩灯,TN 繼承了 ITransientNotification.Stub 類闺金,說明它是遠(yuǎn)程服務(wù)真正干活的類。這什么意思呢峰档?其實(shí)是這樣的败匹,當(dāng)客戶端需要顯示一個(gè)土司,那么這個(gè)要求是要交給遠(yuǎn)程服務(wù) NotificationManagerService 去統(tǒng)一調(diào)度的【這里是第一次 IPC 過程:用戶進(jìn)程訪問服務(wù)進(jìn)程 NotificationManagerService 】讥巡。
而 NotificationManagerService 調(diào)度完成之后是怎么通知用戶進(jìn)程去 show / hide 土司的呢掀亩?遠(yuǎn)程服務(wù)進(jìn)程通過 mTN 與用戶進(jìn)程(當(dāng)前進(jìn)程)進(jìn)行交互(show,hide等操作)【這里是第二次 IPC 過程:服務(wù)進(jìn)程 NotificationManagerService 訪問 用戶進(jìn)程 TN】。
private static class TN extends ITransientNotification.Stub {
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() {
@Override
public void handleMessage(Message msg) {
IBinder token = (IBinder) msg.obj;
handleShow(token);
}
};
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(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(0, windowToken).sendToTarget();
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
public void handleShow(IBinder windowToken) {
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().getConfigura
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDi
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;
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.removeViewImmediate(mView);
}
mView = null;
}
}
}
5欢顷、顯示一個(gè)Toast
在 show 方法中首先判斷 mNextView 是否為空槽棍,這個(gè) mNextView 就是加載出來需要展示 Toast 的布局啦,如果連布局都沒有抬驴,那么顯示什么土司炼七,直接就 throw 了。
獲取包名 pkg 怎爵,為什么需要包名呢特石,因?yàn)樵谶h(yuǎn)程服務(wù)它會(huì)拿這個(gè)包名去判斷需要顯示的 Toast 是不是來至同一個(gè)進(jìn)程的(一個(gè)進(jìn)程對(duì)應(yīng)一個(gè)包名),具體的判斷過程待會(huì)再分析鳖链。
給 tn 賦值 mTN ,還記得這個(gè) mTN 不墩莫?他就是在 Toast.makeText 內(nèi)部方法中創(chuàng)建的(也就是在Toast構(gòu)造中創(chuàng)建)芙委,我在前面說過一個(gè) Toast 對(duì)象對(duì)應(yīng)一個(gè) mTN 對(duì)象。
調(diào)用 IPC 通知遠(yuǎn)程服務(wù) NotificationServiceManager 去enqueueToast狂秦,這個(gè)過程非常重要灌侣。我們看到它將一個(gè) tn 作為參數(shù)傳入,這是干嘛用呢裂问?我們先前在介紹 TN 這個(gè)類時(shí)說過侧啼,這個(gè)類是用于進(jìn)程間通訊的牛柒,因?yàn)轱@示和隱藏 Toast 是需要由遠(yuǎn)程的 NMS 去統(tǒng)一調(diào)度的,它內(nèi)部維護(hù)一個(gè)集合存放一個(gè)個(gè) Toast 痊乾,當(dāng)?shù)搅酥付〞r(shí)間可以顯示這個(gè) Toast 了皮壁,那么 NMS 就可以通過 TN 這個(gè)代理類去通知客戶端去真正顯示一個(gè) Toast 。
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
//獲取遠(yuǎn)程服務(wù) INotificationManager
INotificationManager service = getService();
//獲取包名 pkg
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
5.1哪审、INotificationManager.Stub.Proxy.enqueueToast
service.enqueueToast(pkg, tn, mDuration) 這個(gè)方法的調(diào)用實(shí)際上是通過
INotificationManager的代理對(duì)象去執(zhí)行的蛾魄,具體執(zhí)行代碼就在下面:主要就是將數(shù)據(jù)寫入到 Parcel _data 中,然后調(diào)用 mRemote.transact 進(jìn)行 RPC 遠(yuǎn)程調(diào)用操作,之后通過 _reply 獲取返回的數(shù)據(jù)湿滓。
//INotificationManager.Stub.Proxy.enqueueToast 代碼
public void enqueueToast(java.lang.String pkg, android.app.ITransientNotification callback, int duration) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(pkg);
//callback就是 tn 對(duì)象滴须,這里需要傳遞給遠(yuǎn)程服務(wù),因此轉(zhuǎn)化為 Bidner
_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
_data.writeInt(duration);
mRemote.transact(Stub.TRANSACTION_enqueueToast, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
6叽奥、開始遠(yuǎn)程服務(wù)調(diào)用 Toast
[ NMS 的源碼] (https://android.googlesource.com/platform/frameworks/base/+/f76a50c/services/java/com/android/server/NotificationManagerService.java) 有一個(gè)網(wǎng)站 grepcode 是可以查閱源碼的扔水,有需要可以上這里看看源碼。
在 enqueueToast 方法內(nèi)部主要做了這幾件事:
- synchronize 加鎖判斷朝氓,這里為什么要加鎖魔市?
- indexOfToastLocked校驗(yàn)操作;
- 根據(jù) indexOfToastLocked 的校驗(yàn)結(jié)果進(jìn)行后續(xù)的操作膀篮。
public void enqueueToast(String pkg, ITransientNotification callback, int duration) {
//pkg 和 callback 的空校驗(yàn) 代碼省略...
//加鎖判斷
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
//查找隊(duì)列中是否由該 ToastRecord
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.
//表示在隊(duì)列中找到對(duì)應(yīng)的 ToastRecord
if (index >= 0) {
//更新這個(gè) ToastRecord 的顯示時(shí)間嘹狞,這種情況是在同一個(gè)用用中,使用同一個(gè) Toast 對(duì)象誓竿,在極短時(shí)間內(nèi)多次調(diào)用show方法磅网,就會(huì)出現(xiàn)這個(gè)情況。
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 (!"android".equals(pkg)) {//不是系統(tǒng)彈的土司
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++;
//控制 toast 的次數(shù) 50
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
}
//沒有找到的情況就創(chuàng)建一個(gè) ToastReord 對(duì)象筷屡。
record = new ToastRecord(callingPid, pkg, callback, duration);
//添加到隊(duì)列中涧偷。
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);
}
}
}
6.1、enqueueToast 加鎖判斷
這里要思考一下為什么這里要加鎖毙死?我們知道鎖就是為了怕并發(fā)訪問出現(xiàn)數(shù)據(jù)的不同步問題燎潮,NMS 是系統(tǒng)服務(wù),那么它的對(duì)象就只有一個(gè)扼倘,多個(gè)進(jìn)程(多個(gè) APP) 或者說這多個(gè)線程都可以獲取到這個(gè)服務(wù)确封,并且可以并發(fā)地去調(diào)用該服務(wù)的 enqueueToast 方法,這就像多個(gè)線程操作同一個(gè)對(duì)象的共享變量出現(xiàn)的數(shù)據(jù)不同步問題了再菊,因此這里需要加鎖爪喘。還有就是 enqueueToast 方法是服務(wù)端的方法,它會(huì)在Binder 線程池中去調(diào)用纠拔。
6.2秉剑、indexOfToastLocked的校驗(yàn)操作
檢測(cè)當(dāng)前pag 包名和 callback 是否在 mToastQueue 中已經(jīng)存在?在分析之前首先了解一下 callback 是什么呢稠诲?我們?cè)谙惹皠?chuàng)建 Toast 構(gòu)造提到過侦鹏,一個(gè) Toast 對(duì)象會(huì)對(duì)應(yīng)一個(gè) mTN 對(duì)象诡曙。這個(gè) callback 就是這個(gè) mTN 對(duì)象的代理對(duì)象,為什么略水?因?yàn)樗切枰M(jìn)行進(jìn)程間通訊的价卤,因此需要將 mTN 對(duì)象轉(zhuǎn)化為 Binder 才能傳輸,而在 NMS 中獲取到這個(gè) Binder 之后會(huì)將其轉(zhuǎn)化為 ITransientNotification.Proxy 代理對(duì)象聚请。
該方法會(huì)返回一個(gè)整數(shù)荠雕,-1表示沒有找到,其他值表示找到了驶赏。
private int indexOfToastLocked(String pkg, ITransientNotification callback)
{
IBinder cbak = callback.asBinder();
ArrayList<ToastRecord> list = mToastQueue;
int len = list.size();
for (int i=0; i<len; i++) {
ToastRecord r = list.get(i);
//包名和binder都一樣時(shí)才表示是同一個(gè) ToastRecord
if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
return i;
}
}
return -1;
}
6.3炸卑、根據(jù) indexOfToastLocked 的校驗(yàn)結(jié)果進(jìn)行后續(xù)的操作。
1.如果找到了那么返回值不為-1煤傍,那么就直接更新一下 duration 并且不會(huì)去修改該 ToastRecord 為 mToastQueue 中的位置盖文。意思是什么呢?這里舉個(gè)栗子:這里 for 循環(huán) 500 次蚯姆,每次都調(diào)用 toast.show() 方法五续,其運(yùn)行結(jié)果只會(huì)顯示一次,為什么呢龄恋?因?yàn)槲覀冊(cè)谏厦嫣岬竭^疙驾,使用 indexOfToastLocaked 內(nèi)部是根據(jù) pkg 和 callback 進(jìn)行判斷的,而這兩個(gè)值是一樣的郭毕,因此該方法返回的值不為 -1 它碎,因此就算是 show 500 次其 mToastQueue 中也只有一個(gè) ToastRecord 對(duì)象。
Toast toast = Toast.makeText(this, "Toast", Toast.LENGTH_SHORT);
for (int i = 0; i < 500; i++) {
toast.show();
}
2.如果找到的值為-1表示需要?jiǎng)?chuàng)建一個(gè)新的 ToastRecord 并且添加到 mToastQueue 中显押。因?yàn)橹暗玫降?index 為 -1 扳肛,因此在這里需要重新計(jì)算 index = mToastQueue.size()-1,這個(gè) index 就表示當(dāng)前新創(chuàng)建 ToastRecord 在隊(duì)列中的位置乘碑,當(dāng)這個(gè)位置為 0 時(shí)表示只有隊(duì)列中只有一個(gè) ToastRecord 挖息,直接執(zhí)行 showNextToastLocked 即可。
3.注意這里兽肤,如果沒有知道的情況套腹,并且同一個(gè) pkg 下的 ToastRecord 的數(shù)量超過了 MAX_PACKAGE_NOTIFICATIONS 那么就直接 return 了,官方表示這是為了避免 DOS attacks资铡。
4.這里注意下面這一種情況沉迹,執(zhí)行結(jié)果是 toast 會(huì)執(zhí)行 50 次,多余都被拋棄了害驹,原因看上面第 3 點(diǎn)。但是重點(diǎn)這種寫法跟第 1 點(diǎn)的寫法的區(qū)別就是 Toast 的創(chuàng)建時(shí)機(jī)不一樣蛤育,這里是每循環(huán)一次就創(chuàng)建一個(gè)宛官,也就是 Toast 的構(gòu)造就會(huì)走一次葫松,還記得上面說過 mTN 是在 Toast 構(gòu)造創(chuàng)建的,每次調(diào)用 makeText 都會(huì)創(chuàng)建一個(gè)新的 Toast 對(duì)象底洗,也就是 mTN 也是一個(gè)新的對(duì)象腋么。那么傳遞給 NMS 的 mTN 就不一樣了,那么在進(jìn)行 indexOfToastLocked 就會(huì)返回 -1 因此這里就是這段代碼的區(qū)別亥揖。
for (int i = 0; i < 500; i++) {
Toast toast = Toast.makeText(this, "Toast", Toast.LENGTH_SHORT).show();
}
7珊擂、遠(yuǎn)程服務(wù)端調(diào)度顯示和隱藏操作
7.1、showNextToastLocked()
這個(gè)方法主要做了一下幾件事:
從隊(duì)列中取出一個(gè) ToastRecord 并且通過 IPC 調(diào)用用戶進(jìn)程的 TN 中的 show 方法真正去顯示一個(gè) Toast 费变。
通過 Handler 發(fā)送一個(gè)延時(shí)任務(wù)摧扇,時(shí)間為 duration ,在這個(gè)時(shí)間過后就取消顯示這個(gè) Toast挚歧。
private void showNextToastLocked() {
//從隊(duì)列中取出第一個(gè) ToastRecord 對(duì)象
ToastRecord record = mToastQueue.get(0);
//while 循環(huán)是怕應(yīng)用進(jìn)程出現(xiàn)異常 RemoteException
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
//通知應(yīng)用進(jìn)程去執(zhí)行顯示操作扛稽,實(shí)際上會(huì)調(diào)用 TN 的 show() 方法。
record.callback.show();
//在指定時(shí)間隱藏這個(gè) Toast
scheduleTimeoutLocked(record, false);
return;
} catch (RemoteException e) {
//出現(xiàn)異常的情況滑负,那么就從隊(duì)列中移除
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
//從隊(duì)列中再取出下一個(gè) ToastRecord 去執(zhí)行在张。
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
//如果沒有就置null,退出循環(huán)
record = null;
}
}
}
}
7.2矮慕、record.callback.show();
record.callback 就是用戶進(jìn)程傳遞過來服務(wù)進(jìn)程的 ITransientNotification 的代理對(duì)象帮匾,通過調(diào)用該代理對(duì)象的 show() 方法,這個(gè)過程是一個(gè) IPC 的過程痴鳄,這個(gè)最終會(huì)調(diào)用用戶進(jìn)程的 TN 中的 show() 方法瘟斜。
public void show() {
//通過 handler 去 post 一個(gè)任務(wù)
mHandler.post(mShow);
}
final Runnable mShow = new Runnable() {
public void run() {
handleShow();
}
};
//通過 WindowManager 將 View 顯示出來
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;
mWM = WindowManagerImpl.getDefault();
final int gravity = mGravity;
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();
}
}
7.3、 scheduleTimeoutLocked
使用 Handler 發(fā)送一個(gè)延時(shí)消息夏跷,時(shí)間就是 Toast 設(shè)置的 duration 屬性哼转。
private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
{
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
mHandler.removeCallbacksAndMessages(r);
mHandler.sendMessageDelayed(m, delay);
}
7.4、 handleTimeout
private final class WorkerHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
}
}
}
private void handleTimeout(ToastRecord record)
{
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
7.5、cancelToastLocked
當(dāng)通過 Handler 發(fā)送的延時(shí)任務(wù)的時(shí)間到了那么就需要通知應(yīng)用進(jìn)程去 hide 這個(gè) Toast 萍鲸。具體操作還是通過 IPC 過程去真正調(diào)用 TN 的 hide 方法實(shí)現(xiàn)真正的隱藏操作家夺。
當(dāng)操作完成之后判斷隊(duì)列中是否還有 ToastRecord 沒有執(zhí)行,如果有那么就調(diào)用 showNextToastLocked 方法再次的去取出 ToastRecord 去執(zhí)行佣蓉。
private void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
//遠(yuǎn)程調(diào)用應(yīng)用進(jìn)程的 TN 的 hide() 方法隱藏 toast
record.callback.hide();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
// don't worry about this, we're about to remove it from
// the list anyway
}
mToastQueue.remove(index);
keepProcessAliveLocked(record.pid);
//如果隊(duì)列中還有數(shù)據(jù)那么就再次調(diào)用 showNextToastLocked 方法流程跟上面是一樣的。
if (mToastQueue.size() > 0) {
// Show the next one. If the callback fails, this will remove
// it from the list, so don't assume that the list hasn't changed
// after this point.
showNextToastLocked();
}
}