在targetSdkVersion为30
的情况下,在Android 11
的小米10
的手机上运行,调用ToastUtil
的时候闪退报错:
null cannot be cast to non-null type android.widget.LinearLayout
为什么说的这么详细呢,因为这些条件都是必须的:
同样的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 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>
根布局LinearLayout
和TextView
显示文本。
所以才有了前面报错的这行代码:
val linearLayout = toast!!.view as LinearLayout
现在看来其实是没有错的,事实上运行在Android11
以下也确实没问题。
setView
、getView
也是没问题的
/**
* 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
之后,源码是有改动的
还是直接看重点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
代码还是一样的。
而且setView
、getView
也弃用的
/**
* 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
大致思路:
等有空了再来补一下这个。。
《Android 11 开发者手册》
写作不易,如果对你有用,点个赞呗 ^ _ ^