在系统层实现三方应用去广告2
在上一篇文章中使用修改应用传出的intent来实现三方应用去除开屏广告的操作,可能会有应用资源加载不全导致crash或数据量过大的时候可能会导致性能问题,所以根据千里马framework上的另一种修改方式去进行了一个修改,在后期可以更好的进行新应用的添加,由于这套逻辑是需要根据单项目、单应用进行适配的,它在不同项目的适配上来说相比上一个就不是很好了,但是这套逻辑胜在简便,在xml文件没有写错的情况下,一般不会出什么问题。
修改思路:
在开屏广告中一般会有一个跳过按钮可以在系统中获取当前应用传过来的包名类名进行判断来确定是否是目标界面,在确定是目标界面后在跳过按钮的位置模拟一个点击事件来进行跳转。
大致流程:
数据的存储和判断与上一篇文章相同在系统层实现三方应用去广告将数据从xml文件取出并处理后存入ConcurrentHashMap「Map中存储了目标应用的类名(key)以及跳转按钮的坐标数组(value)」,当拿到资源map且传入的componentName不为空时,开始遍历map并判断传入的icomponentName包名类名是否存在于map中,若存在,返回坐标数组,若不存在,返回空
在开始主要逻辑编写前需要获取应用的包名类名以及坐标位置
- 包名类名获取
(这里是我平时获取包名类名的方法)打开USB调试后执行adb shell dumpsys window | grep “mCurr” - 坐标位置的获取
这里我是直接在dispatchTouchEvent方法中埋log获取的坐标,当然,也可以用其他的方式去获取这个坐标,并不是只有这一种方法。
public boolean dispatchTouchEvent(MotionEvent ev) {
//获取坐标
if (ev.getAction() == MotionEvent.ACTION_DOWN){
float x = ev.getX();
float y = ev.getY();
Log.i("test4","X轴坐标为:" + x + " Y轴坐标为:" +y);
}
//end
...
}
在xml中制作数据
与上一篇文章一样,数据还是存在framework/base/core/res/res/values/arrays.xml中并且在symbols.xml里面定义一下
<string-array name="infomercual_xy">
<item>com.android.contacts.activities.PeopleActivity/415,658</item>
<item>a1/b/c</item>
<item>a2/b/c</item>
<item>a3/b/c</item>
<item>a4/b/c</item>
<item>a5/b/c</item>
<item>a6/b/c</item>
<item>a7/b/c</item>
<item>a8/b/c</item>
<item>a9/b/c</item>
<item>a10/b/c</item>
<item>a11/b/c</item>
<item>a12/b/c</item>
<item>a13/b/c</item>
<item>a14/b/c</item>
<item>a15/b/c</item>
<item>a16/b/c</item>
</string-array>
在framework/base/core下的Activity.java中新增如下方法
这里主要工作还是去进行获取数据并对获取到的数据与传入的componentName进行判断,如果符合则返回对应的坐标数组
/**
* 复用修改intent进行跳转的查询逻辑,对比应用包名后输出所需坐标位置
*/
private String[] InfomercialXandY(ComponentName componentName){
//用于存储xml文件中获取到的类名以及坐标位置
ConcurrentHashMap<String,String> infomercualXY = new ConcurrentHashMap<>();
//获取资源并确认其是否存在
int resId = Resources.getSystem().getIdentifier("infomercual_xy","array","android");
if (resId != 0){
//将arrays.xml中package_name中的类名以及坐标数据存入typedArray中
TypedArray overalls = Resources.getSystem().obtainTypedArray(resId);
//遍历TypedArray并拆分数据
for (int i = 0;i<overalls.length();i++){
String[] appXY = overalls.getString(i).split("/");
infomercualXY.put(appXY[0],appXY[1]);
}
//回收 TypedArray 避免内存泄漏
overalls.recycle();
Log.i("test4","填充完毕");
}else {
Log.i("test4","未获取到相关xml");
}
//当数据填充完毕并且传入的componentName不为空时返回对应的坐标数组
if (componentName != null && resId != 0){
for (String name:infomercualXY.keySet()){
if (name.equals(componentName.getClassName())){
Log.i("test4","填充完毕,返回坐标");
return new String[]{infomercualXY.get(name).split(",")[0],infomercualXY.get(name).split(",")[1]};
}
}
}
return null;
}
编写模拟按下事件的主要代码
通过获取按下时打印的堆栈确认在进行点击操作的时候主要是调用了Activity.java中的dispatchTouchEvent方法,点击事件主要分了一个按下和抬起,这里分别对这两个事件进行模拟并且在两个事件之间加入一点点延时来模拟用户的点击
private void injectClick (int x , int y){
/*
按下事件
MotionEvent.obtain(事件开始的时间,事件发生的时间,action/事件类型,x轴坐标,y轴坐标,metaState为0:没有任何按键状态被激活0)
android.os.SystemClock.uptimeMillis()返回系统自启动以来的毫秒数
MotionEvent.ACTION_DOWN:触摸事件-按下
*/
MotionEvent dowmMotion = MotionEvent.obtain(android.os.SystemClock.uptimeMillis(),android.os.SystemClock.uptimeMillis(),MotionEvent.ACTION_DOWN,x,y,0);
//注入按下事件
dispatchTouchEvent(dowmMotion);
Log.i("test4","按下事件注入完成");
//设置抬起事件,逻辑同上
MotionEvent upMotion = MotionEvent.obtain(android.os.SystemClock.uptimeMillis(),android.os.SystemClock.uptimeMillis(),MotionEvent.ACTION_UP,x,y,0);
//延时后注入抬起事件抬起
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
dispatchTouchEvent(upMotion);
Log.i("test4","抬起事件注入完成");
}
},100);
}
调用上述代码实现模拟点击
这里在获取当前应用的包名类名后,确认有应用包名类名传入且InfomercialXandY方法返回值不为空时执行injectClick方法(方法参数为InfomercialXandY的返回值)
@CallSuper
protected void onResume() {
...
//跳过按钮模拟点击
ComponentName srcCom = new ComponentName("com.android.contacts","com.android.contacts.activities.PeopleActivity");
if (srcCom.equals(getComponentName())&&InfomercialXandY(srcCom) !=null){
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
injectClick(Integer.parseInt(InfomercialXandY(srcCom)[0]),Integer.parseInt(InfomercialXandY(srcCom)[1]));
Log.i("test4","成功跳过");
}
//由于每个应用跳过按钮出现的时间不同,这里的延时需要根据实际情况调整,或对上述代码进行调整以动态获取每个应用的延时
},200);
}
//end
}
延时这里可以进行动态调整,例如:在xml文件中再插入一组数据由当前的*「包名类名/X轴坐标,Y轴坐标」变为「包名类名/X轴坐标,Y轴坐标,延时毫秒值」,并在延时位置调用Integer.parseInt(InfomercialXandY(srcCom)[2]*
存在的缺陷
- 与上一个条广告的方式一样,在数据量较大到时候,由于所有的遍历均扔在主线程,可能会导致卡顿甚至anr,由于在本文中,InfomercialXandY被调了三次(如果动态设置延时会调四次),比上一个更容易出现性能问题。