【问题】

发现app出现crash,具体看到堆栈:

android.app.RemoteServiceException: can't deliver broadcast
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2047)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:236)
	at android.app.ActivityThread.main(ActivityThread.java:7879)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)

断点跟代码,发现历史代码有在两个Activity间使用广播传递压缩文件。好吧,这种操作crash一点都不冤,广播就不是用来干这个事儿的。

【问题原因】

使用broadcast传递压缩图片,在压缩图片稍微大一点就会引起系统跑异常,导致应用crash(原理参见)。

【解决方案】
  • 全局变量

    • 将压缩后的图片文件放在一个全局变量中,使用的Activity通过这个全局变量取出,再进行后续业务操作。
    • 这种方案最为直接暴力,但是存在问题就是这种大内存没有非常合适的时机进行清缓存动作,导致应用可能长时间处在高内存的状态运行。
  • SharedPreference

    • 将压缩后的图片序列化后存储到本地的SharedPreference中,使用的Activity读取SharedPreference后反序列化获得该对象,进行后续业务操作。
    • 该方案解决了一直缓存大对象的问题,但这种io操作对于性能有所损失。
  • Binder

    • Binder是谷歌为了跨进程通信而设计的IPC组件,肩负了Android系统大部分通信任务,在跨进程通信中请注意1MB的对象大小要求。由于本问题出现在同一个APP的两个Activity中,属于同进程通信,共享内存块,binder通信没有了对象大小的限制。所以可以完美解决本场景。具体实现如下。
    • 创建一个Aidl
    interface IPic {
    	String getPic();
    }
    
    • 在压缩图片端填入图片对象
    public void sendBroadcas(final String pic) {
        Bundle bundle = new Bundle();
        bundle.putBinder("picture", new IPic.Stub() {
            @Override
            public String getPic() throws RemoteException {
                return pic;
            }
        });
        Intent intent = new Intent("com.android.pic");
        intent.putExtras(bundle);
        sendBroadcast(intent);
    }
    
    • 在业务处理端获取图片对象
    @Override
    public void onReceive(Context context, Intent intent) {
        if ("com.android.pic".equals(intent.getAction())) {
            Bundle bundle = intent.getExtras();
            if (bundle == null) {
                return;
            }
            Object object = bundle.get("picture");
            if (object instanceof IBinder) {
                IPic iPic = IPic.Stub.asInterface((IBinder) object);
                try {
                    String pic = iPic.getPic();
                    // 后续业务
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 验证是同进程的Binder是否没有1MB限制,简单的进行了一下测试。
    // 10MB createSpecifiedSizeData(10*1024*1024)
    private static String createSpecifiedSizeData(int size) {
        StringBuilder sb = new StringBuilder(size);
        for (int i = 0; i < size; i++) {
            sb.append('0');
        }
        return sb.toString();
    }
    
【参考资料】

Android进程间通信之binder - 可能导致的异常

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐