滥用Static封装Dialog导致某些情况下Dialog show失败问题

由于静态的字段或方法在内存中只存在一份,所以利用这个特性可以做一些对象内存的优化,典型的就是单例的使用。然而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,这些都可以在源码中找到答案这里就不在扯了,有兴趣的小伙伴可以自行探讨。

猜你喜欢

转载自blog.csdn.net/qq_38350635/article/details/109209601