由于靜態的欄位或方法在記憶體中只存在一份,所以利用這個特性可以做一些物件記憶體的優化,典型的就是單例的使用,然而static特性雖好,如果對全域的知識點掌握不夠充分,還是會出現一些不易發現的bug的,最近使用公司專案中Dialog封裝庫就采坑了,,,
一、Dialog庫以及引起的鍋
1、DialogUtils
如下是一個簡化版的封裝庫,但是前同事的這鍋已經基本模擬出來了,
public class DialogUtils {
private static final String TAG = DialogUtils.class.getSimpleName();
private static AlertDialog sslErrorDialog = null;
private static AlertDialog.Builder dialogBuilder(Activity context, String title, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
if (!TextUtils.isEmpty(message)) {
builder.setMessage(message);
}
if (!TextUtils.isEmpty(title)) {
builder.setTitle(title);
}
builder.setCancelable(false);
return builder;
}
/**
* SslError 彈窗
* @param context activity
* @param message title
*/
public static void showSSLErrorDialog(final Activity context, String message) {
if (context == null || context.isFinishing()) {
return;
}
if (sslErrorDialog == null) { // if 陳述句導致,潛在bug,
AlertDialog.Builder builder = dialogBuilder(context, null, message);
builder.setNegativeButton("back",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (sslErrorDialog != null) {
sslErrorDialog.dismiss();
sslErrorDialog = null;
}
}
});
builder.setPositiveButton("sure",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (sslErrorDialog != null) {
sslErrorDialog.dismiss();
sslErrorDialog = null;
}
}
});
sslErrorDialog = builder.create();
}
sslErrorDialog.show();
Button button = sslErrorDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
button.requestFocus();
}
}
2、bug的觸發

如上圖有個簡單的App A ,activity 為默認啟動模式,在activity的onCreate中通過上述工具庫呼叫dialog,這時進行如下步驟:
1、點擊桌面app A icon 進入app A
2、嗯home鍵->點擊 app iconB->點擊B內按鈕
這時你會發現1會彈出dialog2不會彈出dialog,所以bug就出來了,默認情況下2也需要彈窗的,
二、問題排查
熟悉Android Dialog 的同學都知道AlertDialog 是基于Build模式封裝的Dialog類,AlertDialog.Builder類的作用就是創建AlertDialog 物件,為AlertDialog物件提供一些“原料”,這些原料中有一個最重要的欄位Context,這里就是引起上述bug的關鍵所在,
1、大話Dialog與Activity的關系
(1)說到Dialog 與Activity 的關系這里就不得不扯一下安卓的Window的作用
其實我們在手機上能夠看到的界面都是View,View最終被添加到Window顯示出來我們才能看到的,這時有人可能會問不對啊,我們看到的界面不是Activity嗎?這時我只能說你了解的知識還不夠全面(啊哈哈裝逼了輕拍,,,)其實Activity 內部的setContentView最侄訓是被加載到Window上的,
(2)Window 的分類
- 應用Window(對應activity)
- 子Window(對應dialog,依附于activity)
- 系統window(對應Toast等)
其實Window就是View的載體:
如某一個Activity頁面就是一個Window表單(activity作為媒介吧View加載到對應的Window上),
在比如我們通過WindowManager可以像Window上添加VIew,
(3)子window被add到window上的限制
dialog最終是被加載到window上的,但是被加載的程序需要activity的token,也就是當你在某個activity上show dialog時傳遞這個activity實體即可,
(4)原始碼略讀

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
...
if (parentWindow != null) {
// 核心就在此處,具體實作在Window類中adjustLayoutParamsForSubWindow方法
// 內部處理了三種win型別,
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
....
....
}
這里我們只需知道普通的dialog是需要依附于activity而存在的,也就是需要activity的token,所以你在傳遞引數時要傳activity的context即可,在哪個activity彈窗,就傳哪個activity的實體即可,
2、回顧問題
分析DialogUtils 工具類結合我們的實踐步驟我們可以知道,當第二次進入activity時又創建了新的activity實體,雖然showSSLErrorDialog(final Activity context, String message)傳遞了第二次進入的實體,但是方法內部的sslErrorDialog !=null直接把第二次傳入的context實體掐死了,AlertDialog.Build 沒有重新構建AlertDialog,這時context實體還是第一次傳入的,所以就算彈窗也是彈在App A的activity上,
三、栗子驗證
接下來以一個小栗子驗證下 指定activity上彈窗
public class MyApplication extends Application {
public static List<Activity>mList = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
mList.add(activity);
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
});
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startActivity(new Intent(this,Main2Activity.class));
}
}
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Log.d("Main2Activity",""+this);
Log.d("MainActivity",""+MyApplication.mList.get(0));
Log.d("Main2Activity",""+MyApplication.mList.get(1));
DialogUtils.showSSLErrorDialog(MyApplication.mList.get(0),"簡單的dialog模擬");// 此時dialog會彈在MainActivity上
}
}
觀察發現dialog會彈在MainActivity上
三、總結
至此采坑完畢,,,其實上述強調過普通dialog,其實就是依附于activity的dialog,其實我們還可以通過api設定dialog所屬Window的型別讓其不依附activity,這些都可以在原始碼中找到答案這里就不在扯了,有興趣的小伙伴可以自行探討,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/194886.html
標籤:其他
上一篇:Error:Unable to resolve dependency for ‘:@debug/compileClasspath,也可能是代理問題哦
