Dialog大面积崩溃现象-总结一番Android开发中dialog使用规范详解,夯实基础

Dialog大面积崩溃现象-总结一番Android开发中dialog使用规范详解,夯实基础

声明下:app日活至少20W左右,dialog才可能会引发起java.lang.IllegalArgumentException: View not attached to window manager的崩溃,

很多人都不是重视dialog的使用,导致app日活多了后,引起线上大面积的崩溃。我们要注重基础知识的积累,规范使用dialog,因此写了这篇文章,dialog使用的详解

一、dialog造成崩溃的现象java.lang.IllegalArgumentException: View not attached to window manager

运行的时候,有时会出现界面弹框消失的时候,程序崩溃现象


java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:389)
at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:318)
at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:84)
at android.app.Dialog.dismissDialog(Dialog.java:331)
at android.app.Dialog.dismiss(Dialog.java:314)
at com.huawei.mw.plugin.app.activity.AppManagerActivity$1.handleMessage(AppManagerActivity.java:171)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5356)

我们来看源码

WindowManagerGlobal类中

int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }


原因是当Dialog调用dismiss方法的时候,WindowManager检查发现Dialog所属的Activity因为某种原因已经被杀掉,在依赖的activity上removeView的时候就会报上面的异常

解决方法

1、当activity关闭的时候,在onDestroy销毁dialog


 @Override
  protected void onDestroy() {
    super.onDestroy();
if (null != dialogOne && dialogOne.isShowing()) {
        dialogOne.dismiss();
      }
  if (null != dialogTwo && dialogTwo.isShowing()) {
        dialogTwo.dismiss();
      }
   if (null != dialogThree && dialogThree.isShowing()) {
        dialogThree.dismiss();
      }
  }

2、在activity中创建一个类自动监听dialog的生命周期,当activity销毁的时候dialog都全部销毁


public class AlertUtils implements LifecycleObserver {
  Context context;
  //可以命名自己的
  WeakReference<Dialog> oneDialog;
  public AlertUtils(Context context) {
    this.context = context;
    if (null != context && context instanceof FragmentActivity) {
      ((FragmentActivity) context).getLifecycle().addObserver(this);
    }
  }
    public void setoneDialog(Dialog oneDialog) {
    this.oneDialog = new WeakReference<>(oneDialog);
  }
   /***
   * 销毁的时候 检测dialog有没有销毁
   */
  @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
  void destroy() {
    destoryDialog(oneDialog);
  }
   /***
   * 销毁的
   */
  private void destoryDialog(WeakReference<Dialog> dialogWeakReference){
    try {
      if (dialogWeakReference != null && dialogWeakReference.get() != null){
        if (dialogWeakReference.get().isShowing()){
          dialogWeakReference.get().dismiss();
          dialogWeakReference.clear();
        }
      }
    }catch (Exception e){
    }
  }
}

在当前的activity里创建AlertUtils类,把用到的dialog放到AlertUtils里进行自动监听生命周期

二、弹框使用详解

1、系统自带弹框,弹框样式跟随系统,不同系统不同的样式

①AlertDialog-单选、多选、列表、确认单个按钮或者多个按钮等

代码示例

图片
图片
图片

 AlertDialog.Builder builder  builder = new AlertDialog.Builder(this).setIcon(R.mipmap.ic_launcher).setTitle("最普通dialog")
                .setMessage("我是最简单的dialog").setPositiveButton("确定(积极)", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        //ToDo: 你想做的事情
                        Toast.makeText(MainActivity.this, "确定按钮", Toast.LENGTH_LONG).show();
                    }
                }).setNegativeButton("取消(消极)", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        //ToDo: 你想做的事情
                        Toast.makeText(MainActivity.this, "关闭按钮", Toast.LENGTH_LONG).show();
                        dialogInterface.dismiss();
                    }
                });
        builder.create().show();

②AlertDialog、ProgressDialog(进度的 dialog)

图片
图片
/**
     * 带有进度的 dialog
     */
    private void showLoading() {
        final int MAX_VALUE = 100;
      ProgressDialog  progressDialog = new ProgressDialog(this);
        progressDialog.setProgress(0);
        progressDialog.setTitle("带有加载进度dialog");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMax(MAX_VALUE);
        progressDialog.show();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int progress = 0;
                while (progress < MAX_VALUE) {
                    try {
                        Thread.sleep(100);
                        progress++;
                        progressDialog.setProgress(progress);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //加载完毕自动关闭dialog
                progressDialog.cancel();
            }
        }).start();
    }

2、自定义dialog

   直接创建对象dialog设置布局进行显示

  public static Dialog showCommDialog(Context context) {
     Dialog dialog = new Dialog(context, R.style.dialogStyle);
    LayoutInflater inflater = LayoutInflater.from(context);
    View view = inflater.inflate(R.layout.test, null);
    TextView btn = view.findViewById(R.id.btn);
    btn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      }
    });
//设置dialog的属性,点击边上不关闭dialog
    dialog.setCanceledOnTouchOutside(false);
//设dialog的布局
    dialog.setContentView(view);
//弹出dialog
    dialog.show();
//设置dialog的属性值
    DialogUtil.setDialogLayoutParams(context, dialog);
    return dialog;
  }

    <style name="dialogStyle" parent="@android:style/Theme.Dialog">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@color/transparent</item>
        <item name="android:backgroundDimEnabled">true</item>
        <item name="android:windowIsFloating">true</item>
</style>
 public static void setDialogLayoutParams(Context context, Dialog dialog) {
        try {
      DisplayMetrics metric = new DisplayMetrics();
      WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
      wm.getDefaultDisplay().getMetrics(metric);
      Window w = dialog.getWindow();
      WindowManager.LayoutParams lp = w.getAttributes();
      int width = metric.widthPixels;
      int height = metric.heightPixels;
      float dialogWidth = 0f;
      width = (int)Math.min(width * 0.9, dialogWidth);
      lp.width = width;
      lp.gravity = Gravity.CENTER;
      lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
      dialog.onWindowAttributesChanged(lp);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

3、DialogFragment

  ① DialogFragment中重写onCreateView方法,该方法创建的View将会作为Dialog的内容布局


@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Logger.d(TAG, "onCreateView");
        TextView textView = new TextView(requireActivity());
        textView.setText("通过onCreateView使用DialogFragment");
        textView.setGravity(Gravity.CENTER);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 28);
        return textView;
    }

   ②DialogFragment生命周期


  DialogFragment: onAttach
    DialogFragment: onCreate
    DialogFragment: onCreateDialog
    DialogFragment: onActivityCreated
    DialogFragment: onStart
    DialogFragment: onResume

③Dialog进行属性设置

  @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        Window window = dialog.getWindow();
        if (window == null) {
            return;
        }
        WindowManager.LayoutParams attributes = window.getAttributes();
        //设置Dialog窗口的高度
        attributes.height = WindowManager.LayoutParams.MATCH_PARENT;
        //设置Dialog窗口的宽度
        attributes.width = WindowManager.LayoutParams.MATCH_PARENT;
        //设置Dialog的居中方向
        attributes.gravity = Gravity.CENTER;
        //设置Dialog弹出时背景的透明度
        attributes.dimAmount = 0.6f;
        //设置Dialog水平方向的间距
        attributes.horizontalMargin = 0f;
        //设置Dialog垂直方向的间距
        attributes.verticalMargin = 0f;
        //设置Dialog显示时X轴的坐标,具体屏幕X轴的偏移量
        attributes.x = 0;
        //设置Dialog显示时Y轴的坐标,距离屏幕Y轴的偏移量
        attributes.y = 0;
        //设置Dialog的透明度
        attributes.alpha = 0f;
        //设置Dialog显示和消失时的动画
        attributes.windowAnimations = 0;
        window.setAttributes(attributes);
        Logger.d(TAG, "onStart");
    }

  ④来一波dialogfragment源码分析

 public class DialogFragment extends Fragment implements OnCancelListener, OnDismissListener {
    boolean mViewDestroyed;
    boolean mDismissed;
    boolean mShownByMe;
    public DialogFragment() {
    }
    //标准的显示Fragment的方法,没什么好说的
    public void show(FragmentManager manager, String tag) {
        this.mDismissed = false;
        this.mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
    //传入一个事务管理,将fragment加入到事务管理中,并返回回退栈id
    //返回的mBackStackId将在下文中用到
    public int show(FragmentTransaction transaction, String tag) {
        this.mDismissed = false;
        this.mShownByMe = true;
        transaction.add(this, tag);
        this.mViewDestroyed = false;
        this.mBackStackId = transaction.commit();
        return this.mBackStackId;
    }
    //第一个show方法的加强版,看名字就知道,使用这个方法之后,则会立即执行fragment当中相关的方法,这个待会儿作出解释
    public void showNow(FragmentManager manager, String tag) {
        this.mDismissed = false;
        this.mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commitNow();
    }
    //Dialaog消失时的回调,setOndismissListener是在onActivityCreate中设置的,当前正是把dialog给dismiss,并没有让dialogfragment出栈
    public void dismiss() {
        this.dismissInternal(false);
    }
    //dismiss的加强版,先消失dialog,并将dialogfragment移除栈内
    public void dismissAllowingStateLoss() {
        this.dismissInternal(true);
    }
    //显示判断dialog,是否已经消失,如果已经消失,则不做任何操作
    //1.如果dialog实例不为空,先调用dialog的dismiss方法,隐藏dialog
    //2.如果先前调用的是public int show(FragmentTransaction transaction, String tag)
    //方法显示的dialogfragment,那么此时会根据之前返回的mBackStackId来将fragment移除栈内
    //3.如果不是则再启用事务将dialogfragment移除栈内,这里会根据传入的allowStateLoss来区分
    //提交事务的方法
    void dismissInternal(boolean allowStateLoss) {
        if (!this.mDismissed) {
            this.mDismissed = true;
            this.mShownByMe = false;
            if (this.mDialog != null) {
                this.mDialog.dismiss();
            }
            this.mViewDestroyed = true;
            if (this.mBackStackId >= 0) {
                this.getFragmentManager().popBackStack(this.mBackStackId, 1);
                this.mBackStackId = -1;
            } else {
                FragmentTransaction ft = this.getFragmentManager().beginTransaction();
                ft.remove(this);
                if (allowStateLoss) {
                    ft.commitAllowingStateLoss();
                } else {
                    ft.commit();
                }
            }
        }
    }
    //如果子类重写该方法,那么使用的就是你自定义的dialog
    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(this.getActivity(), this.getTheme());
    }
    //dialog设置了onDismissListener后的回调,如果dialog正常消失,次回调中的方法不会调用到
    //因为在DialogFragment的onStar方法中将mViewDestroyed变量赋值为true,dialog显示设置到调用显示出来的生命周期回调我们已经打印过了。
    public void onDismiss(DialogInterface dialog) {
        if (!this.mViewDestroyed) {
            this.dismissInternal(true);
        }
    }
    //该方法,先判断Dialog是否已经显示,然后会取onCreateView中返回的View,如果View不为空,那么该View将作为Dialog的内容布局,所以,如果你同时重写了onCreateDialog和onCreateView方法,那么会优先采用onCreateView当中的View作为内容布局,然后再作了一些监听设置
    //设置了dialog是否可点击
    //设置了dialog的消失监听onDismissListener,所以消失时会回调文中的dimiss方法
    //设置了dialog的取消监听onCancelListener,在取消时会回调文中的onCancel方法
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (this.mShowsDialog) {
            View view = this.getView();
            if (view != null) {
                if (view.getParent() != null) {
                    throw new IllegalStateException("DialogFragment can not be attached to a container view");
                }
                this.mDialog.setContentView(view);
            }
            Activity activity = this.getActivity();
            if (activity != null) {
                this.mDialog.setOwnerActivity(activity);
            }
            this.mDialog.setCancelable(this.mCancelable);
            this.mDialog.setOnCancelListener(this);
            this.mDialog.setOnDismissListener(this);
            if (savedInstanceState != null) {
                Bundle dialogState = savedInstanceState.getBundle("android:savedDialogState");
                if (dialogState != null) {
                    this.mDialog.onRestoreInstanceState(dialogState);
                }
            }
        }
    }
}

总结

  •  其所使用的context必须要用Activity的
  •  在调用dialog.show();前,一定要先判断其所依赖的Activity是否还存在
  •  关闭activity前一定要销毁当前页面的dialog,不然会报java.lang.IllegalArgumentException: View not attached to window manager
阅读原文

简介:一个有10多年经验开发的android、java、前端等语言的老程序员,在这里一起聊聊技术,一起聊聊生活、一起聊聊中年危机的生存之道,一起进步一起加油,感兴趣的欢迎订阅;不定时的更新。欢迎关注微信公众号:Android开发编程
(0)
打赏 喜欢就点个赞支持下吧 喜欢就点个赞支持下吧

声明:本文来自“Android开发编程”,分享链接:https://www.zyxiao.com/p/292191    侵权投诉

网站客服
网站客服
内容投稿 侵权处理
分享本页
返回顶部