IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 通过拦截 Activity的创建 实现APP的隐私政策改造 -> 正文阅读

[移动开发]通过拦截 Activity的创建 实现APP的隐私政策改造

序言

最近因为政策收紧,现在要求APP必须在用户同意的情况下才能获取隐私信息。但是很多隐私信息的获取是第三方SDK获取的。而SDK的初始化一般都在application中。由于维护的项目多,如果贸然改动很有可能造成潜在的问题。所以想研究一个低侵入性的方案。在不影响原有APP流程的基础上完成隐私改造。

方案

研究了几个方案,简单的说一下

方案1

通过给APP在设置一个入口,将原有入口的activity的enable设置为false。让客户端先进入到隐私确认界面
。确认完成,再用代码使这个activity的enable设置为false。将原来的入口设置为true。
需要的技术来自这篇文章
(技术)Android修改桌面图标

效果

这种方案基本能满足要求。但是存在两个问题。

  1. 将activity设置为false的时候会让应用崩溃。上一篇文章提到使用别名的方案也不行。
  2. 修改了activity以后,Android Studio启动的时候无法找到在清单文件中声明的activity。

方案2

直接Hook Activity的创建过程,如果用户没有通过协议,就将activity 变为我们的询问界面。
参考文献:
Android Hook Activity 的几种姿势
Android应用进程的创建 — Activity的启动流程

需要注意的是,我们只需要Hook ActivityThread 的mInstrumentation 即可。需要hook的方法是newActivity方法。

public class ApplicationInstrumentation extends Instrumentation {

    private static final String TAG = "ApplicationInstrumentation";

    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;

    public ApplicationInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public Activity newActivity(ClassLoader cl, String className,
                                Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        className = CheckApp.getApp().getActivityName(className);
        return mBase.newActivity(cl, className, intent);
    }


}

使用

最终使用了方案2。通过一个CheckApp类来实现管理。
使用很简单,将你的Application类继承自CheckApp 将sdk的初始化放置到 initSDK方法中
为了避免出错,在CheckApp中我已经将onCreate设置为final了

public class MyApp extends CheckApp {
  

    public DatabaseHelper dbHelper;
   protected void initSDK() {
        RxJava1Util.setErrorNotImplementedHandler();
        mInstance = this;
        initUtils();
    }

    private void initUtils() {
    }
}

在清单文件中只需要注册你需要让用户确认隐私协议的activity。

<application>
...
        <meta-data
            android:name="com.trs.library.check.activity"
            android:value=".activity.splash.GuideActivity" />
           
</application>
           

如果要在应用每次升级以后都判断用户协议,只需要覆盖CheckApp中的这个方法。(默认开启该功能)

/**
     * 是否每个版本都检查是否拥有用户隐私权限
     * @return
     */
    protected boolean checkForEachVersion() {
        return true;
    }

判断用户是否同意用这个方法

CheckApp.getApp().isUserAgree();

用户同意以后的回调,第二个false表示不自动跳转到被拦截的Activity

    /**
             * 第二个false表示不自动跳转到被拦截的Activity
             * CheckApp 记录了被拦截的Activity的类名。
             */
            CheckApp.getApp().agree(this,false,getIntent().getExtras());

源码

一共只有3个类
在这里插入图片描述

ApplicationInstrumentation

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import java.lang.reflect.Method;

/**
 * Created by zhuguohui
 * Date: 2021/7/30
 * Time: 13:46
 * Desc:
 */
public class ApplicationInstrumentation extends Instrumentation {

    private static final String TAG = "ApplicationInstrumentation";

    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;

    public ApplicationInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public Activity newActivity(ClassLoader cl, String className,
                                Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        className = CheckApp.getApp().getActivityName(className);
        return mBase.newActivity(cl, className, intent);
    }


}


CheckApp



import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.multidex.MultiDexApplication;

import com.trs.library.util.SpUtil;

import java.util.List;

/**
 * Created by zhuguohui
 * Date: 2021/7/30
 * Time: 10:01
 * Desc:检查用户是否给与权限的application
 */
public abstract class CheckApp extends MultiDexApplication {

    /**
     * 用户是否同意隐私协议
     */
    private static final String KEY_USER_AGREE = CheckApp.class.getName() + "_key_user_agree";
    private static final String KEY_CHECK_ACTIVITY = "com.trs.library.check.activity";

    private boolean userAgree;

    private static CheckApp app;


    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        userAgree = SpUtil.getBoolean(this, getUserAgreeKey(base), false);
        getCheckActivityName(base);
        if (!userAgree) {
            //只有在用户不同意的情况下才hook ,避免性能损失
            try {
                HookUtil.attachContext();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    protected String getUserAgreeKey(Context base) {
        if (checkForEachVersion()) {
            try {
                long longVersionCode = base.getPackageManager().getPackageInfo(base.getPackageName(), 0).versionCode;
                return KEY_USER_AGREE + "_version_" + longVersionCode;
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return KEY_USER_AGREE;

    }

    /**
     * 是否每个版本都检查是否拥有用户隐私权限
     * @return
     */
    protected boolean checkForEachVersion() {
        return true;
    }

    private static boolean initSDK = false;//是否已经初始化了SDK

    String checkActivityName = null;

    private void getCheckActivityName(Context base) {
        mPackageManager = base.getPackageManager();
        try {
            ApplicationInfo appInfo = mPackageManager.getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
            checkActivityName = appInfo.metaData.getString(KEY_CHECK_ACTIVITY);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        checkActivityName = checkName(checkActivityName);

    }

    public String getActivityName(String name) {
        if (isUserAgree()) {
            return name;
        } else {
            setRealFirstActivityName(name);
            return checkActivityName;
        }
    }

    private String checkName(String name) {
        String newName = name;
        if (!newName.startsWith(".")) {
            newName = "." + newName;
        }
        if (!name.startsWith(getPackageName())) {
            newName = getPackageName() + newName;
        }

        return newName;

    }


    @Override
    public final void onCreate() {
        super.onCreate();
        if (!isRunOnMainProcess()) {
            return;
        }
        app = this;
        initSafeSDK();

        //初始化那些和隐私无关的SDK
        if (userAgree && !initSDK) {
            initSDK = true;
            initSDK();
        }

    }


    public static CheckApp getApp() {
        return app;
    }


    /**
     * 初始化那些和用户隐私无关的SDK
     * 如果无法区分,建议只使用initSDK一个方法
     */
    protected void initSafeSDK() {

    }


    /**
     * 判断用户是否同意
     *
     * @return
     */
    public boolean isUserAgree() {
        return userAgree;
    }


    static PackageManager mPackageManager;


    private static String realFirstActivityName = null;

    public static void setRealFirstActivityName(String realFirstActivityName) {
        CheckApp.realFirstActivityName = realFirstActivityName;
    }

    public void agree(Activity activity, boolean gotoFirstActivity, Bundle extras) {

        SpUtil.putBoolean(this, getUserAgreeKey(this), true);
        userAgree = true;

        if (!initSDK) {
            initSDK = true;
            initSDK();
        }

        //启动真正的启动页
        if (!gotoFirstActivity) {
            //已经是同一个界面了,不需要自动打开
            return;
        }
        try {
            Intent intent = new Intent(activity, Class.forName(realFirstActivityName));
            if (extras != null) {
                intent.putExtras(extras);//也许是从网页中调起app,这时候extras中含有打开特定新闻的参数。需要传递给真正的启动页
            }
            activity.startActivity(intent);
            activity.finish();//关闭当前页面
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }


    /**
     * 子类重写用于初始化SDK等相关工作
     */
    abstract protected void initSDK();

    /**
     * 判断是否在主进程中,一些SDK中的PushServer可能运行在其他进程中。
     * 也就会造成Application初始化两次,而只有在主进程中才需要初始化。
     * * @return
     */
    public boolean isRunOnMainProcess() {
        ActivityManager am = ((ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE));
        List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
        String mainProcessName = this.getPackageName();
        int myPid = android.os.Process.myPid();
        for (ActivityManager.RunningAppProcessInfo info : processInfos) {
            if (info.pid == myPid && mainProcessName.equals(info.processName)) {
                return true;
            }
        }
        return false;
    }


}

HookUtil


import android.app.Instrumentation;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Created by zhuguohui
 * Date: 2021/7/30
 * Time: 13:20
 * Desc:
 */
public class HookUtil {



    public static void attachContext() throws Exception {
        Log.i("zzz", "attachContext: ");
        // 先获取到当前的ActivityThread对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        //currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);

        // 拿到原始的 mInstrumentation字段
        Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
        // 创建代理对象
        Instrumentation evilInstrumentation = new ApplicationInstrumentation(mInstrumentation);
        // 偷梁换柱
        mInstrumentationField.set(currentActivityThread, evilInstrumentation);
    }


}

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 16:45:30  更:2021-07-31 16:46:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 22:07:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码