Toast源码分析(Android 11)
Posted guangdeshishe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Toast源码分析(Android 11)相关的知识,希望对你有一定的参考价值。
基于android R(11) ,targetSdkVersion 30源码分析
基本用法
- 普通Toast:
Toast.makeText(this@MainActivity, "hello Toast!", Toast.LENGTH_SHORT).show()
- 自定义View的Toast:
val toast = Toast(this@MainActivity)
val view = Button(this@MainActivity)
view.text = "custom toast!"
toast.setView(view)
toast.show()
- 不同版本差异:
- Android R以及以上:
- Toast的setView方法标记了过期,使用了该方法的自定义Toast,如果当前APP不在前台则不会显示;也就是以后慢慢会禁止自定义Toast的View,官方建议使用Snackbar代替自定义View的Toast
- Android Q以及以上:
- 普通Toast由系统绘制显示,相当于显示一个系统组件;
- 自定义View的Toast由客户端处理显示/隐藏
- Android Q以下:不管是普通Toast还是自定义View的Toast,全部都由客户端处理显示/隐藏
- Android R以及以上:
源码分析
Toast.java关键源码:
public class Toast {
private View mNextView;//Toast自定义View
private CharSequence mText;//显示的文案
int mDuration;//显示时长
private final ToastPresenter mPresenter;//辅助类,Toast类型的Window添加和移除等
class TN extends ITransientNotification.Stub {
TN(){
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();
break;
}
}
}
};
}
public void handleShow(IBinder windowToken) {
mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
mHorizontalMargin, mVerticalMargin,
new CallbackBinder(getCallbacks(), mHandler));
}
@UnsupportedAppUsage
public void handleHide() {
mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler));
}
public void show(IBinder windowToken) {
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
public void hide() {
mHandler.obtainMessage(HIDE).sendToTarget();
}
}
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
Toast result = new Toast(context, looper);
result.mText = text;
result.mDuration = duration;
return result;
} else {
Toast result = new Toast(context, looper);
View v = ToastPresenter.getTextToastView(context, text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
}
@Deprecated
public void setView(View view) {
mNextView = view;
}
public void show() {
INotificationManager service = getService();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
// It's a custom toast
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// It's a text toast
ITransientNotificationCallback callback =
new CallbackBinder(mCallbacks, mHandler);
service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
}
} else {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
}
} catch (RemoteException e) {
// Empty
}
}
}
- mNextView:Toast自定义的View,AndroidQ以下默认是使用系统布局,Q以及以上则默认为null,由系统去显示
- TN类:继承自Binder,自定义Toast的VIew时供NotificationManagerService调用,用于客户端控制Toast的显示/隐藏
- Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM):可以简单的理解为判断是否Android Q以及以上系统
通过Toast.java源码可以看出:
Toast调用show方法显示时,并没有直接调用Toast的handleShow()方法 ;而是调用NotificationManagerService的相关方法,将Toast放入某个队列当中;
这是因为Toast每次只能显示一个,每个Toast显示时长也不一样,而且每个APP都可以显示很多个Toast,不同应用之间需要通过NMS统一调度,否则所有Toast各自显示各自的就乱了。
- NotificationManagerService.enqueueToast:用于自定义View的Toast显示,NMS通过TN对象来回调客户端方法控制
- NotificationManagerService.enqueueTextToast:用于默认普通Toast显示,使用的是系统组件去显示
可以看到自定义View的Toast最后是通过ToastPresenter.java的show和hide方法控制显示的。
ToastPresenter.java关键源码
public class ToastPresenter {
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
@Nullable ITransientNotificationCallback callback) {
if (mView.getParent() != null) {
mWindowManager.removeView(mView);
}
try {
mWindowManager.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
return;
}
if (callback != null) {
try {
callback.onToastShown();
} catch (RemoteException e) {
Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
}
}
}
/**
* Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,
* int, int, int, float, float, ITransientNotificationCallback)}.
*
* <p>This method has to be called on the same thread on which {@link #show(View, IBinder,
* IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.
*/
public void hide(@Nullable ITransientNotificationCallback callback) {
if (mView.getParent() != null) {
mWindowManager.removeViewImmediate(mView);
}
try {
mNotificationManager.finishToken(mPackageName, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
}
if (callback != null) {
try {
callback.onToastHidden();
} catch (RemoteException e) {
Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
}
}
}
}
- ToastPresenter.java中的mView其实就是Toast.java中的mNextView,也就是用户自定义的View
- 可以看出Toast的显示和隐藏,本质上也就是通过WindowManager的addView和removeView方法完成的
NotificationManagerService主要源码
public class NotificationManagerService extends SystemService {
final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();//存放所有待显示的Toast
static final int MAX_PACKAGE_NOTIFICATIONS = 25;//每个应用最多同时只能有25个Toast在队列中
final IBinder mService = new INotificationManager.Stub() {
public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
int displayId, @Nullable ITransientNotificationCallback callback) {
enqueueToast(pkg, token, text, null, duration, displayId, callback);
}
private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
@Nullable ITransientNotification callback, int duration, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
synchronized (mToastQueue) {
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, token);
// If it's already in the queue, we update it in place, we don't
// move it to the end of the queue.
if (index >= 0) {//判断Toast是否已经存在,存在则更新它的显示时长
record = mToastQueue.get(index);
record.update(duration);
} else {
// Limit the number of toasts that any given package can enqueue.
// Prevents DOS attacks and deals with leaks.
int count = 0;
final int N = mToastQueue.size();
//判断该APP是否有超过最大Toast个数限制
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;
}
}
}
Binder windowToken = new Binder();
mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId);
//将Toast封装成ToastRecord;如果是自定义View则封装成CustomToastRecord,否则TextToastRecord
record = getToastRecord(callingUid, callingPid, pkg, token, text, callback,
duration, windowToken, displayId, textCallback);
//加入队列中
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveForToastIfNeededLocked(callingPid);
}
// If it's at index 0, it's the current toast. It doesn't matter if it's
// new or just been updated, show it.
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
//如果当前队列中只有一个Toast,则立刻显示
if (index == 0) {
showNextToastLocked();
}
}
}
}
}
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);//获取队列中第一个Toast用于显示
while (record != null) {
if (record.show()) {//调用Toast显示的方法
scheduleDurationReachedLocked(record);//显示成功,则通过Handler发送延迟消息去隐藏Toast
return;
}
int index = mToastQueue.indexOf(record);
if (index >= 0) {//显示失败则移除队列
mToastQueue.remove(index);
}
//获取下一个Toast
record = (mToastQueue.size() > 0) ? mToastQueue.get(0) : null;
}
}
}
主要流程: