Android接入第三方SDK可能带来的问题以及解决方法

2019-03-12  本文已影响0人  虚假雨

笔者由于工作的原因,需要接入多家广告厂商的SDK。但是厂商的SDK偶尔会出现奔溃,由于大厂流程原因没办法及时提供新版SDK,这就需要我们临时解决下;而且在部分场景下对方提供的接口不能满足我们的需求,因此需要灵活修改。这里结合自身经验分享下,希望有更多的朋友提供自己的想法。

临时解决奔溃

遵循合作方的使用规范

在大多数时候,使用第三方SDK出现奔溃都是因为没有按照合作方的方法来,比如有的SDK强制要求在Application初始化的时候初始化自己的SDK,有的时候我们为了启动性能的考虑会将其延后启动,这就会导致奔溃,本身SDK的接入就得考虑其带来的体积以及性能影响。

能用try-catch解决的问题都不叫问题

有经验的程序员都喜欢在关键代码出加上try-catch,同样的面对大多数SDK奔溃我们可以采用最简单的try防止异常抛出,防止整个app奔溃。举个例子,头条SDK1.9.8.8修改了初始化的方式,将其统一在Application初始化中,在使用时需要获取头条的native类,如果发现尚未初始化完成则会直接抛出奔溃(本质上说不算奔溃,只是api的修改对于老代码很不友好)。


1.9.8.8头条获取native

但是呢,这种包裹只能在代码所在的同步线程上catch住奔溃,很多情况下是不够用的,比如新开的Activity中的奔溃

使用javassist 修改jar

有些时候奔溃发生在我们触碰不到的地方,这个时候我们发现奔溃想要停止他的话只能侵入代码了。这里介绍一个工具:javassist。
这个东西网上介绍它的比较多的是动态编程,通过修改字节码来达到动态修改的目的。所以这个东西也能被用来修改我们的目标class类型。

下面开码。

第一步,对我们的目标jar包进行修改,拿到奔溃的class,弄一个新的
        //使用的javasist jar包为Javassist 3.23.1-GA.jar
        //首先第一步,获取ClassPool对象
        ClassPool pool = ClassPool.getDefault();
        //随后加入我们的jar包路径,使得它能找到我们的目标class
        pool.insertClassPath("你想要修改的Jar包.jar");
        //insertClassPath可以多次调用
        pool.insertClassPath("/Users/Android/sdk/platforms/android-28/android.jar");
        //从类池里找到我们的目标类
        CtClass adEventThreadClass = pool.get("com.cmcm.Gzoom.GzTest");
        //打印目标类信息
        System.out.println("class info : " + adEventThreadClass);
        //找到目标类中的方法,如果被混淆过的话只能用混淆的方法名
        CtMethod handleMessageMethod = adEventThreadClass.getDeclaredMethod("test");
        //在方法里加上try-catch
        CtClass etype = pool.get("java.lang.Exception");
        //传进去的文本必须是大括号括起来的,还要加上返回值;$e代表异常值,这里我们打印出来,也就是我们传入android.jar的目的
        handleMessageMethod.addCatch("{ android.util.Log.e(\"gzoomTTCatch\", \"tt adv sdk crash\", $e); return true; }", etype);
        //可以将修噶后的方法打印出来
        printMethod(handleMessageMethod);
        // 写出来是个class
        adEventThreadClass.writeFile("你想要打包的路径");

执行之后,在目标路径下就有了我们修改后的类

第二步,解压原来的jar包,将我们新生成的class替换进去

解压命令为:

unzip 目标Jar包.jar -d 解压文件夹名

然后顺着路径找到目标class,替换

第三步,打包我们的解压文件夹为jar包

打包命令为:

jar cvf 新Jar包名字.jar -C 解压出来的文件夹名/ .

请注意最后的.,不过你写错他也会提示你的

到这里就完成了,我们拿到了一个新的jar包,能catch住奔溃

如果是aar怎么办

在实际项目中,遇到很多的aar只是为了清单Manifest中注册而生成了aar,也就是说aar中只有Manifest、jar包以及部分资源(比如style)之外就没有了,所以大可以自己拷过来,只拿jar包
当然上面的不能包括全部,aar如何重新打包请参考:
Android修改第三方.aar后重新打包

根据自己业务需求灵活修改

很多时候合作方提供的api不能满足我们的需求,这个时候我们就要在合理范围内灵活运用了。

Activity的跳转指定

有的时候我们掉起合作方的功能,大多数是一个Activity,如果我们想要在Activity结束后回到某个Activity,这个时候一般有这么几个途径:

Activity的取消

在某些时候,我们希望能够从外部杀掉Activity,比如第三方SDK没有做好数据恢复导致白屏等。这里我们需要反射拿到Application的栈,然后找到目标Activity实例finish它。
如何找到目标Activity呢?如果它能停留在页面上一会的话,你可以使用adb命令查看:

adb shell dumpsys activity

然后在合适的地方调用如下代码,finish掉它

private void clearTTRewardActivity(Application application) {
        try {
            Class<Application> applicationClass = Application.class;
            Field mLoadedApkField = applicationClass.getDeclaredField("mLoadedApk");
            mLoadedApkField.setAccessible(true);
            Object mLoadedApk = mLoadedApkField.get(application);
            Class<?> mLoadedApkClass = mLoadedApk.getClass();
            Field mActivityThreadField = mLoadedApkClass.getDeclaredField("mActivityThread");
            mActivityThreadField.setAccessible(true);
            Object mActivityThread = mActivityThreadField.get(mLoadedApk);
            Class<?> mActivityThreadClass = mActivityThread.getClass();
            Field mActivitiesField = mActivityThreadClass.getDeclaredField("mActivities");
            mActivitiesField.setAccessible(true);
            Object mActivities = mActivitiesField.get(mActivityThread);
            if (mActivities instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<Object, Object> arrayMap = (Map<Object, Object>) mActivities;
                for (Map.Entry<Object, Object> entry : arrayMap.entrySet()) {
                    Object value = entry.getValue();
                    Class<?> activityClientRecordClass = value.getClass();
                    Field activityField = activityClientRecordClass.getDeclaredField("activity");
                    activityField.setAccessible(true);
                    Object o = activityField.get(value);
                    Activity activity = (Activity) o;
                    if ("你想要找的Activity全名".equals(activity.getLocalClassName())) {
                        activity.finish();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

当然了,上面的方法很强大,已经拿到了Activity实例了,你想调别的方法也可以,这里不拓展。

Activity的扩展

有时候第三方的Activity进行了混淆,内部代码没法看,但是提供的功能又不足,同时外部对它的使用是开放可见的(比如SDKActivity.start()),这样我们可以继承他,然后在Activity中增加一些需要的东西比如接口回调


增加接口,写成静态的需要注意被替换等问题
上一篇下一篇

猜你喜欢

热点阅读