好吧,我承认,其实这一篇文章,主要使用到的就是动态代理,但是个人觉得还是有很大意义的,比如说可以降低代码耦合度,如果想在用户的某一类操作都要打印log获取当前参数,或者是记录用户的点击事件,点击时间等,那么此时在现有代码的基础上每次在点击事件中做处理,肯定是可以的,但是这样,我们要修改多少代码,其实此时,我们就可以完全使用代理来实现类似的功能。说道这里了,就先来看看,什么是代理吧

##静态代理
比如,现在都有海外代购,那么这些代购就可以称之为代理,我们不用和真正卖家沟通,只需要借助代理去实现就ok了,但是添加没有免费的午餐,作为代理工作人员,一定是要从中获利的,下面我们就来模拟这一过程。
###定义购买接口

/** * BuySomething接口用来购买东西的,实现类和代理类需要实现它 * @author liuhang * */public interface BuySomething {public void buyGood(Good good);}

###添加购买实现类

/** * 购买买东西的实现类 * @author liuhang * */public class BuySomethingImpl implements BuySomething {@Overridepublic void buyGood(Good good) {System.out.println("购买了 :"+good.getName()+"  花费 :"+good.getPrice() +"$");}}

###购买东西的代理类

/** * 购买东西的代理类 * @author liuhang * */public class BuyProxy implements BuySomething {private BuySomething buy;public BuyProxy(BuySomething buy) {super();this.buy = buy;}/** * 作为代理,需要从中抽成,这里每件商品赚取30$ */@Overridepublic void buyGood(Good good) {System.out.println("作为BuyProxy, 需要给我打折.....");good.setPrice(good.getPrice() - 30);buy.buyGood(good);}    public static void main(String[] args) {// 将需要购买的东西放到集合中List goodList = new ArrayList<>();Good iphone = new Good("iphone",5000);Good ipad = new Good("ipad",2000);goodList.add(iphone);goodList.add(ipad);        // 通过代理购买东西,最终是通过BuySomethingImpl进行购买的BuySomething buySomething = new BuyProxy(new BuySomethingImpl());for (Good good : goodList) {buySomething.buyGood(good);}    }}


可以看到,此时我们购买iphone和ipad实际上不是直接通过BuySomethingImpl去购买的,而是通过BuyProxy,并且BuyProxy也赚取了60大洋呢。 那么代理有什么好处,说一种情况吧,大家应该都在APP中使用过第三方sdk,如果直接在代码中使用,那么如果sdk升级,以后接口参数变了,此时如果我们代码中很多地方使用到了这个方法,
那么糟糕了,难道每个地方都要更改,其实如果使用了代理,不管sdk怎么更改,我们只需要和代理打交道,具体只需要更改代理类就行了。

##动态代理
在java中实现动态代理有两种方法,一种是jdk提供的,另一种是使用cglib实现
###基于jdk实现动态代理###
下面我们使用jdk提供的Proxy,同样实现上面的功能。
####定义代理类DynamicProxy实现InvocationHandler接口

/** * 基于JDK实现动态代理,需要实现InvocationHandler接口 * @author liuhang * */public class DynamicProxy implements InvocationHandler {private BuySomething mBuySomething;private List goodList;public DynamicProxy(BuySomething mBuySomething, List goodList) {super();this.mBuySomething = mBuySomething;this.goodList = goodList;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {for (Good good : goodList) {System.out.println("作为BuyProxy, 需要给我打折.....");good.setPrice(good.getPrice() - 30);mBuySomething.buyGood(good);}return null;}public static void main(String[] args) {// 将需要购买的东西放到集合中List goodList = new ArrayList<>();Good iphone = new Good("iphone",5000);Good ipad = new Good("ipad",2000);goodList.add(iphone);goodList.add(ipad);BuySomething buySomething = (BuySomething) Proxy.newProxyInstance(BuySomething.class.getClassLoader(),new Class[]{BuySomething.class},new DynamicProxy(new BuySomethingImpl(),goodList));buySomething.buyGood(null);}}

###使用cglib动态代理
下面使用cglib动态代理实现上面的需求,这里需要注意一点,就是一定要引入正确的jar包

public class BuySomeThingCglib implements MethodInterceptor {private Enhancer enhancer = new Enhancer();  /** * 利用Enhancer生成代理类 * @param clazz * @return */public Object getProxy(Class clazz){  //设置需要创建子类的类  enhancer.setSuperclass(clazz);  enhancer.setCallback(this);  //通过字节码技术动态创建子类实例  return enhancer.create();  } @Overridepublic Object intercept(Object obj, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {System.out.println("作为BuyProxy, 需要给我打折.....");methodProxy.invokeSuper(obj, arg);  return null;}      public static void main(String[] args) {// 将需要购买的东西放到集合中List goodList = new ArrayList<>();Good iphone = new Good("iphone",5000);Good ipad = new Good("ipad",2000);goodList.add(iphone);goodList.add(ipad);BuySomeThingCglib buyCglib = new BuySomeThingCglib();  BuySomething buySomething = (BuySomethingImpl)buyCglib.getProxy(BuySomethingImpl.class);  for (Good good : goodList) {buySomething.buyGood(good);}}}

好了,cglib动态代理同样实现上面的需求,这里不做过多的描述,毕竟不是这篇重点。

##Hook activity启动
我们都知道启动activity的时候,最终是通过Instrumentation#execStartActivity 方法启动activity的,具体可以参考 Activity启动流程
###Instrumentation#execStartActivity

public ActivityResult execStartActivity(            Context who, IBinder contextThread, IBinder token, Activity target,            Intent intent, int requestCode, Bundle options) {        ........        try {            // 通过ActivityManagerNative.getDefault()的startActivity来启动activity            int result = ActivityManagerNative.getDefault()                .startActivity(whoThread, who.getBasePackageName(), intent,                        intent.resolveTypeIfNeeded(who.getContentResolver()),                        token, target != null ? target.mEmbeddedID : null,                        requestCode, 0, null, options);            // 根据返回的result结果,给用户对应的提示            checkStartActivityResult(result, intent);        } catch (RemoteException e) {            throw new RuntimeException("Failure from system", e);        }        return null;}

###ActivityManagerNative

public abstract class ActivityManagerNative extends Binder implements IActivityManager{    static public IActivityManager getDefault() {        return gDefault.get();    }    private static final Singleton gDefault = new Singleton() {        protected IActivityManager create() {            IBinder b = ServiceManager.getService("activity");            if (false) {                Log.v("ActivityManager", "default service binder = " + b);            }            IActivityManager am = asInterface(b);            if (false) {                Log.v("ActivityManager", "default service = " + am);            }            return am;        }    };}

这里可以看到ActivityManagerNative.getDefault()内部直接返回gDefault.get(),其中gDefault是Singleton泛型类

public abstract class Singleton {    private T mInstance;    protected abstract T create();    // get方法返回对应的泛型类,这里是IActivityManager    public final T get() {        synchronized (this) {            if (mInstance == null) {                mInstance = create();            }            return mInstance;        }    }}

揭开gDefault.get()的面纱,我们知道其中在Singleton中维护了一个mInstance变量,并且gDefault.get()方法最终返回的就是mInstance对应的泛型类实例,也就是说通过ContextImpl#startActivity方法启动activity的时候,最终是通过mInstance也就是IActivityManager来启动的,其实就是ActivityManagerService,所以我们要做的就是通过反射对mInstance重新赋值,将我们自己的代理类赋值给mInstance,然后在代理类中根据系统获取的IActivityManager在执行操作,所以这里我们就可以在对应的操作前后,进行一些记录或者关键log的打印.
好了,废话不多说,先看代码吧

  • 创建一个代理类,需要实现InvocationHandler接口,用来代理IActivityManager的操作
public class AMSProxy implements InvocationHandler {    private static final String TAG = "HookAMS";    private Object iActivityManager;    public AMSProxy(Object iActivityManager) {        this.iActivityManager = iActivityManager;    }    @Override    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {        Log.d(TAG, "method name is :" + method.getName() + " args length is :" + args.length + "   args is :" + args);        if ("startActivity".equals(method.getName())) {            // 第三个参数是intent            Intent intent = (Intent)args[2];            Log.d(TAG, "method name is :"+ method.getName()+"   intent is :"+intent+"   extradata is :"+intent.getStringExtra("DATA"));        }        return method.invoke(iActivityManager, args);    }}
  • 开始hook AMS
Class activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");Field gDefaultField = activityManagerNativeClazz.getDeclaredField("gDefault");gDefaultField.setAccessible(true);Object gDefault = gDefaultField.get(null); // 获取gDefault实例,由于是静态类型的属性,所以这里直接传递null参数// 下面通过反射执行gDefault.get();操作,最终返回IActivityManager,也就是ActivityManagerService的实例Class singleTonClazz = Class.forName("android.util.Singleton");Field mInstanceField = singleTonClazz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);Object iActivityManager = mInstanceField.get(gDefault);Class iActivityManagerClazz = Class.forName("android.app.IActivityManager");// 指定被代理对象的类加载器// 指定被代理对象所实现的接口,这里就是代理IActivityManager// 表示这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上Object myProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivityManagerClazz}, new AMSProxy(iActivityManager));mInstanceField.set(gDefault,myProxy);

此时运行我们的程序,启动activity,就可以看到,每次在启动activity之前都会打印相关的参数,其实不只是启动activity,启动service也可以,只要最终是通过ActivityManagerNative来操作的都可以拦截。

##Hook AMS##
有了上面的基础,Hook AMS就可以照猫画虎了,其实主要是要找到一个HOOK点,一般来说 静态变量和静态方法是很好的点,因为反射的时候,我们不用构造对象来获取。好了进入正轨吧,在Android中所有的关于PMS的操作,其实我们都是通过PackageManager操作的,那么就从PackageManager的获取开始分析吧:
###ContextImpl#getPackageManager

@Overridepublic PackageManager getPackageManager() {        if (mPackageManager != null) {            return mPackageManager;        }        // 通过ActivityThread获取IPackageManager,bingo,ActivityThread#getPackageManager是一个静态方法        IPackageManager pm = ActivityThread.getPackageManager();        if (pm != null) {            // 这里我们还需要替换pm为自己的代理            return (mPackageManager = new ApplicationPackageManager(this, pm));        }        return null;}

###ApplicationPackageManager

final class ApplicationPackageManager extends PackageManager {      ApplicationPackageManager(ContextImpl context,                              IPackageManager pm) {        mContext = context;        mPM = pm;    }}

###ActivityThread#getPackageManager###

public static IPackageManager getPackageManager() {        if (sPackageManager != null) {            return sPackageManager;        }        IBinder b = ServiceManager.getService("package");        sPackageManager = IPackageManager.Stub.asInterface(b);        return sPackageManager;}

###ActivityThread#currentActivityThread###

public static ActivityThread currentActivityThread() {        return sCurrentActivityThread;}

currentActivityThread也是一个静态方法,所以可以通过反射直接获取ActivityThread实例
可以看到,上面最终返回的是sPackageManager实例,所以这里hook点也一目了然,所以我们只需要按照下面步骤进行即可:

  1. 获取ActivityThread类中sPackageManager静态属性的值
  2. 创建一个代理,然后将sPackageManager传递进入代理
  3. 最后重新为当前的ActivityThread对象设置sPackageManager,此时设置为我们的代理类即可
  4. 替换ApplicationPackageManager中的mPM对象,此时设置为我们的代理类即可
// 1.获取ActivityThread类中sPackageManager静态属性的值Class activityThreadClazz = Class.forName("android.app.ActivityThread");// 1.1 获取当前ActivityThread实例Method currentActivityThreadMethod = activityThreadClazz.getDeclaredMethod("currentActivityThread");Object activityThreadObj = currentActivityThreadMethod.invoke(null);// 1.2 获取sPackageManager静态属性的值Field sPackageManagerField = activityThreadClazz.getDeclaredField("sPackageManager");sPackageManagerField.setAccessible(true);Object sPackageManagerObj = sPackageManagerField.get(activityThreadObj);// 2. 创建一个代理,然后将sPackageManager传递进入代理Class iPackageManagerClazz = Class.forName("android.content.pm.IPackageManager");Object pmsProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{iPackageManagerClazz},new PMSProxy(sPackageManagerObj));// 3. 重新为当前的ActivityThread对象设置sPackageManagersPackageManagerField.set(activityThreadObj,pmsProxy);// 4. 替换ApplicationPackageManager中的mPM对象PackageManager packageManager = getPackageManager();Class applicationPackageManagerClazz = Class.forName("android.app.ApplicationPackageManager");Field mPMField = applicationPackageManagerClazz.getDeclaredField("mPM");mPMField.setAccessible(true);mPMField.set(packageManager,pmsProxy);

此时如果我们使用PMS的API调用,就会先被我们的代理拦截,执行自己的log打印,然后才做对应的操作需要注意的是,我们hook AMS和PMS的代码
,由于是需要针对整个应用的,所以必须越早执行越好,这里我把他放在自定义的Application的onCreate方法去执行

##hook点击事件
很多时候我们需要在不修改系统代码的情况下,对当前用户的操作进行记录和统计,比如用户点击的是哪个view,什么时间点击的等等,此时同样可以通过反射和代理来实现对应的功能,这个和Spring中的AOP的实现其实是一样的,下面我举一个Click button的栗子。

###点击事件分析###
平时为button设置点击事件是通过setOnClickListener来实现的

public void setOnClickListener(@Nullable OnClickListener l) {        if (!isClickable()) {            setClickable(true); // 设置当前view可以被点击        }        getListenerInfo().mOnClickListener = l;}ListenerInfo getListenerInfo() {        if (mListenerInfo != null) {            return mListenerInfo;        }        mListenerInfo = new ListenerInfo();        return mListenerInfo;}

最终点击事件是通过 方法执行的

public boolean performClick() {        final boolean result;        final ListenerInfo li = mListenerInfo;        if (li != null && li.mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);            li.mOnClickListener.onClick(this); // 点击事件操作            result = true;        } else {            result = false;        }        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        return result;}

可以看到这里其实是将我们设置的listener赋值给了mOnClickListener, 所以思路和上面的启动activity一样,我们需要获取系统已有的(其实就是我们设置的OnClickListener)mOnClickListener,然后使用自己的代理来实现该需求。mOnClickListener是ListenerInfo中的一个属性,其实在ListenerInfo中有很多 用户点击,长按等事件的监听

static class ListenerInfo {        protected OnFocusChangeListener mOnFocusChangeListener;        private ArrayList mOnLayoutChangeListeners;        protected OnScrollChangeListener mOnScrollChangeListener;        private CopyOnWriteArrayList mOnAttachStateChangeListeners;        public OnClickListener mOnClickListener;        protected OnLongClickListener mOnLongClickListener;        protected OnContextClickListener mOnContextClickListener;        protected OnCreateContextMenuListener mOnCreateContextMenuListener;        private OnKeyListener mOnKeyListener;        private OnTouchListener mOnTouchListener;        private OnHoverListener mOnHoverListener;        private OnGenericMotionListener mOnGenericMotionListener;        private OnDragListener mOnDragListener;        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;}

所以我们需要做的就是有如下几步:

  1. 通过反射获取ListenerInfo对象
  2. 通过ListenerInfo获取mOnClickListener对象
  3. 创建代理类,传入onClickListener对象
  4. 重新设置系统的mOnClickListener为我们自己的代理类

###hook单个View的点击事件###
有了上面的分析和基础,下面hook单个view的点击事件就是顺理成章的事情了。

private  void hookOnClick(T view) {        try {            // 通过反射获取ListenerInfo对象            Class viewClazz = Class.forName("android.view.View");            Method getListenerInfoMethod = viewClazz.getDeclaredMethod("getListenerInfo");            getListenerInfoMethod.setAccessible(true);            Object listenerInfo = getListenerInfoMethod.invoke(view);    // 通过ListenerInfo获取mOnClickListener对象            Class mClassListenerInfo = Class.forName("android.view.View$ListenerInfo");            Class listenerInfoClazz = Class.forName("android.view.View$ListenerInfo");            Field mOnClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");            mOnClickListenerField.setAccessible(true);            View.OnClickListener onClickListener =  (View.OnClickListener)mOnClickListenerField.get(listenerInfo);                        if (onClickListener == null) { // 若onClickListener == null,表明当前view没有设置点击事件                return;            }            // 创建代理类,传入onClickListener对象            OnClickListenerProxy clickListenerProxy = new OnClickListenerProxy(onClickListener);            // 重新设置系统的mOnClickListener为我们自己的代理类            mOnClickListenerField.set(listenerInfo,clickListenerProxy);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (NoSuchFieldException e) {            e.printStackTrace();        }}

###hook整个activity的点击事件###
以上方法是对于单个view的点击事件代理的,那么对于一个activity来讲,我们需要按下面步骤进行处理

  1. 创建一个BaseActivity,使我们的activity继承自它
  2. 遍历当前activity中的所有子元素,对于每一个view执行hookOnClick方法,此时如果当前view设置了OnClickListener,那么就会按照我们的代理类来执行获取点击操作,否则不做任何操作
  3. 提供获取HookViewClick的接口
  4. 在BaseActivity的onWindowFocusChanged方法中,hook当前activity所有view的点击事件

####获取Activity的所有View####

/** * @note 获取该activity所有view * @author liuh * */private List getAllChildViews(Activity activity) {        View view = activity.getWindow().getDecorView();        return getAllChildViews(view);}private List getAllChildViews(View view) {        List allchildren = new ArrayList();        if (view instanceof ViewGroup) {            ViewGroup vp = (ViewGroup) view;            for (int i = 0; i < vp.getChildCount(); i++) {                View viewchild = vp.getChildAt(i);                allchildren.add(viewchild);                allchildren.addAll(getAllChildViews(viewchild));            }        }        return allchildren;}

####遍历所有View####

// 遍历所有的Viewpublic void hookViews(Activity activity) {        List views = getAllChildViews(activity);        for (View view : views) {            hookOnClick(view);        }}

####提供获取HookViewClick的接口####

public static HookViewClick getInstance() {       if (mHookViewClick == null) {           synchronized (HookViewClick.class) {                if (mHookViewClick == null) {                    mHookViewClick = new HookViewClick();                }           }       }        return mHookViewClick;}

####hook当前activity所有view的点击事件####

public class BaseActivity extends AppCompatActivity {    @Override    public void onWindowFocusChanged(boolean hasFocus) {        super.onWindowFocusChanged(hasFocus);        HookViewClick.getInstance().hookViews(this);    }}

好了,到此为止,我们如果需要搜集用户的点击事件相关参数,只需要继承我们的BaseActivity即可,而不需要对当前的代码做过多的修改。

源代码下载

欢 迎 关 注 我 的 公 众 号 “编 程 大 全”

专注技术分享,包括Java,python,AI人工智能,Android分享,不定期更新学习视频

更多相关文章

  1. 安装Android(安卓)Studio的最新最简最详细安装教程
  2. Android获取数据时 浮点型整数位数值(超8位)过大导致科学计数法
  3. android通过指定目录获取该目录下所有类(反编译)
  4. 基于android的网络音乐播放器-网络音乐的搜索和展示(五)
  5. android 新闻图片加载,缓存处理
  6. Android(安卓)图片获取及上传
  7. Android用户权限之记录是否调起弹窗
  8. android 的外部存储的挂载的理解
  9. Android创建应用快捷方式(ShortCut)的有效方式

随机推荐

  1. Android--Toast 两个 Crash
  2. Widget开发中遇到的坑
  3. android 系统 makefile文件(Android.mk)组
  4. Android(安卓)新浪微博XAuth方式授权与发
  5. [PX3][Android7.1] 调试笔记 去除USB权限
  6. android之绘图工具类详解
  7. Android(安卓)Studio 项目基本结构
  8. Android(安卓)Looper And Hander 机制剖
  9. Android之使用PopupWindow使用和总结
  10. vitamio简介.java