写点什么

Android 11 适配指南之 Toast 解析

作者:yechaoa
  • 2022 年 6 月 23 日
  • 本文字数:4019 字

    阅读完需:约 13 分钟

起源

targetSdkVersion为30的情况下,在Android 11小米10手机上运行,调用ToastUtil的时候闪退报错:


null cannot be cast to non-null type android.widget.LinearLayout
复制代码


且看已知条件:


  • targetSdkVersion 30

  • Android 11

  • 小米 10


文末附 Android 11 适配手册

定位问题

ok,遇到问题,迅速定位。我在原有的Toast调用上重新封装了一下,即ToastUtil


所以很快就定位到问题所在了


    private fun createToast(msg: String) {        if (toast == null) {            toast = Toast.makeText(YUtils.getApp().applicationContext, msg, Toast.LENGTH_SHORT)        } else {            toast!!.setText(msg)        }        val linearLayout = toast!!.view as LinearLayout        val messageTextView = linearLayout.getChildAt(0) as TextView        messageTextView.textSize = 15f        toast!!.show()    }
复制代码


没错,就是这句进行了转换:


val linearLayout = toast!!.view as LinearLayout
复制代码


代码也比较简单,拿到 view 之后只是设置了一下字体大小。


为什么这么写呢,且看接下来源码分析(非常简单)。

源码解析

我们一般的调用是这么写的:


Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
复制代码


一行代码,也很容易能找到重点——makeText,没错,接下来从这里开始分析

compileSdkVersion 30 之前

compileSdkVersion 28为例,makeText源码:


    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,            @NonNull CharSequence text, @Duration int duration) {        Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text);
result.mNextView = v; result.mDuration = duration;
return result; }
复制代码


这几行的代码重点在哪呢,在这:


View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
复制代码


引用了一个布局来显示信息


这个 layout 也非常的简单:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:background="?android:attr/toastFrameBackground">
<TextView android:id="@android:id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginHorizontal="24dp" android:layout_marginVertical="15dp" android:layout_gravity="center_horizontal" android:textAppearance="@style/TextAppearance.Toast" android:textColor="@color/primary_text_default_materiaal_light"/>
</LinearLayout>
复制代码


根布局LinearLayoutTextView显示文本。


所以才有了前面报错的这行代码:


val linearLayout = toast!!.view as LinearLayout
复制代码


现在看来其实是没有错的,事实上运行在Android11以下也确实没问题。


setViewgetView也是没问题的


    /**     * Set the view to show.     * @see #getView     */    public void setView(View view) {        mNextView = view;    }
/** * Return the view. * @see #setView */ public View getView() { return mNextView; }
复制代码


author:yechaoa

compileSdkVersion 30 之后

重点来了,在compileSdkVersion 30之后,源码是有改动的


还是直接看重点makeText


    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; } }
复制代码


嗯?view 的获取方式变了,原来是inflate的方式,现在是


View v = ToastPresenter.getTextToastView(context, text);
复制代码


ok,继续看ToastPresenter.getTextToastView


public class ToastPresenter {    ...
@VisibleForTesting public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;
/** * Returns the default text toast view for message {@code text}. */ public static View getTextToastView(Context context, CharSequence text) { View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT, null); TextView textView = view.findViewById(com.android.internal.R.id.message); textView.setText(text); return view; } }
复制代码


到这里是不是有点熟悉了,没错,跟compileSdkVersion 28中的源码差不多,但是 layout 变成常量了,且有@VisibleForTesting注解,不过xml代码还是一样的。


而且setViewgetView也弃用的


    /**     * Set the view to show.     *     * @see #getView     * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the     *      {@link #makeText(Context, CharSequence, int)} method, or use a     *      <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>     *      when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps     *      targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background     *      will not have custom toast views displayed.     */    @Deprecated    public void setView(View view) {        mNextView = view;    }
/** * Return the view. * * <p>Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)} * with a non-{@code null} view will return {@code null} here. * * <p>Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context, * CharSequence, int)} or its variants will also return {@code null} here unless they had called * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the * toast is shown or hidden, use {@link #addCallback(Callback)}. * * @see #setView * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the * {@link #makeText(Context, CharSequence, int)} method, or use a * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a> * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background * will not have custom toast views displayed. */ @Deprecated @Nullable public View getView() { return mNextView; }
复制代码


直接来看注释的重点:


@deprecated Custom toast views are deprecated. Apps can create a standard text toast with the{@link #makeText(Context, CharSequence, int)} method, or use a <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a>when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, appstargeting API level {@link Build.VERSION_CODES#R} or higher that are in the backgroundwill not have custom toast views displayed.


大意:自定义 toast view 已经弃用,你可以创建一个标准的toast,或者用Snackbar。从 Android R开始,将不再显示自定位 toast view。


Android R 也就是 Android11,具体的版本对应关系查看


这里有同学可能会有一些想法,既然getView弃用了,那我可不可以像系统一样通过 ToastPresenter.getTextToastView 来获取呢,很遗憾,是不行的,ToastPresenter@hide。。

适配方案

综上所诉,适配方案也了然于心了。

方案一

使用标准的 toast


Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
复制代码

方案二

使用 Snackbar


Snackbar 的使用跟 Toast 差不多,更多查看这个


Snackbar.make(view, "已加入行程", Snackbar.LENGTH_SHORT).show()
复制代码

方案三

不使用系统的 toast,但可以借鉴来写一个自定义 view


大致思路:


  • 初始化引用自定义布局

  • 编写一些公开的 set、get 属性

  • 加上进入进出动画

  • 开始/结束显示倒计时


等有空了再来补一下这个。。

Android 11 开发手册

《Android 11 开发者手册》

最后

写作不易,如果对你有用,点个赞呗 ^ _ ^

发布于: 刚刚阅读数: 3
用户头像

yechaoa

关注

优质作者 2018.10.23 加入

知名互联网大厂技术专家,多平台博客专家、优秀博主、人气作者,博客风格深入浅出,专注于Android领域,同时探索于大前端方向,持续研究并落地前端、小程序、Flutter、Kotlin等相关热门技术

评论

发布
暂无评论
Android 11适配指南之Toast解析_android_yechaoa_InfoQ写作社区