美文网首页
Android接入第三方SDK可能带来的问题以及解决方法

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

作者: 虚假雨 | 来源:发表于2019-03-12 21:15 被阅读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,这个时候一般有这么几个途径:

    • 在结束方法中指定Intent
      如果Activity有提供结束回调,比如关闭按钮的点击事件,我们可以在点击事件中主动startActivity进行跳转

    • 修改Activity的任务栈
      在项目中,有些Activity会有自己的任务栈,这个时候如果不做修改,第三方的SDK的Activity一般会运行在App主任务栈上,跳转变得琐碎,这个时候就可以在清单中重写第三方这个Activity,将其规划到自己的其他任务栈中 ,也就能实现流畅跳转了。这个方法修改起来幅度下见效快,但是缺点也是很明显的,就是只能在你指定的那个任务栈上运行了,想在其他任务栈上使用会存在同样的问题;另外,这样的修改已经算侵入第三方SDK了,可能会带来奔溃等问题

    • 监听返回以及home事件
      这个方法限定比较大,因为一是很多SDK的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中增加一些需要的东西比如接口回调


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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/xnsppqtx.html