Android Toast机制实现原理
Toast的实现原理
通过本文,你将懂得:
1. 为什么调用Toast的子线程需要Looper.prepare()
2. Toast的Window是在哪里创建的
Toast中的IPC通信
在Toast的实现中主要有两类IPC通信:
1. 从Toast通过IPC访问NotificationManagerService(以下简称NMS)
2. 从NMS通过IPC访问Toast
其中,Toast通过SystemServer来获取NMS的远程代理对象;NMS通过Toast传递过来的远程代理对象TN来进行IPC。
TN类 典型的AIDL生成的IBinder服务端Stub类。
private static class TN extends ITransientNotification.Stub {
/**
* 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);
}
}
NotificationManagerService 是通过ToastRecord中的callback来回调TN中的方法的,实际上callback就是TN在客户端中的类。
private static final class ToastRecord
{
final int pid;
final String pkg;
final ITransientNotification callback;
int duration;
Binder token;
}
源码分析
从Toast类的show()方法开始:
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService(); //获得NMS的Proxy对象
String pkg = mContext.getOpPackageName(); //包名
TN tn = mTN; //Toast端的IBinder对象,用来给NMS回调
tn.mNextView = mNextView;
try {
//远程调用NMS的enqueueToast方法
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
//获得Service的远程代理对象
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
然后就来到了NMS的enqueueToast方法:
//这里只展示主要语句
public void enqueueToast(String pkg, ITransientNotification callback, int duration) {
//判断是否是系统调用
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
//同步Toast队列
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback); //查找是否已经存在
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration); //更新展示时间
} else {
//如果是非系统的toast,限制每个应用在队列中只能保持个ToastRcord
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;
}
}
}
}
//产生一个窗口令牌,Toast拿到这个令牌之后才能创建系统级的Window
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token,
WindowManager.LayoutParams.TYPE_TOAST);
//创建toastRecord并加入队列
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveIfNeededLocked(callingPid);
}
if (index == 0) {
//展示下个toast
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
//回调Toast.TN的show方法
record.callback.show(record.token);
scheduleTimeoutLocked(record);
return;
}
//展示时间完毕之后,再回调TN的cancel方法
private void scheduleTimeoutLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
这里Toast通过调用NMS的远程方法,把pkg包名、callback回调、duration,构建一个ToastRecord,加入到ToastQueue队列中。
NMS通过内部的WindowManagerInternal服务来产生一个token,把这个token通过IPC再回传给Toast。
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
IBinder token = (IBinder) msg.obj;
handleShow(token);
}
};
public void handleShow(IBinder windowToken) {
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);
// 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.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
//设置token
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);
//把视图添加到Toast类型的Window里面
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
Toast拿到具有创建系统级窗口权限的token之后就可以创建Window,并且把自己的视图添加上去。
展示时间完毕,或者主动cancel的时候:
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
//回调TN的hide方法
record.callback.hide();
} catch (RemoteException e) {
}
//移出队列
ToastRecord lastToast = mToastQueue.remove(index);
//删除分配的token
mWindowManagerInternal.removeWindowToken(lastToast.token, true);
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
//只有当一个Toast执行完毕的时候才进行下一个Toast
showNextToastLocked();
}
}
解决问题
现在来解决开头的问题:
1、为什么调用Toast的子线程需要Looper.prepare()
因为TN中的方法show()、hide()是在Binder线程池执行的,需要通过Handler机制把消息发送到调用Toast的子线程中执行进一步的操作。
2、Toast的Window是在哪里创建的
在Toast中创建,但是需要NMS提供的token,Toast类型的Window是系统级的Window,有了这个token才有权限创建系统级的Window。
上一篇: List调用toString()方法后,去除两头的中括号实例
下一篇: R的一些操作