好吧,我承认,其实这一篇文章,主要使用到的就是动态代理,但是个人觉得还是有很大意义的,比如说可以降低代码耦合度,如果想在用户的某一类操作都要打印log获取当前参数,或者是记录用户的点击事件,点击时间等,那么此时在现有代码的基础上每次在点击事件中做处理,肯定是可以的,但是这样,我们要修改多少代码,其实此时,我们就可以完全使用代理来实现类似的功能。说道这里了,就先来看看,什么是代理吧
静态代理
比如,现在都有海外代购,那么这些代购就可以称之为代理,我们不用和真正卖家沟通,只需要借助代理去实现就ok了,但是添加没有免费的午餐,作为代理工作人员,一定是要从中获利的,下面我们就来模拟这一过程。
定义购买接口
/**
* BuySomething接口用来购买东西的,实现类和代理类需要实现它
* @author liuhang
*
*/
public interface BuySomething {
public void buyGood(Good good);
}
添加购买实现类
/**
* 购买买东西的实现类
* @author liuhang
*
*/
public class BuySomethingImpl implements BuySomething {
@Override
public 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$
*/
@Override
public void buyGood(Good good) {
System.out.println("作为BuyProxy, 需要给我打折.....");
good.setPrice(good.getPrice() - 30);
buy.buyGood(good);
}
public static void main(String[] args) {
// 将需要购买的东西放到集合中
List<Good> 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<Good> goodList;
public DynamicProxy(BuySomething mBuySomething, List<Good> goodList) {
super();
this.mBuySomething = mBuySomething;
this.goodList = goodList;
}
@Override
public 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<Good> 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();
}
@Override
public 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<Good> 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<IActivityManager> gDefault = new Singleton<IActivityManager>() {
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<T> {
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
@Override
public 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对象设置sPackageManager
sPackageManagerField.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<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
private CopyOnWriteArrayList<OnAttachStateChangeListener> 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 <T extends View> 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<View> getAllChildViews(Activity activity) {
View view = activity.getWindow().getDecorView();
return getAllChildViews(view);
}
private List<View> getAllChildViews(View view) {
List<View> allchildren = new ArrayList<View>();
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
// 遍历所有的View
public void hookViews(Activity activity) {
List<View> 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即可,而不需要对当前的代码做过多的修改。