android隐私违规获取问题处理 及 Hook拦截处理记录 (VirtualXposted/epic等)及 android/iOS 多bundle加载方式修复方案


最新记录 - 自启动隐私问题 (2023/02/23 )

— 小米平台给出的解决方案 - 直接看答案

小米平台-自启动审核解决方案 ->>

----隐私最新问题

在这里插入图片描述
上面写了应用退出是, 但是没给出堆栈…
后来发现 在做纯净Bundle的js包的时候, 退出代码用的 在这里插入图片描述
这里有可能存在监听问题, 所以移除
在这里插入图片描述
换成安卓退出的常规调用

    @ReactMethod
    public void exitComplete() {
        System.exit(0);
    }

目前继续提交审核,下周看结果…

通过堆栈查询后发现:
在这里插入图片描述问题出在极光的jPush

com.regan.xxxx/cn.jpush.android.service.PushReceiver action=android.intent.action.PACKAGE_ADDED

以上方案无效, 但小米平台给出的一个方案:

小米平台-自启动审核解决方案 ->>

于此同时,由于RN框架集成的
由于采用的是RN框架, 所以必须去修改jcore-react-native里面找到对应要移除的receiver
在这里插入图片描述
在 jpush-react-native中,把权限也移除
在这里插入图片描述

目前小米平台已经审核通过
腾讯应用宝 在审核中, 隐私检测已经合规
在这里插入图片描述


正文部分 * 正文部分 * 正文部分 * 正文部分 * 正文部分 * 正文部分

1.如何检测/复现 android_id/Mac地址等权限被超前获取

最近公司启动了隐私合规检测,报告告知我们在用户未同意隐私协议前,我们APP提前获取了WIIF/SSID/Mac地址等信息,不合规需要处理 (就是在同意前以及拒绝后, 不进行某些SDK的初始化…)

APP启动时,在用户授权同意隐私政策前,APP及SDK不可以提前收集和使用IMEI、OAID、IMSI、MAC、应用列表等信息

为了处理上面问题,或者说, 为了自己能够检测改版后的APP 是否符合要求, 我们就需要自己进行对APP进行检测和验证, 确保修改后达到效果
(第三方检测机构出的报告, 我们也要能自己给自己出 ╭(╯^╰)╮)

一通了解折腾以后, 我遇到了这么些框架/工具/demo, 这里逐个介绍一下
Xposed -> Dexposed -> Epic -> VirtualXposed ->HookLoginDemo

Xposed框架不用多说 rovo90 - Xposed , Xpose中文站 但是直接使用这个,需要走root,搞机等操作,较为麻烦

Dexposed 是阿里开源的 alibaba/dexposed,能在非root情况下掌控自己进程空间内的任意Java方法调用, 主要的就是用框架进行hook, 来抓取系统函数调用, 追踪三方SDK的启动情况/函数调用等

Epic - github 是weishu大神基于ART重新实现的一套 Dexposed( ART取代Dalvik成为Android的运行时,Dexposed无法支持更高版本的安卓系统) 这边一篇大神的Epic介绍

Epic可以在你自己的android工程中添加依赖,

dependencies {
    compile 'com.github.tiann:epic:0.11.2'
}

然后可以通过

class ThreadMethodHook extends XC_MethodHook{
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        Thread t = (Thread) param.thisObject;
        Log.i(TAG, "thread:" + t + ", started..");
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Thread t = (Thread) param.thisObject;
        Log.i(TAG, "thread:" + t + ", exit..");
    }
}

DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Thread thread = (Thread) param.thisObject;
        Class<?> clazz = thread.getClass();
        if (clazz != Thread.class) {
            Log.d(TAG, "found class extend Thread:" + clazz);
            DexposedBridge.findAndHookMethod(clazz, "run", new ThreadMethodHook());
        }
        Log.d(TAG, "Thread: " + thread.getName() + " class:" + thread.getClass() +  " is created.");
    }
});
DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());

提供的函数进行 hook操作, 但是只支持 inhook,就是你自己有android工程, 自己在项目里面添加epic依赖后, 自己写代码自己抓日志,
其实, 如果仅仅是为了验证 用户隐私确认前合规的改版后APP. Epic其实足够使用了. 借助Epic,我们可以通过移除SDK来比对监听偷跑的WIFI/MAC地址等行为是否还存在

贴一下我项目里面的一些调用
在MainActivity中, oncreat() 中调用就可以

implementation 'com.github.tiann:epic:0.11.2'
    private String TAG ="ddddd";

    private void startHook() {
        XC_MethodHook hook = new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Object hookObj = param.thisObject;
                String clsName = "unknownClass";
                if (hookObj != null) {
                    clsName = hookObj.getClass().getName();
                }
                String mdName = "unknownMethod";
                if (param.method != null) {
                    mdName = param.method.getName();
                }
                Log.d(TAG, "beforeHookedMethod: " + clsName + "-" + mdName);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Object hookObj = param.thisObject;
                String clsName = "unknownClass";
                if (hookObj != null) {
                    clsName = hookObj.getClass().getName();
                }
                String mdName = "unknownMethod";
                if (param.method != null) {
                    mdName = param.method.getName();
                }
                Log.d(TAG, "afterHookedMethod: " + clsName + "-" + mdName);
            }
        };
        try {
            // hook系统方法
            // hook系统方法
//            DexposedBridge.hookAllMethods(Thread.class, "run", hook);
            // hook构造方法
//            DexposedBridge.hookAllConstructors(Thread.class, hook);

            DexposedBridge.hookAllMethods(BluetoothLeScanner.class, "startScan", hook);
            // deviceID
            DexposedBridge.hookAllMethods(TelephonyManager.class,"getDeviceId", hook);
            // wifi
            DexposedBridge.hookAllMethods(WifiManager.class,"", hook);
            // wifi - 构造hook
            DexposedBridge.hookAllConstructors(WifiManager.class, hook);
            // MAC
            DexposedBridge.hookAllMethods(WifiManager.class,"getMacAddress", hook);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

踩坑1: Epic依赖的minSdkVersion21, 记得把项目的minSdkVersion改为一样否则跑不了程序

但是,检测机构是如何检测我们的APP?

猜测也是借助于 VirtualXposed 等工具,自行编写hook插件 , 来扫描 提交审核的 APP

VirtualXposed - 官方介绍及使用视频 是一个apk应用程序, 目的就是 过简单的 APP 使用 Xposed,无需 root、解锁引导加载程序或刷写系统映像。
具体使用开官网/或者视频,

VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~10.0)。

我最后使用的是 0.20.3 版本, 其他版本都没成功, 要么唤起不了自己的APP, 要么安装不了 Xposed install 失败

这是目前我测试确认可以使用的环境:

设备: 小米 Mix2s  
Androdi版本:10
VirtualXposed版本: 0.20.3

在这里插入图片描述

踩坑1: VirtualXposed 仅支持到 Android 5~10 , 在2022的今天, 也好在我的Mix2s还能使用, 一开始我用公司测试机 都是Android11, 不支持…

踩坑2: 确定自己项目的APP 是 32位 还是 64位 , 32位的仅仅能使用 0.18版本

HookLoginDemo 是一个插件(也是APP) , 基于这个插件 , 可以让我们方便的对我们的APP进行hook, 通过查看日志来方便的检测 在用户同意隐私前我们的APP或第三方SDK, 有没有偷跑一些系统API, (就像检测机构对我们APP做的检测一样)

当然, 也可以在 HookLoginDemo中添加跟多的检测 比如SSID / WIFI列表. 只要稍微修改代码编译成apk 安装到手机, 接着就是 VirtualXposed 的时间了

贴一下 HookLoginDemo 中 HookLogin代码的修改 (有错误的代码… 后面再完善)

package com.example.hooklogin;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.location.LocationManager;
import android.util.Log;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.Toast;

import java.lang.reflect.Field;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

import static de.robv.android.xposed.XposedHelpers.findField;

public class HookLogin implements IXposedHookLoadPackage {
    private static final String TAG = "HookLogin";

    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {

        if (lpparam == null) {
            return;
        }

        Log.e(TAG, "Load app packageName:" + lpparam.packageName);
        /*判断hook的包名*/

//        if (!MyApplication.pageName.equals(lpparam.packageName)  && !"com.enhance.kaomanfen.yasilisteningapp".equals(lpparam.packageName)
//        && !"com.tal.tiku".equals(lpparam.packageName)) {
//            return;
//        }

//        //固定格式
//        XposedHelpers.findAndHookMethod(
//                "com.", // 需要hook的方法所在类的完整类名
//                lpparam.classLoader,                            // 类加载器,固定这么写就行了
//                "attachBaseContext",                     // 需要hook的方法名
//                Context.class,
//                new XC_MethodHook() {
//                    @Override
//                    protected void beforeHookedMethod(MethodHookParam param) {
//                        XposedBridge.log("调用getDeviceId()获取了imei");
//                    }
//
//                    @Override
//                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//                        XposedBridge.log(getMethodStack());
//                        super.afterHookedMethod(param);
//                    }
//                }
//        );

        //固定格式
        XposedHelpers.findAndHookMethod(
                android.telephony.TelephonyManager.class.getName(), // 需要hook的方法所在类的完整类名
                lpparam.classLoader,                            // 类加载器,固定这么写就行了
                "getDeviceId",                     // 需要hook的方法名
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getDeviceId()获取了imei");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );
        XposedHelpers.findAndHookMethod(
                android.telephony.TelephonyManager.class.getName(),
                lpparam.classLoader,
                "getDeviceId",
                int.class,
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getDeviceId(int)获取了imei");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

//            XposedHelpers.findAndHookMethod(
//                    "com.android.internal.telephony.PhoneSubInfo",
//                    lpparam.classLoader,
//                    "getDeviceId",
//                    new XC_MethodHook() {
//                        @Override
//                        protected void beforeHookedMethod(MethodHookParam param) {
//                            XposedBridge.log("调用PhoneSubInfo的getDeviceId()获取了imei");
//                        }
//                    }
//            );

        XposedHelpers.findAndHookMethod(
                android.telephony.TelephonyManager.class.getName(),
                lpparam.classLoader,
                "getSubscriberId",
                int.class,
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getSubscriberId获取了imsi");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        XposedHelpers.findAndHookMethod(
                android.net.wifi.WifiInfo.class.getName(),
                lpparam.classLoader,
                "getMacAddress",
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getMacAddress()获取了mac地址");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        // wifiName
        XposedHelpers.findAndHookMethod(
                android.net.wifi.WifiInfo.class.getName(),
                lpparam.classLoader,
                "getWifiName",
                new XC_MethodHook() {

                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getWifiName()获取了wifi地址");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        //SSID
        XposedHelpers.findAndHookMethod(
                android.net.wifi.WifiInfo.class.getName(),
                lpparam.classLoader,
                "getSSID",
                new XC_MethodHook() {

                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getSSID()获取了wifi地址");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        //BSSID
        XposedHelpers.findAndHookMethod(
                android.net.wifi.WifiInfo.class.getName(),
                lpparam.classLoader,
                "getBSSID",
                new XC_MethodHook() {

                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getBSSID()获取了wifi地址");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        XposedHelpers.findAndHookMethod(
                java.net.NetworkInterface.class.getName(),
                lpparam.classLoader,
                "getHardwareAddress",
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getHardwareAddress()获取了mac地址");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        XposedHelpers.findAndHookMethod(
                android.provider.Settings.Secure.class.getName(),
                lpparam.classLoader,
                "getString",
                ContentResolver.class,
                String.class,
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用Settings.Secure.getstring获取了" + param.args[1]);
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );

        XposedHelpers.findAndHookMethod(
                LocationManager.class.getName(),
                lpparam.classLoader,
                "getLastKnownLocation",
                String.class,
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) {
                        XposedBridge.log("调用getLastKnownLocation获取了GPS地址");
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log(getMethodStack());
                        super.afterHookedMethod(param);
                    }
                }
        );
    }

    private String getMethodStack() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

        StringBuilder stringBuilder = new StringBuilder();

        for (StackTraceElement temp : stackTraceElements) {
            stringBuilder.append(temp.toString() + "\n");
        }

        return stringBuilder.toString();

//        try
//        {
//            throw new NullPointerException();
//        }
//        catch (Exception ex)
//        {
//            return Log.getStackTraceString(ex);
//        }
    }
}

抓到的一些日志
在这里插入图片描述

关于hooklogin插件

后续自己研究一下 , 计划写一个插件, 方便以后使用,

太极

太极 是VirtualXposed的兄弟版本, 具体怎么用还没尝试, 后续把使用计划回来补全


---- 以上基础工作完成----


2.RN端对安卓/iOS 隐私权限的处理方案

相关资料参考
如何在android原生中加载RN页面
RN打包生产jsbundle文件
RN加载Bundle的方式

简要介绍下项目当前情况, 纯RN项目一枚
在尽量不改动原版功能的基础上,做好隐私协议的前置(在各种RN组件/三方组件加载前),不同意就多次确认后,退出APP, 基本上就是这种思路

基于以上思路, 有两个处理方案

1.方案一:RN工程中, 仍然使用一个bundle资源,
通过配置index.js / idnea.js 两个入口
在这里插入图片描述

在原生APP启动的时候(这里用安卓举例),
用户拒绝隐私->退出app
用户同意隐私-> 动态修改 getJSMainModuleName()入口->重刷新MainActivity生命周期

     @Override
            protected String getJSMainModuleName() {
            // 这里通过本地缓存数据指定加载不同入口文件
                return "indea"; 
                return "indeX"; 
             
            }

在MaincActivity.java中添加重刷代码

    
    public void destoryAndRecreate() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        recreate();
                    }
                });
            }
        }, 1000);
    }

2.方案二 :方案1的变种 ,区别是, 在Assets中放入一个极简的bundle包, 这意味着,我们打包的apk,将携带两个bundle
在这里插入图片描述
区别就在于:
方案一加载的是一个包括indea.js/index.js文件的, 名为index.android.bundle的budle
方案二分别加载的是 一个仅包含indea.js文件的名为indea.android.bundle的bundle和 一个仅包含index.js文件的名为index.android.bundle的bundle

同样的通过如下逻辑
用户拒绝隐私->退出app
用户同意隐私-> ->动态加载getBundleAssetName()两个bundle->动态修改 getJSMainModuleName()入口->重刷新MainActivity生命周期

重写的 getBundleAssetName()代码举例

            @Nullable
            @org.jetbrains.annotations.Nullable
            @Override
            protected String getBundleAssetName() {
//                return "------";
 // 这里通过本地缓存数据指定加载不同bundle资源
                return "indea.android.bundle"; 
                return "index.android.bundle"; 
            }
采用方案二的考虑

1.双bundle,代码隔离/逻辑隔离
首先加载极简隐私bundle, 无任何三方SDK引用/无RN相关特殊组件影响,仅RN最基础UI组件逻辑,
如果是方案一, 混在一起不讨本人喜欢
2.方案一已经实现了, 方案二没实现过, 一定要搞! 不会就要搞!

Android + RN

依旧拿Android举例
Android端的原生加载RN源码,网上挺多, 这里记录一下个人的理解,方便后期自己查阅和修改
这里稍作记录

Android端的RN加载bundle过程

1.MainApplication中

public class MainApplication extends Application implements ReactApplication {

2.ReactApplication中发现

public interface ReactApplication {
    ReactNativeHost getReactNativeHost();
}

3.ReactNativeHost中发现

        String jsBundleFile = this.getJSBundleFile();
        if (jsBundleFile != null) {
            builder.setJSBundleFile(jsBundleFile);
        } else {
            builder.setBundleAssetName((String)Assertions.assertNotNull(this.getBundleAssetName()));
        }

        ReactInstanceManager reactInstanceManager = builder.build();
        ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_END);
        return reactInstanceManager;

源码基本写的较为明白, 优先从 this.getJSBundleFile()加载bundleFile,
但是仔细看

    @Nullable
    protected String getJSBundleFile() {
        return null;
    }

getJSBundleFile()默认是返回null的

这里也插一个小实践 ,如果是直接 react-native run-android运行RN命令, RN会默认打包出debug模式的apk文件,帮我们install到手机/模拟器上去

注意: 在打包apk之前, react-native run-android先是会打包出bundle文件:index.android.bundle,
这里打包出的index.android.bundle是作为Assets资源在后面打包apk的过程中,存入了Assets资源目录
并不会帮我们存放到./android/app/src/main/assets/文件路径下,
所以在 ./android/app/src/main/assets/中找不到 index.android.bundle, 因为已经被打包到apk包的asset资源目录了
在这里插入图片描述
这个debug的apk, 就是自带了index.android.bundle的一个apk包

也因此无论是直接react-native run-android还是gradlew直接打包apk, 我们如果不通过打包bundle的特殊命令,是无法直接获取bundle资源的, 想要打bundle包,得自己单独打包

继续说回getJSBundleFile()
源码这里很明确,RN就是这样设计的, jsBundleFile默认是null,

        if (jsBundleFile != null) {
            builder.setJSBundleFile(jsBundleFile);
        } else {
            builder.setBundleAssetName((String)Assertions.assertNotNull(this.getBundleAssetName()));
        }

andorid Bundle的加载逻辑:

1.优先从 getJSBundleFile()加载资源
2.如果没有,就从apk打包的Asset资源目录里面加载bundle(即 index.android.bundle这个默认的bundle)

这里就很明确了, 如果不做任何处理, 一定走入else逻辑分支, 从 getBundleAssetName()中获取BundleAssetName, 继续看

    @Nullable
    protected String getBundleAssetName() {
        return "index.android.bundle";
    }

getBundleAssetName()还真的就是默认返回一个 "index.android.bundle"资源路径程序会在Assets中加载该名称的bundle

结合上面提过的小实践, 也印证了直接 react-native run-android, RN会默认打包出 index.android.bundle存在Assets资源目录里面

我们再看看运行日志

在这里插入图片描述

这里要注意一点:
在这里插入图片描述
1.如果 getBundleAssetName()在开发/调试模式, 并不会真的走asset资源下的bundle, 而是会走 pakcager server, 就是从我们的终端服务器中获取, 毕竟是为了要热刷新
2.如果是在打包出apk以后, getBundleAssetName()会去asset资源中加载真正的 index.android.bundle, 当然如果加载不到, 表现就是启动后闪退啦

那么, 如果:

1.手动打包一个名为: indea.android.bundle的bundle作为极简bundle, 存放于项目工程./android/app/scr/main/assets/
(亦或者 ./android/app/scr/main/assets/bundleA/, 自己添加一个bundleA的文件目录也是可以的)

  1. 重写 getJSMainModuleName(), 修改主入口为 :indea
  @Override
            protected String getJSMainModuleName() {
                return "indea";
            }

3.重写 getBundleAssetName() ,指定加载 asset资源下的indea.android.bundle

            @Nullable
            @org.jetbrains.annotations.Nullable
            @Override
            protected String getBundleAssetName() {
                return "indea.android.bundle"; //
                or
                // 如果是放在 :asset/bundleA/indea.android.bundle 这里, 那么返回也要这样写
                return "bundleA/indea.android.bundle"; 

            }

这三步处理好, 这不就是 方案二

Android几个函数的理解/记录

1.getJSMainModuleName()的用法

用于在RN工程中, 寻找MainModule的文件入口,
讲白了就是在Android工程中,安卓原生启动后,会从这个接口指定RN层代码的入口

举个例子
RN端 我有一个index.js入口 代码如下

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

我还有个indea.js入口 代码如下

import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';
import TestApp from "./test/TestApp";

AppRegistry.registerComponent(appName, () => TestApp);

在Android端
如果不重写 ReactNativeHost下的 getJSMainModuleName()我们看到源码里面是这样的

  protected String getJSMainModuleName() {
        return "index.android";
    }

安卓默认会去找一个叫做 index.android的MainModule主入口js文件,
这里 可以重写 getJSMainModuleName()改为寻找 我们上面两个例子的入口文件名

例子1

  @Override
  protected String getJSMainModuleName() {
        return "index";
    }

例子2

  @Override
  protected String getJSMainModuleName() {
        return "indea";
    }

分别运行安卓, 进入的效果也是分别进入了 App.jsTestApp.js

getJSMainModuleName()就是一个用来指定 RN工程中, 主入口js文件的函数, 是getJSMainModuleName选择了RN的主入口js文件, 当然如果不匹配就报错了

2.getBundleAssetName()的用法

记得只要把打包的bundle文件 放在 ./android/app/scr/main/assets/文件夹下, 在打包apk的过程中会存放在apk包内,通过getBundleAssetName()加载使用,
(当然,采用方案二, 我们会有俩bundle , indea.android.bundle和默认的index.android.bundle,apk包也会变大咯, 2022年了,大公司的apk,当然是越大越好啦 :XD )

记录下indea.android.bundle打包/使用

1.bundle包-直接打包到./android/app/src/maic/assets/

 react-native bundle --entry-file indea.js --bundle-output ./android/app/src/main/assets/indea.android.bundle --platform android --assets-dest ./android/app/src/main/assets/ --dev false --verbose
  1. 修改 MainApplication中代码
            @Override
            protected String getJSMainModuleName() {
                return "indea";
            }

            @Nullable
            @org.jetbrains.annotations.Nullable
            @Override
            protected String getBundleAssetName() {
                return "indea.android.bundle"; // 取android工程下
            }

在这里插入图片描述
3.打包+安装 apk, 运行看效果
在这里插入图片描述

如果加一层文件夹路径呢? 也是可以的

1.bundle包-直接打包到./android/app/src/maic/assets/bundleA

 react-native bundle --entry-file indea.js --bundle-output ./android/app/src/main/assets/bundleA/indea.android.bundle --platform android --assets-dest ./android/app/src/main/assets/bundleA/ --dev false --verbose

2.修改 MainApplication中代码

            @Override
            protected String getJSMainModuleName() {
                return "indea";
            }

            @Nullable
            @org.jetbrains.annotations.Nullable
            @Override
            protected String getBundleAssetName() {
            // 如果是放在 :asset/bundleA/indea.android.bundle 这里, 那么返回也要这样写
                return "bundleA/indea.android.bundle";
            }

在这里插入图片描述
3.打包+安装 apk, 运行看效果
在这里插入图片描述


3.验证该极简bundle :indea.android.bundle

在这里插入图片描述

4. iOS 端

bundle资源打包在工程文件中

在这里插入图片描述

加载 - 开发模式

  if(self.privateAgree_flag){
    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  }else{
    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"indea" fallbackResource:nil];
  }

加载 - 打包模式

  if(self.privateAgree_flag){
    return [[NSBundle mainBundle] URLForResource:@"./bundle/index.ios" withExtension:@"jsbundle"];
  }else{
    return [[NSBundle mainBundle] URLForResource:@"./bundleA/indea.ios" withExtension:@"jsbundle"];
  }

iOS端比较简单, 通过自定义一个全局标识privateAgree_flag来确定APP启动时加载不同的bundle,
在开发模式下, iOS 通过 jsBundleURLForBundleRoot指定rn的入口
在发布模式下,iOS通过URLForResource加载不同的jsbundle包

5. android_id : react-natieve-device-info 问题

react-native 调用Settings.Secure.getstring获取了android_id / app上架违规获取android_id被拒

又在腾讯应用宝的审核中, 发现过渡搜集问题…
目前已经做过一次申请,缓存读取的方案, 但是依然检测多次触发获取,
现在打算尝试上面链接的方案…

是否有效后续补上…

在这里插入图片描述
在这里插入图片描述

上面说的改为内联方式 ---- 目前确定无效

解决方案:

通过下载堆栈调用发现,四次获取android_Id都是调用了 getUniqueId()
查看文档发现. 是一个Promise, 推测是否异步问题导致, 故而改为 同步调用获取
目前该项已经通过扫描
在这里插入图片描述

在这里插入图片描述
至于友盟SDK的四次…
再说吧 o(╥﹏╥)o

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值