起源
在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>
根布局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之后
重點來了,在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
* <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 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 開發者手冊》
最后
寫作不易,如果對你有用,點個贊唄 ^ _ ^
CSDN認證博客專家
Android Jetpack
Flutter
小程式
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/253131.html
標籤:其他
