起源

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

null cannot be cast to non-null type android.widget.LinearLayout

为什么说的这么详细呢,因为这些条件都是必须的:

  • targetSdkVersion 30
  • Android 11
  • 小米10

同样的targetSdkVersion,在Android 11的华为P30 Pro上运行确实正常的,为什么呢,根据这些年厂商不断完善自己的通知渠道来看,不光我们要适配新的android版本,厂商同样也要适配,所以只能归纳为是厂商做了一些处理。

文末附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     *      Snackbar     *      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.     *     * 

Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)} * with a non-{@code null} view will return {@code null} here. * *

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 * Snackbar * 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 Snackbar
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.

大意:
自定义toast view已经弃用,你可以创建一个标准的toast,或者用Snackbar
从AndroidR开始,将不再显示自定位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 开发者手册》

最后

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

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. android 全屏 webview 加载的h5的输入框,被键盘遮挡的解决
  3. Android——GSON解析JSON
  4. android APP隐私政策弹框的实现代码实例
  5. Android系统源码极速搜索引擎(OpenGrok)
  6. 解决 Android(安卓)模拟器 无法上网问题
  7. Android(安卓)IPC 通讯机制源码分析
  8. android service 相关问题汇总
  9. android手机打电话代码分析

随机推荐

  1. Android保活从入门到放弃:乖乖引导用户加
  2. 【移动开发】Android中不用图片资源也能
  3. Android(安卓)ROM分析(1):刷机原理及方法
  4. Apache POI库简化,仅保留hwpf部分(word文
  5. YUV 400 格式图像转换成 ARGB 格式图像中
  6. Android(安卓)Developers:高效的加载大的
  7. 【腾讯优测干货分享】鹅厂专家详解Androi
  8. Android图像处理技术(实现Android中的PS)(二
  9. Android查看调用函数名与行号等信息的日
  10. Android绘图机制与处理技巧(二)——Android