Android 源码系列之通过反射解决在HuaWei手机出现Register too many Broadcast Receivers的crash

       转载请注明出处:http://blog.csdn.net/llew2011/article/details/79054457

       Android开发适配问题一直是一个让人头疼的话题,由于国内很多厂商都有对原生Android系统做不同的定制,结果导致适配起来很麻烦。印象最深的一个适配是让Notification的背景色做到和系统通知栏背景色一致,然后就是想各种办法做适配……近来在Bugly上查看统计APP的crash日志的时候发现有一个crash日志很诡异,该crash只发生在HuaWei手机上,截取部分Crash日志如下所示:

java.lang.AssertionError:Register too many Broadcast Receivers
	android.app.LoadedApk.checkRecevierRegisteredLeakLocked(LoadedApk.java:1010)
	android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:1038)
	android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1476)
	android.app.ContextImpl.registerReceiver(ContextImpl.java:1456)
	android.app.ContextImpl.registerReceiver(ContextImpl.java:1450)
	android.content.ContextWrapper.registerReceiver(ContextWrapper.java:586)
	com.tencent.sharp.jni.TraeAudioManager$TraeAudioManagerLooper.void _post_stopService()(TraeAudioManager.java:1982)
	com.tencent.sharp.jni.TraeAudioManager$TraeAudioManagerLooper.void stopService()(TraeAudioManager.java:1628)
	com.tencent.sharp.jni.TraeAudioManager$TraeAudioManagerLooper$2.void handleMessage(android.os.Message)(TraeAudioManager.java:1695)
	android.os.Handler.dispatchMessage(Handler.java:105)
	android.os.Looper.loop(Looper.java:156)
	com.tencent.sharp.jni.TraeAudioManager$TraeAudioManagerLooper.void run()(TraeAudioManager.java:1891)
       根据日志信息看到抛出的异常为:Register too many Broadcast Receivers,翻译过来就是注册的BroadcastReceiver太多导致的。根据调用的栈信息,是TraeAudioManager类的内部类TraeAudioManager的_post_stopService()方法内注册BroadcastReceiver过多导致应用crash的。所以我们猜测该crash很有可能是在_post_stopService()方法内部注册BroadcastReceiver前没有进行反注册操作导致的。由于TraeAudioManager类是鹅厂SDK中的类,因此只能反编译查看TraeAudioManager类的实现方式,反编译后的TraeAudioManager.class主要代码如下;
public class TraeAudioManager extends BroadcastReceiver {

    // 省略部分代码
    
    class TraeAudioManagerLooper extends Thread {
        
        // 省略部分代码

        public TraeAudioManagerLooper(TraeAudioManager var2) {
            this._parent = var2;
            this.start();
            // 省略部分代码
        }

        void stopService() {
            AudioDeviceInterface.LogTraceEntry(" _enabled:" + (this._enabled?"Y":"N") + " activeMode:" + TraeAudioManager.this._activeMode);
            if(this._enabled) {

                // 省略部分代码

                this._post_stopService();

                // 省略部分代码
            }
        }

        public void run() {
            Looper.prepare();
            this.mMsgHandler = new Handler() {
                public void handleMessage(Message var1) {

                    // 省略部分代码

                    if(var1.what == '耄') {
                        TraeAudioManagerLooper.this.startService(var6);
                    } else if(!TraeAudioManagerLooper.this._enabled) {

                        Intent var7 = new Intent();
                        TraeAudioManager.this.sendResBroadcast(var7, var6, 1);
                    } else {
                        switch(var1.what) {
                        case 32773:
                            TraeAudioManagerLooper.this.stopService();
                            break;
                    }

                }
            };

            // 省略部分代码

        }

        void _post_stopService() {
            try {
                if(TraeAudioManager.this._bluetoothCheck != null) {
                    TraeAudioManager.this._bluetoothCheck.release();
                }

                TraeAudioManager.this._bluetoothCheck = null;
                if(TraeAudioManager.this._context != null) {
                    TraeAudioManager.this._context.unregisterReceiver(this._parent);// 先反注册广播接收器
                    IntentFilter var1 = new IntentFilter();
                    var1.addAction("com.tencent.sharp.ACTION_TRAEAUDIOMANAGER_REQUEST");
                    TraeAudioManager.this._context.registerReceiver(this._parent, var1);// 再注册广播接收器
                }
            } catch (Exception var2) {
                ;
            }

        }

        // 省略部分代码
    }

    // 省略部分代码
}
       从反编译后的TraeAudioManager类来看,_post_stopService()方法内部在注册BroadcastReceiver之前都有反注册BroadcastReceiver的操作,并且_post_stopService()方法又加上了try-catch操作,理论上来说通过这两层验证不应该再发生crash了,但事实真的crash了……通过在_post_stopService()方法内部整体添加try-catch的操作我们可以推断:鹅厂SDK的研发童靴也清楚该处会抛异常(在HuaWei手机上会crash),所以他们添加了try-catch试图捕获该异常。但是我们回头再仔细看一下crash日志信息,发现抛出的异常名称是AssertionError,该异常类型是Error类型,而_post_stopService()方法内部添加的try-catch()捕获的异常是Exception,清楚Java异常捕获机制的童靴应该清楚这两个异常类型根本就不是同一类型,因此catch中定义的Exception类型是不能捕获AssertionError异常的,很显然鹅厂的SDK研发童靴忽略了这一点。

       既然_post_stopService()方法内部的try-catch操作是无效的,那么我们就可以借助上篇文章Android 源码系列之<十七>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug开发的BytecodeFixer插件来对该方法做修复,也就是添加捕获Throwable所有异常的try-catch代码块。如果有小伙伴不清楚该插件的使用请阅读上篇文章。引入插件,添加配置如下所示:

apply plugin: 'com.llew.bytecode.fix'

bytecodeFixConfig {

    enable true

    logEnable = true

    keepFixedJarFile = true

    keepFixedClassFile = true

    fixConfig = [
            'com.tencent.ilivesdk.core.impl.ILVBRoom##quitIMGroup()##if (null != super.mOption && super.mOption.isIMSupport()) {com.tencent.ilivesdk.core.ILiveLog.ki(TAG, "quitIMGroup", new com.tencent.ilivesdk.core.ILiveLog.LogExts().put("isHost", isHost));if (isHost) {com.tencent.ilivesdk.ILiveSDK.getInstance().getGroupEngine().deleteGroup(getIMGroupId(), null);} else {com.tencent.ilivesdk.ILiveSDK.getInstance().getGroupEngine().quitGroup(getIMGroupId(), null);};chatRoomId = null;};##-1',
            'com.tencent.ilivesdk.adapter.avsdk_impl.AVSDKContext$AVCreateContextCallBack##onComplete(int,java.lang.String)##{}##0',
            'com.tencent.ilivesdk.adapter.avsdk_impl.AVSDKContext##changeRole(java.lang.String,com.tencent.ilivesdk.ILiveCallBack)##{}##0',
            'com.tencent.av.sdk.NetworkHelp##getMobileAPInfo(android.content.Context,int)##if(android.content.pm.PackageManager.PERMISSION_GRANTED != $1.checkPermission(android.Manifest.permission.READ_PHONE_STATE, android.os.Process.myPid(), android.os.Process.myUid())){return new com.tencent.av.sdk.NetworkHelp.APInfo();}##0',
            'com.tencent.sharp.jni.TraeAudioManager$TraeAudioManagerLooper##_post_stopService()##{}##0',// 对_post_stopService()添加try-catch(Throwable)操作
    ]
}
       根据以上的配置,就可以对_post_stopService()方法添加try-catch(Throwable)代码了,运行项目,修复后的_post_stopService()方法如下所示:
public class TraeAudioManager extends BroadcastReceiver {

    // 省略部分代码
    
    class TraeAudioManagerLooper extends Thread {
        
        // 省略部分代码

        void _post_stopService() {
            try {
                try {
                    if(TraeAudioManager.this._bluetoothCheck != null) {
                        TraeAudioManager.this._bluetoothCheck.release();
                    }

                    TraeAudioManager.this._bluetoothCheck = null;
                    if(TraeAudioManager.this._context != null) {
                        TraeAudioManager.this._context.unregisterReceiver(this._parent);
                        IntentFilter var1 = new IntentFilter();
                        var1.addAction("com.tencent.sharp.ACTION_TRAEAUDIOMANAGER_REQUEST");
                        TraeAudioManager.this._context.registerReceiver(this._parent, var1);
                    }
                } catch (Exception var3) {
                    ;
                }

            } catch (Throwable var4) {
                var4.printStackTrace();
            }
        }

        // 省略部分代码
    }

    // 省略部分代码
}
       现在我们利用了BytecodeFixer插件已经成功的对鹅厂SDK内部抛出的异常进行了捕获操作,之后应用就不会在HuaWei手机上crash了,但是这并没有根本性的解决在HuaWei手机上崩溃的问题,我通过在HuaWei手机上做测试发现根本原因是HuaWei自家定制的ROM系统中有一个白名单机制,只有加入了白名单的APP才允许注册超过500个BroadcastReceiver,否则就会抛出Register too many Broadcast Receivers的异常。也就是说没有加入该白名单机制的APP最多只能注册500个BroadcastReceiver,我是怎么发现这个白名单机制的呢?在华为手机上做如下测试,新建项目工程HuaWeiVerifier,MainActivity的布局文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.llew.huawei.verifier.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Register"
        android:onClick="register"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
       MainActivity的布局文件中仅仅添加了一个Button,当点击Button的时候就会执行MainActivity的register()方法,MainActivity代码如下所示:
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void register(View view) {
        for (int i = 1; i <= 1000; i++) {
            IntentFilter filter = new IntentFilter();
            filter.addAction("test index : " + i);
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                }
            }, filter);
            Log.e(getClass().getName(), "当前注册了:" + i + " 个广播接收器");
        }
    }
}
       然后在HuaWei手机上运行该Demo,点击Button后打印Log如下所示:

       从打印的日志信息看,当注册了500个BroadcastReceiver后,再继续注册BroadcastReceiver就会抛出异常,异常信息为:registered 501 Receivers  in 1 Contexts,也就是说当前的应用通过Context注册的BroadcastReceiver超过了500个。根据项目运行后抛出的异常信息,在HuaWei手机上一个应用只能注册500个BroadcastReceiver,除非当前应用加入了白名单里。

       既然HuaWei定制的ROM系统做了限制,那么肯定是在哪一个类中做了校验操作,根据crash信息可以知道崩溃是发生在LoadedApk类的checkRecevierRegisteredLeakLocked()方法中,由于我们没法拿到HuaWei手机ROM系统中LoadedApk类的具体代码,只能通过反射对比在HuaWei手机和Google Pixel手机上这俩LoadedAPK究竟有和不同,功夫不负有心人,最终发现在HuaWei的ROM的LoadedApk类中定义了一个叫mReceiverResource的成员变量,根据名称我们猜测该属性就是和注册BroadcastReceiver的数量相关的类。在HuaWei手机中的Debug模式下,LoadedApk信息如下所示:


       mReceiverResource是ReceiverResource类型,ReceiverResource内部定义了一个ArrayList类型的成员变量mWhiteList,直接看名字就知道是白名单的意思,mWhiteList中默认添加了"com.tencent.mm"字符串,该字符串就是微信的包名。由此可知HuaWei定制的ROM系统把微信加入了白名单里从而允许微信可以注册超过500个BroadcastReceiver。为了验证mReceiverResource是用来控制注册BroadcastReceiver的数量的,我把HuaWei手机上的微信卸载了,然后把刚刚创建的工程包名改为com.tencent.mm,紧接着再运行工程,这时候果然可以注册超过500个BroadcastReceiver了,打印日志如下所示:


       从实验结果来看,果真的如我们前边猜测的那样,mReceiverResource就是用来控制非白名单中的APP最多只能注册500个BraodcastReceiver的开关,只要把我们APP的packageName添加到白名单中,也就跳过了HuaWei手机的限制,顿时好开心呀,终于可以从根本上解决该问题了(*^__^*) ……

       接下来的工作就是通过反射来拿到LoadedApk中的mReceiverResource中的mWhiteList对象,然后把我们APP的packageName加入到mWhiteList中就行了。记得在前边的文章中提过,反射技术在Java开发中是很重要的,学会了反射,你可以做很多事情……为了方便小伙伴们使用反射,我抽出了一个精简的反射库Reflection,该库目前已经开源到了Github上并上传到了Jcenter仓库中,使用的时候只需简单的引入就行了:compile 'com.llew:reflect:1.0.1'

       下边就是通过反射把我们APP的packageName添加到mWhiteList中的操作,实现起来并不复杂。由于我没法在全部的HuaWei手机上做验证测试,只是大概的测试了下,如果有小伙伴们能够用手里的HuaWei手机帮忙做下验证和完善,不胜感激……LoadedApkHuaWei全部代码如下所示:

public class LoadedApkHuaWei {


    public static void hookHuaWeiVerifier(Context baseContext) {
        try {
            if (null != baseContext && "ContextImpl".equals(baseContext.getClass().getSimpleName())) {
                IMPL.verifier(baseContext);
            } else {
                Log.w(LoadedApkHuaWei.class.getSimpleName(), "baseContext is't instance of ContextImpl");
            }
        } catch (Throwable ignored) {
            // ignore it
        }
    }


    private static final HuaWeiVerifier IMPL;

    static {
        final int version = android.os.Build.VERSION.SDK_INT;
        if (version >= 26) {
            IMPL = new V26VerifierImpl();
        } else if (version >= 24) {
            IMPL = new V24VerifierImpl();
        } else {
            IMPL = new BaseVerifierImpl();
        }
    }

    private static class V26VerifierImpl extends BaseVerifierImpl {

        private static final String WHITE_LIST = "mWhiteListMap";

        @Override
        public void verifier(Context baseContext) throws Throwable {
            Object whiteListMapObject = getWhiteListObject(baseContext, WHITE_LIST);
            if (whiteListMapObject instanceof Map) {
                Map whiteListMap = (Map) whiteListMapObject;
                List whiteList = (List) whiteListMap.get(0);
                if (null == whiteList) {
                    whiteList = new ArrayList<>();
                    whiteListMap.put(0, whiteList);
                }
                whiteList.add(baseContext.getPackageName());
            }
        }
    }

    private static class V24VerifierImpl extends BaseVerifierImpl {

        private static final String WHITE_LIST = "mWhiteList";

        @Override
        public void verifier(Context baseContext) throws Throwable {
            Object whiteListObject = getWhiteListObject(baseContext, WHITE_LIST);
            if (whiteListObject instanceof List) {
                List whiteList = (List) whiteListObject;
                whiteList.add(baseContext.getPackageName());
            }
        }
    }

    private static class BaseVerifierImpl implements HuaWeiVerifier {

        private static final String WHITE_LIST = "mWhiteList";

        @Override
        public void verifier(Context baseContext) throws Throwable {
            Object receiverResourceObject = getWhiteListObject(baseContext, WHITE_LIST);
            if (receiverResourceObject instanceof String[]) {
                String[] whiteList = (String[]) receiverResourceObject;
                List<String> newWhiteList = new ArrayList<>();
                newWhiteList.add(baseContext.getPackageName());
                Collections.addAll(newWhiteList, whiteList);
                FieldUtils.writeField(receiverResourceObject, WHITE_LIST, newWhiteList.toArray(new String[newWhiteList.size()]));
            }
        }

        Object getWhiteListObject(Context baseContext, String whiteList) throws Throwable {
            Field receiverResourceField = FieldUtils.getDeclaredField("android.app.LoadedApk", "mReceiverResource", true);
            if (null != receiverResourceField) {
                Field packageInfoField = FieldUtils.getDeclaredField("android.app.ContextImpl", "mPackageInfo", true);
                if (null != packageInfoField) {
                    Object packageInfoObject = FieldUtils.readField(packageInfoField, baseContext);
                    if (null != packageInfoObject) {
                        Object receivedResource = FieldUtils.readField(receiverResourceField, packageInfoObject, true);
                        if (null != receivedResource) {
                            return FieldUtils.readField(receivedResource, whiteList);
                        }
                    }
                }
            }
            return null;
        }
    }

    private interface HuaWeiVerifier {
        void verifier(Context baseContext) throws Throwable;
    }
}

       LoadedApkHuaWei只对外暴露一个hookHuaWeiVerifier()方法,该方法内部实现是根据HuaWei手机不同的版本做了不同的Hook操作。该API使用非常简单,需要在自己APP中显示的定义一个Application,然后在Application的onCreate()方法中调用LoadedApkHuaWei的hookHuaWeiVerifier()方法就行了,如下所示:

public class SimpleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LoadedApkHuaWei.hookHuaWeiVerifier(getBaseContext());
    }
}

       以上操作就可以把我们APP添加进HuaWei ROM中的白名单里了(*^__^*) ……以后再也不怕只在HuaWei手机上出现Register too many Broadcast Receivers的Crash了。目前我把该库起名为HuaWeiVerifer并开源到了GitHub上也上传了jcenter仓库中,希望给遇见同样问题的小伙伴们一点帮助……

       另外需要注意的是LoadedApkHuaWei的hookHuaWeiVerifier(Context baseContext)需要传递进去的是当前APP的baseContext,不要弄错了。这个库没有做过全面的验证,可能还有不兼容的情况,这里欢迎小伙伴们fork and pr


       HuaWeiVerifer的GitHub地址:https://github.com/llew2011/HuaWeiVerifier,欢迎小伙伴们fork and star




发布了39 篇原创文章 · 获赞 87 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/llew2011/article/details/79054457