ITPub博客

首页 > 应用开发 > IT综合 > 直播带货app源码,怎样实现用户同意获取信息

直播带货app源码,怎样实现用户同意获取信息

IT综合 作者:云豹科技阿星 时间:2021-12-02 16:57:31 0 删除 编辑

序言

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

方案

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

方案1

通过给直播带货app源码在设置一个入口,将原有入口的activity的enable设置为false。让客户端先进入到隐私确认界面 。确认完成,再用代码使这个activity的enable设置为false。将原来的入口设置为true。 需要的技术来自这篇文章

效果

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

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

方案2

直接Hook Activity的创建过程,如果用户没有通过协议,就将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>

如果要在应用直播带货app源码升级以后都判断用户协议,只需要覆盖 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);
    }
}

这样,直播带货app源码就能让用户同意获取隐私信息了。

声明:本文由云豹科技转发自solo_99博客,如有侵权请联系作者删除


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69982461/viewspace-2845462/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2020-08-24

  • 博文量
    239
  • 访问量
    81622