在Android开发中,注册广播是一项常用的功能,通过广播可以进行数据的传递和信息的即使反馈和处理。广播的注册有动态注册和静态注册两种方式。广播作为Android中的一种资源,应该遵守Android对资源处理的规则,资源对于Android来讲是重要的功能实现传递,也是Android发生异常的重要缘由。针对广播,为了防止内存泄漏和准确地注册和注销,在注册和注销广播的时候,需要选择恰当的时机,这样既能准确地实现需求,又能防止资源的不回收导致对其他的资源的持有而引起内存泄漏。下面,我将以此抛砖引玉并记录在开发过程中遇到的问题作一些浅略的分析。

    广播注册和注销的时机

       主要分析动态注册广播的情形,因为静态地广播注册不存在注销的条件,静态的广播是和整个应用程序联系在一起的,只有当所依附的应用程序所在的进程死掉,静态广播才有可能被注销。动态注册广播需要依附一个Context,因为资源的使用都应该具备适当的环境。广播的注册和注销很简单。

 注册广播:需要为注册的广播匹配一个IntentFilter,然后调用registerReceiver的方法

IntentFilter intentFilter = new IntentFilter(ImsManager.ACTION_IMS_INCOMING_CALL);intentFilter.addAction(ImsManager.ACTION_IMS_SERVICE_DOWN);mContext.registerReceiver(mBroadcastReceiver, intentFilter);

   注销广播:直接调用unregisterReceiver方法

mContext.unregisterReceiver(mBroadcastReceiver);

   为了合理地使用资源,应该恰当选择注册和注销时机,通常有两种:

  1. 在onCreate中注册广播而在onDestroy中注销广播;

  2. 在onResume中注册广播而在onPause中注销广播;

  第一种方式虽然能够满足资源的使用,但是在使用上会有一些不完美,正如所知道的,当打开一个Actvitity时,这是onCreate会被调用,但是onDestroy在Activty不可见的时候并不一定被调用,只有在当前Activity结束的时候才会被调用。比如当用户点击Home键时,这时候Activity被挂起,然而并不会调用onDestory,资源得不到及时地释放,当发送的广播到达时,此时仍然会执行广播里的逻辑,会发生设计之外的现象。

  既然这样,当一个Activity被挂起的时候,onPause的方法一定会执行,那可不可以在onCreate中注册而在onPause中注销广播呢?这样貌似可以,但是真的可以吗?仔细分析一下,广播的注册和注销应该遵循成对出现的原则,有注册必须要有注销,有注销必须先要有注册。如果只有注册没有注销,由于广播对当前Context的引用和持有,即使当前依附的环境被正常的结束,依然由于被引用而无法并回收系统捕获回收,引起内存泄漏。如果当前注销的广播未被注册,则会抛出异常:

java.lang.IllegalArgumentException: Receiver not registered

  现在回想之前的假设,如果打开一个activity,activity被创建,广播被注册。设想一下这种情形,跳转到另一个页面,然后返回,在重复执行上面的操作,你会发现异常抛出了,这是因为onPause被调用了两次,同时注销广播也被执行两次。第一次广播正常被注销了,而第二次广播由于未被注册而注销抛出了异常。

   所以,通常采用第二种注册和注销时机。

  在弹窗的时候注册广播引发的问题思考 

  这是一个新的情形,之前从未有过这方面的思考,但是最近的时候确实遇到了:需要按Home键结束一个窗口显示。这个时候就进行如下的设计:

public static void registerDismissListener(Dialog dialog) {        boolean[] registered = new boolean[1];        Context context = dialog.getContext();        final BroadcastReceiver mReceiver = new BroadcastReceiver() {            @Override            public void onReceive(Context context, Intent intent) {                if (dialog != null) {                    dialog.dismiss();                }            }        };        context.registerReceiverAsUser(mReceiver, UserHandle.CURRENT,                new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null, null);        registered[0] = true;        dialog.setOnDismissListener(d -> {        if (registered[0]) {             context.unregisterReceiver(mReceiver);             registered[0] = false;            }        });    }

在窗口建立的时候注册广播,在窗口消失的时候注销广播,为了防止抛异常,设置了标志位registered[0]。

Intent.ACTION_CLOSE_SYSTEM_DIALOGS:是一个关闭系统弹窗的匹配。这种看起来是没什么异常,当时页面发生旋转切换时,弹窗消失,程序崩溃了,怎么了?通过Log看到,竟然是熟悉的异常:

java.lang.IllegalArgumentException: Receiver not registered: [email protected]01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:1203)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1450)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:645)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:645)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at com.android.systemui.statusbar.phone.SystemUIDialog.lambda$-com_android_systemui_statusbar_phone_SystemUIDialog_3951(SystemUIDialog.java:113)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at com.android.systemui.statusbar.phone.-$Lambda$XV-hQ-G8iNhGv40C_xJ7BBI6VyQ.$m$0(Unknown Source:12)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at com.android.systemui.statusbar.phone.-$Lambda$XV-hQ-G8iNhGv40C_xJ7BBI6VyQ.onDismiss(Unknown Source:0)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.app.Dialog$ListenersHandler.handleMessage(Dialog.java:1361)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)01-03 00:14:58.130  8075  8075 E AndroidRuntime: at android.os.Looper.loop(Looper.java:164)

明明做了防止操作,还是出错了。

    原来是在自动旋转,横竖屏切换的时候,activity被重建了,之前的activity被销毁了,在其中注册的广播无效了,无法接受到广播。而activity虽然重建了,但是Dialog并没有重新建立,所以广播没有被注册,当Dialog消失的时候,会触发注销广播的事件,这个时候由于异常程序崩溃了。

    知道原因后,解决问题就简单了,就是为了防止activity重建。我采用的方法是在AndroidManifest.xml文件中为对应的activity添加android:configChanges="orientation|screenSize"属性,这样可以防止activity在orientation变化时进行重建。

更多相关文章

  1. Android 性能优化:使用 Lint 优化代码、去除多余资源
  2. 批量处理ios破解后的资源文件为android所用
  3. Android的asset/res资源框架结构
  4. 管理Android应用程序的资源
  5. Android的static静态变量
  6. android phoneGap 静态页面中简单的数据传递
  7. Android-Fresco系列2 加载资源

随机推荐

  1. Android(安卓)AOSP基础(四)Source Insight
  2. Android8.0 蓝牙系统
  3. android JNI层线程回调Java函数
  4. Android作为SocketServer以及手机IP问题
  5. android handler 多线程demo
  6. Android(安卓)Activity类应用技巧分享
  7. Android(安卓)来去电自动录音(一)
  8. Android图片异步加载之Android-Universal
  9. Android(安卓)资源(resource)学习小结
  10. Android(安卓)插件化 动态升级