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实现的;

相关推荐
xvch2 小时前
Kotlin 2.1.0 入门教程(二十三)泛型、泛型约束、协变、逆变、不变
android·kotlin
Twilight-pending2 小时前
DeepSeek 新注意力架构NSA
架构
ianozo2 小时前
BUU40 [安洵杯 2019]easy_serialize_php
android·开发语言·php
abs6253 小时前
uniapp使用uts插件启动原生安卓Service
android·uni-app·uniapp uts插件·uniapp 安卓服务
Evaporator Core3 小时前
MATLAB在投资组合优化中的应用:从基础理论到实践
android
Neo Evolution4 小时前
Flutter与移动开发的未来:谷歌的技术愿景与实现路径
android·人工智能·学习·ios·前端框架·webview·着色器
coooliang4 小时前
Flutter项目中设置安卓启动页
android·flutter
xianrenli384 小时前
android 使用 zstd算法压缩文件
android
九思x4 小时前
Android Studio安装配置及运行
android·ide·android studio
一位卑微的码农10 小时前
深入解析Spring Cloud Config:构建高可用分布式配置中心
分布式·spring cloud·微服务·架构