SystemUI_Toast

Toast类用于在屏幕中显示一个消息提示框,该消息提示没哟任何控制按钮,并且不会获得焦点经过一段时间后会自动消失。通常用于显示一些快速提示信息,应用范围非常广泛;

Toast 应用

Toast创建

Toast的创建有两种方式:

  • 使用构造方式进行创建;
ini 复制代码
Toast toast = new Toast(context);
toast.setDuration(Toast.LENGTH_SHORT);//设置持续时间
toast.setGravity(Gravity.CENTER,0, 0);//设置对齐方式
LinearLayout layout = new LinearLayout(context);//创建一个线性布局管理器
TextView tv = new TextView(context);
tv.setText("我是通过构造方法创建的消息提示框");
layout.addView(tv);
toast.setView(layout);
toast.show();

这种方式使用了自定义View,通过setView方法传入这个自定义View,最终也是通过show方法显示;

  • 调用Toast提供的makeText()方法创建;
ini 复制代码
Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT).show();

一般根据使用场景的不同来确定使用哪种方式,例如:

  • 简单的信息提示,一般使用makeText的方式;
  • 复杂场景的信息提示,如需要进行图片的显示,则可以使用构造方法的方式提示;

Toast 构造方法

ini 复制代码
public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    mToken = new Binder();
    looper = getLooper(looper);
    mHandler = new Handler(looper);
    mCallbacks = new ArrayList<>();
    mTN = new TN(context, context.getPackageName(), mToken,
                 mCallbacks, 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);
}
  • mContext:Context实例;
  • mToken:构造binder对象,后面和NotificationManagerService通信使用;
  • looper:当前Toast绑定的线程looper;
  • mTN:Binder.Stub类型对象,作为binder的client端,其接受跨进程传递过来的信息时是在单独的binder线程中处理;
  • mTN.mY:纵坐标偏移量,简单来说就是控制Toast在屏幕中显示位置是靠上一点还是靠下一点;
  • mTN.mGravity:控制Toast的显示位置,一般是居中、靠下两种;

Toast 原理

基于两种使用方式的不同,其工作原理也是不同的;

  • 使用构造方法,创建自定义View:是由APP自身处理的;
  • makeText方法:通过NotificationManagerService显示;

makeText

less 复制代码
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
                             @NonNull CharSequence text, @Duration int duration) {}
  • context:绑定的上下文对象;

  • looper:绑定线程的Looper,可以为空,为空时则默认使用当前线程的looper;

  • text:显示内容;

  • duration:持续时间

    • Toast.LENGTH_LONG:显示时间较长,为3.5s,其3500ms的值定义在NotificationManagerService.LONG_DELAY;
    • Toast.LENGTH_SHORT:显示时间较短,为2s,其2000ms的值定义在NotificationManagerService.SHORT_DELAY;

    但是真实显示的时间,却不是3.5s和2s,实际显示时间会比这两个时间更长一些;

less 复制代码
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;
    }
}
  • CHANGE_TEXT_TOASTS_IN_THE_SYSTEM:判断该文本消息框是否由SystemUI呈现;

    CHANGE_TEXT_TOASTS_IN_THE_SYSTEM对于Android R(API级别30)或更高版本为目标平台的应用处于启用状态;

默认为true,即支持使用SystemUI定义的ToastUI组件进行显示;

原生Toast和自定义View的Toast的唯一区别就是原生Toast对象中的mNextView对象为null

show

java 复制代码
public void show() {
    if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
        // 校验Toast 配置是否合理
        checkState(mNextView != null || mText != null, "You must either set a text or a view");
    } else {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
    }
​
    // 获取NotificationManagerService
    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    // 获取Display Id
    final int displayId = mContext.getDisplayId();
​
    try {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            if (mNextView != null) {
                // It's a custom toast -- 定制化Toast
                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
            } else {
                // It's a text toast -- SystemUi 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
    }
}

至此,该Text就传入到了NotificationManagerService中了;

NotificationManagerService

less 复制代码
@Override
public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
                             int displayId, @Nullable ITransientNotificationCallback callback) {
    enqueueToast(pkg, token, text, null, duration, displayId, callback);
}
  • pkg:包名;
  • token:APP端binder;
  • text:需要提示的信息内容;
  • duration:持续时间;
  • displayId:标记唯一显示区域的ID,对应的实体类是DisplayContent;
  • callback:跨进程的callback对象,自定义View的Toast有值,默认的Toast中的callback = null;
生产者

每个Toast都对应一个binder对象,所以如果toast是复用的,则短时间内多次调用show放,也只会对应同一个Record对象,所以也只会显示一次;

如果index<0,则说明mToastQueue不存在该toast所对应的binder,则进入插入的逻辑;

通过getToastRecord方法生成一个ToastRecord对象加入到集合最尾端,并且通过keepProcessAliveForToastIfNeededLocked方法保证弹Toast的进程不被杀死,如果当前只有一条记录的话,则直接调用showNextToastLocke方法进行显示;

ToastRecord其实是一个抽象方法,它有两个实现类,TextToastRecord和CustomToastRecord。getToastRecord方法中会根据callback是否为空来进行对应的生成,其中callback==null时生成的是TextToastRecord类型对象;

消费者

这里涉及到生产者消费者模式了,既然APP端通过binder方法向mToastQueue集合中插入数据,那么就一定有消费者来消费。而这个消费者就是showNextToastLocked方法,所以永远只会有一个线程在执行showNextToastLocked方法;

  1. 按照先后顺序便利mToastQueue集合,取出record对象。
  2. 通过showToast方法尝试显示record对象。如果成功,则执行scheduleDurationReachedLocked方法。
  3. 如果失败,则从集合中删除。就是说如果Toast显示时如果失败了也不会再次尝试;

我们这边分析TextToastRecord的show逻辑;

CommandQueue

mStatusBar是一个注册的service:

arduino 复制代码
private final StatusBarManagerInternal mStatusBar;

其实现类在StatusBarManagerService.java中:

java 复制代码
 private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {}

mBar其实是一个binder的引用,为IStatusBar,其server的实现在SystemUI进程中,实现类是CommandQueue;

arduino 复制代码
private volatile IStatusBar mBar;
scala 复制代码
public class CommandQueue extends IStatusBar.Stub implements CallbackController<Callbacks>,
             DisplayManager.DisplayListener {}

而在SystemUI模块中,很多SystemUI组件都和CommandQueue有关联,因为在CommandQueue中定义了一个Callback接口,这些接口用于对外进行交互;

scala 复制代码
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {}
public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {}
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
        AuthDialogCallback {}
public class PipUI extends SystemUI implements CommandQueue.Callbacks {}
public class PowerUI extends SystemUI implements CommandQueue.Callbacks {}
public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks,
        StatusBarStateController.StateListener {}
........................

在SystemUI中,在StatusBar的初始化过程中,将CommandQueue实例注册到了IStatusBarService中:

java 复制代码
protected final CommandQueue mCommandQueue;
protected IStatusBarService mBarService;
​
@Override
public void start() {
    ........................
    mBarService = IStatusBarService.Stub.asInterface(
        ServiceManager.getService(Context.STATUS_BAR_SERVICE));
​
    ........................
​
    // Connect in to the status bar manager service
    mCommandQueue.addCallback(this);
​
    RegisterStatusBarResult result = null;
    try {
        result = mBarService.registerStatusBar(mCommandQueue);
    } catch (RemoteException ex) {
        ex.rethrowFromSystemServer();
    }
​
    createAndAddWindows(result);
    ........................
}

在StatusBar中,将创建好的CommandQueue对象注册到了IStatusBarService中,而IStatusBarService aidl的实现为:

scala 复制代码
public class StatusBarManagerService extends IStatusBarService.Stub implements DisplayListener {}

StatusBarManagerService是状态栏导航栏向外界提供服务的前端接口,运行于system_server进程中;

CustomToastRecord

CustomToastRecord区别于TextToastRecord在于show的逻辑;

  • TextToastRecord:

    • Android P中,Text和Custom类型的Toast都是在Toast的TN类中进行show,没有明确的通过代码区分TextToastRecord和CustomToastRecord;
    • Android R中,对Text和Custom进行了代码区分,定义了CustomToastRecord和TextToastRecord,其中Text类型的Toast的show移动到了SystemUI模块中进行显示,SystemUI组件中定义了一个ToastUI组件;
  • CustomToastRecord:

    CustomToastRecord的显示流程和Android P基线的流程相同;

CustomToastRecord是直接使用了Toast类的内容类TN进行show,而TN的show的本质还是调用了ToastPresenter实现的;

相关推荐
潜洋5 分钟前
Spring Boot 教程之六:Spring Boot - 架构
java·spring boot·后端·架构
车联网安全杂货铺9 分钟前
新能源整车厂车联网安全:架构、流程与融合
安全·网络安全·架构·车载系统·系统安全
明天再做行么1 小时前
PHP8解析php技术10个新特性
android·php
Ting丶丶1 小时前
安卓应用安装过程学习
android·学习·安全·web安全·网络安全
kingdawin2 小时前
Android系统开发-判断相机是否在使用
android
恋猫de小郭3 小时前
IntelliJ IDEA 2024.3 K2 模式已发布稳定版,Android Studio Meerkat 预览也正式支持
android·android studio
找藉口是失败者的习惯7 小时前
Jetpack Compose 如何布局解析
android·xml·ui
Estar.Lee12 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh12 小时前
uiautomator案例
android
工业甲酰苯胺13 小时前
MySQL 主从复制之多线程复制
android·mysql·adb