本文同步更新于旺仔的個人博客,訪問可能有點慢,多刷新幾次。
前面有篇文章已經介紹了如何創建Xposed模塊的文章了,這篇就讓我們來實現一個簡單的去除啟動廣告的功能吧。
起因
為什么要是要去掉微博國際版的開屏廣告呢,因為廣告煩人啊,而且我打開微博的時間也是偶爾才會打開的,每次一打開就能看到廣告,所以就想把這個開屏廣告給刪掉,奇怪的是,打開一次后,再關掉再打開是沒有廣告的,要隔一段時候才會出現廣告,這個原因會在下面解釋。
實踐
查找啟動Activity
首先我們先拿到微博國際版的apk,apk的版本是2.5.7-5。
然后拉到Android Studio里面,然后點擊AndroidManifest.xml文件,然后搜索android.intent.category.LAUNCHER,找到啟動的Activity,在這里我們找到Activity是com.weico.international.activity.LogoActivity
反編譯classes.dex
在這里所用到的反編譯工具都是可以在網上找到,工具為dex2jar和jd-gui,大家自行搜索下載。
找到了啟動的Activity后呢,我們就要將apk給反編譯,把里面的classes.dex文件提取出來,首先將微博國際版的apk后綴改為.zip,然后打開
然后將里面的兩個dex文件,拷貝出來,放到dex2jar目錄里面,然后拖兩個文件到dex2jar.bat上面,讓其轉換成jar文件
轉換過程
轉換完成之后會出現classes_dex2jar.jar和classes2_dex2jar.jar兩個文件
然后我們用jd-gui打開這兩個jar文件,然后找到LogoActivity
查找廣告
使用jd-gui打開LogoActivity之后,我們就要在里面查找廣告相關的內容了,我們搜索ad,最后找到loginOrGlance方法和openGDTAD方法。
通過下面代碼可以看出,真正顯示廣告的是openGDTAD方法,而loginOrGlance方法則是判斷當前啟動是否需要顯示廣告,這下找到源頭就好辦了。
private void loginOrGlance()
{
if (!Setting.getInstance().loadBoolean("first_open_guide"))
{
Setting.getInstance().saveBoolean("first_open_guide", true);
startActivityForResult(new Intent(this.me, GuideActivity.class), 1025);
return;
}
if (AccountsStore.getCurAccount() != null)
{
if (Setting.getInstance().loadInt("display_ad") == 1)
{
if (System.currentTimeMillis() - Setting.getInstance().loadLong("ad_display_time") > ProcessMonitor.repeatedTime)
{
openGDTAD();
return;
}
initMainTabActivity();
return;
}
initMainTabActivity();
return;
}
initGuestActivity();
}
// 加載廣告
private void openGDTAD()
{
getWindow().getDecorView().postDelayed(new Runnable()
{
public void run()
{
WIActions.startActivityWithAction(new Intent(LogoActivity.this.me, NewSplashActivity.class), Constant.Transaction.GROW_FADE);
LogoActivity.this.finish();
}
}
, 600L);
}
廣告間隔出現原因
前面我們說過啟動一次出現廣告后,要隔一段時候才會重新出現廣告,上面的代碼System.currentTimeMillis() - Setting.getInstance().loadLong("ad_display_time") > ProcessMonitor.repeatedTime,這里就是原因,當前時間跟上一次顯示廣告的時間相差超過ProcessMonitor.repeatedTime的值的時候,就會出現廣告。
我們來看一下repeatedTime的值是多少,進到ProcessMonitor類里面,值為1800000毫秒,也就是30分鐘才出現一次廣告,比起那些每次打開都會出現廣告的好多了,但是也阻擋不了我干掉廣告。
顯示廣告條件
我們來看loginOrGlance方法里面的廣告相關代碼
if (Setting.getInstance().loadInt("display_ad") == 1)
{
if (System.currentTimeMillis() - Setting.getInstance().loadLong("ad_display_time") > ProcessMonitor.repeatedTime)
{
openGDTAD();
return;
}
initMainTabActivity();
return;
}
initMainTabActivity();
return;
可以看出,首先先判斷display_ad的值是否為1,如果不為1,就直接調用啟動主界面的initMainTabActivity()方法。
如果為1,在繼續判斷上一次顯示廣告的時間ad_display_time是否超過30分鐘,如果超過就顯示,沒有超過就啟動主界面。
所以,顯示廣告有兩個條件
display_ad的值為1
上一次顯示廣告的時間和現在的時間相差30分鐘
Hook方法
這里我們來介紹一下Hook所用到的一下Xposed里面的方法
findAndHookMethod方法,其參數對應為加載的類(Class>) + 方法名 + 參數類型(根據所Hook方法的參數的類型,即有多少個寫多少個,加上.class) + XC_MethodHook回調接口。
XC_MethodHook中定義了回調方法:
beforeHookedMethod(MethodHookParam param):被hook方法調用前執行,調用param.setResult可以跳過被Hook的方法。
afterHookedMethod(MethodHookParam param) : 被Hook方法調用后執行,調用param.setResult更改被hook方法的執行結果。
通過上面分析,我們知道了所需要的Hook的兩個方法,這兩個值居然是存到本地SharedPreferences里面,那么我們修改就更容易了。
一個是Setting.getInstance().loadInt("display_ad"),既是Setting類里面的loadInt方法。
public int loadInt(String paramString)
{
return this.mSharedPreferences.getInt(paramString, -1);
}
一個是Setting.getInstance().loadLong("ad_display_time"),既是Setting類里面的loadLong方法。
public long loadLong(String paramString)
{
return this.mSharedPreferences.getLong(paramString, -1L);
}
驗證
首先我們來驗證一下我們上面的兩個條件是否正確,打開我們的Tutorial類,在handleLoadPackage方法里面實現我們的Hook。
我們先驗證30分鐘出現的條件,既然是要超過30分鐘,那么我們只需要將loadLong("ad_display_time")返回的值改為-1L,也就是afterHookedMethod(MethodHookParam param)方法里面修改,就可以實現每次啟動都能出現廣告界面了
public class Tutorial implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// 只對微博國際版進行操作
if (lpparam.packageName.equals("com.weico.international")) {
try {
// 獲取Setting類
Class> aClass = XposedHelpers.findClassIfExists("com.weico.international.activity.v4.Setting", lpparam.classLoader);
if (aClass == null) {
return;
}
// Hook方法
XposedHelpers.findAndHookMethod(aClass, "loadLong", String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String param1 = (String) param.args[0];
// 如果參數為ad_display_time的時候將返回值改為-1L
if (!TextUtils.isEmpty(param1) && param1.equals("ad_display_time")) {
Log.e("info", "com.weico.international---loadLong---ad_display_time");
param.setResult(-1L);
}
}
});
} catch (Throwable t) {
XposedBridge.log("Hook出錯" + t);
}
}
}
}
接著運行重啟手機,開機后,打開微博國際版。
可以看出,現在每次打開都會有廣告,證明我們的猜測是正確的。
去除廣告
我們只需要將Setting.getInstance().loadInt("display_ad")的返回值改為-1就能實現去除廣告的效果了,下面看代碼
public class Tutorial implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// 只對微博國際版進行操作
if (lpparam.packageName.equals("com.weico.international")) {
try {
Class> aClass = XposedHelpers.findClassIfExists("com.weico.international.activity.v4.Setting", lpparam.classLoader);
if (aClass == null) {
return;
}
// Hook loadInt方法
XposedHelpers.findAndHookMethod(aClass, "loadInt", String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String param1 = (String) param.args[0];
// 如果參數為display_ad的時候將返回值改為-1
if (!TextUtils.isEmpty(param1) && param1.equals("display_ad")) {
Log.e("info", "com.weico.international---loadInt---display_ad");
param.setResult(-1);
}
}
});
// Hook loadLong方法
XposedHelpers.findAndHookMethod(aClass, "loadLong", String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String param1 = (String) param.args[0];
// 如果參數為ad_display_time的時候將返回值改為當前時間
if (!TextUtils.isEmpty(param1) && param1.equals("ad_display_time")) {
Log.e("info", "com.weico.international---loadLong---ad_display_time");
param.setResult(System.currentTimeMillis());
}
}
});
} catch (Throwable t) {
XposedBridge.log("Hook出錯" + t);
}
}
}
}
接著運行重啟手機,開機后,打開微博國際版。
可以看出,再也沒有廣告了。
總結
這次使用Xposed實踐,來去除微博國際版的啟動廣告,可以說是收獲挺大的,Hook到所調用的方法,然后里面進行我們自己的操作,關鍵就是在于beforeHookedMethod和afterHookedMethod這兩個方法里面實現的操作。
Github
Github地址在此奉上:XposedRemoveAd,歡迎star