IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Toast源码分析(Android 11) -> 正文阅读

[移动开发]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,全部都由客户端处理显示/隐藏

源码分析

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;
        }
    }
	
}

主要流程:

Created with Rapha?l 2.3.0 enqueueToast 已经存在? 更新显示时长 数量超过限制? 结束 自定义VIew? 封装成CustomToastRecord 加入队列 唯一Toast? 显示第一个Toast 显示成功? 延迟隐藏 移除队列 封装成TextToastRecord yes no yes no yes no yes no yes no
  • NotificationManagerService通过ArrayList<ToastRecord>来保存所有待显示的Toast
  • 显示完之后会通过Handler发送延迟消息去隐藏Toast,延迟的时间就是Toast中的mDuration,这个值只能是Toast.LENGTH_LONG或者Toast.LENGTH_SHORT之一
  • 如果是自定义View的Toast,则会封装成CustomToastRecord对象,否则就会封装成TextToastRecord;这两个类都继承自ToastRecord
public abstract class ToastRecord {
	public abstract boolean show();
	public abstract void hide();
}

public class TextToastRecord extends ToastRecord {
	private final StatusBarManagerInternal mStatusBar;
	@Override
    public boolean show() {
        mStatusBar.showToast(uid, pkg, token, text, windowToken, getDuration(), mCallback);
        return true;
    }

    @Override
    public void hide() {
        mStatusBar.hideToast(pkg, token);
    }
}
public class CustomToastRecord extends ToastRecord {
	public final ITransientNotification callback;
	@Override
    public boolean show() {
        try {
            callback.show(windowToken);
            return true;
        } catch (RemoteException e) {

            return false;
        }
    }

    @Override
    public void hide() {
        try {
            callback.hide();
        } catch (RemoteException e) {
        }
    }
}
  • TextToastRecord:show/hide是通过StatusBarManager实现
  • CustomToastRecord:show/hide是通过ITransientNotification实现,通过前面Toast源码知道,Toast里的NT类就是实现的该接口,也就是回调客户端里Toast的NT类中的show/hide方法

小结:

  • Toast的显示和隐藏归根结底都是通过WindowManager的addView/removeView方法实现的
  • 如果是自定义View的Toast,则由客户端通过WindowManager实现,否则就是通过StatusBarManager实现
  • 不管是自定义View,还是默认的Toast,都需要通过NotificationManagerService来统一管理所有的Toast的显示和延迟隐藏
  • 如果需要使用自定义View的Toast,建议尽快使用Snackbar代替
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 11:46:00  更:2021-07-29 11:46:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 13:55:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码