Android上hook AMS和PMS
好吧,我承认,其实这一篇文章,主要使用到的就是动态代理,但是个人觉得还是有很大意义的,比如说可以降低代码耦合度,如果想在用户的某一类操作都要打印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点也一目了然,所以我们只需要按照下面步骤进行即可:
- 获取ActivityThread类中sPackageManager静态属性的值
- 创建一个代理,然后将sPackageManager传递进入代理
- 最后重新为当前的ActivityThread对象设置sPackageManager,此时设置为我们的代理类即可
- 替换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;}
所以我们需要做的就是有如下几步:
- 通过反射获取ListenerInfo对象
- 通过ListenerInfo获取mOnClickListener对象
- 创建代理类,传入onClickListener对象
- 重新设置系统的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来讲,我们需要按下面步骤进行处理
- 创建一个BaseActivity,使我们的activity继承自它
- 遍历当前activity中的所有子元素,对于每一个view执行hookOnClick方法,此时如果当前view设置了OnClickListener,那么就会按照我们的代理类来执行获取点击操作,否则不做任何操作
- 提供获取HookViewClick的接口
- 在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分享,不定期更新学习视频
更多相关文章
- 安装Android(安卓)Studio的最新最简最详细安装教程
- Android获取数据时 浮点型整数位数值(超8位)过大导致科学计数法
- android通过指定目录获取该目录下所有类(反编译)
- 基于android的网络音乐播放器-网络音乐的搜索和展示(五)
- android 新闻图片加载,缓存处理
- Android(安卓)图片获取及上传
- Android用户权限之记录是否调起弹窗
- android 的外部存储的挂载的理解
- Android创建应用快捷方式(ShortCut)的有效方式