#去廣告APP學習筆記2 ###By 月澤java
PrefsFragment模塊中的刷新方法refresh()調用了CheckBoxAdapter,CheckBoxAdapter裏實現了對去廣告操做點擊事件的響應。linux
在CheckBoxAdapter中,url過濾的點擊事件響應爲:android
urlFilter.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CheckBox cb = (CheckBox) v; boolean value = cb.isChecked(); pref.edit() .putBoolean(key + "_url", value) .commit(); } });
其中web
pref = PreferenceManager.getDefaultSharedPreferences(mContext);
因此選中url過濾的Checkbox事後,會改變PrefsFragment中的SharedPreferenceapi
選中Checkbox,點擊Dialog中的Button確認:app
AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(mContext.getString(R.string.title_settings)) .setIcon(R.drawable.ic_launcher) .setView(checkBoxView) .setCancelable(false) .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dlg, int id) { dlg.dismiss(); } }) .show();
確認後關閉對話框ide
到這裏,視圖上的流程就結束了,接下來是功能上的流程函數
##去廣告功能的實現過程學習
這裏要先講zygoteui
###zygote
zygote的啓動由linux的祖先init啓動,在android中,應用程序的入口是ActivityThead中的main函數,而建立APP進程 靠的是zygote。在android中提到zygote,主要兩塊,一個是C/C++編寫的zygote,主要用來爲應用和SystemService fork 進程的。一個是java編寫的zygote接口,負責爲應用和service調用C/C++ zygote的接口執行fork,從而建立VM進程。
####zygote在android中主要有兩個做用: 1.創建運行時環境並啓動虛擬機
2.爲應用程序建立DVM進程
總結:
在android中SystemService的啓動是在Zygote進程建立好後進行的,而且由Zygote進程創建好DVM運行環境,加載ZygoteInit的main函數,最終調用Zygote的本地方法forkSystemServer,並執行linux的fork方法建立SystemServer進程。 應用程序的進程也是由Zygote建立的,在ActivityManagerService中的startProcessLocked中調用了Process.start()方法。並經過鏈接調用Zygote的native方法forkAndSpecialize,執行fork任務。 應用進程和服務進程位於不一樣的進程中,他們之間是經過IPC進行數據傳遞的。
###使用Xposed提供的hook技術去廣告
在assets建立xposed_init文件,指定XposedHook入口類
tw.fatminmin.xposed.minminguard.Main
Main.java實現IXposedHookLoadPackage接口的handleLoadPackage方法,使用XSharedPreferences獲取保存的數據, 在全部android.app.Activity類及其子類的onCreate方法後面使用XposedHelpers注入去廣告方法XC_MethodHook():
Class<?> activity = XposedHelpers.findClass("android.app.Activity", lpparam.classLoader); XposedBridge.hookAllMethods(activity, "onCreate", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context context = (Context) param.thisObject; if(pref.getBoolean(packageName, false)) { adNetwork(packageName, lpparam, false, context); appSpecific(packageName, lpparam); UrlFiltering.removeWebViewAds(packageName, lpparam, false); nameBasedBlocking(packageName, lpparam); } else { adNetwork(packageName, lpparam, true, context); } } });
其中,adNetwork(packageName, lpparam, false, context);和appSpecific(packageName, lpparam);爲 去除固定廣告商的廣告,會在hook住廣告代碼後插入調用Main.removeAdView方法
而UrlFiltering.removeWebViewAds(packageName, lpparam, false);的做用是找出不屬於已列出廣告商的廣告
首先找到可能含有廣告的類,在 public class UrlFiltering 中:
Class<?> adView = findClass("android.webkit.WebView", lpparam.classLoader);
對這個類裏的全部loadurl、loadData和loadDataWithBaseURL方法進行hook:
XposedBridge.hookAllMethods(adView, "loadUrl", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String url = (String) param.args[0]; adExist = urlFiltering(url, "", param, packageName, test); if(adExist && !test) { param.setResult(new Object()); } } }); XposedBridge.hookAllMethods(adView, "loadData", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String data = (String) param.args[0]; adExist = urlFiltering("", data, param, packageName, test); if(adExist && !test) { param.setResult(new Object()); } } }); XposedBridge.hookAllMethods(adView, "loadDataWithBaseURL", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String url = (String) param.args[0]; String data = (String) param.args[1]; adExist = urlFiltering(url, data, param, packageName, test); if(adExist && !test) { param.setResult(new Object()); } } });
經過以上代碼,hook了android.webkit.WebView類以及它的子類的loadUrl、loadData和loadDataWithBaseURL方法。 在這些方法被調用前,先執行了添加的代碼。在添加的代碼裏面,接收了傳遞給原方法的url參數,調用了urlFiltering() 來處理過濾
static private boolean urlFiltering(String url, String data, MethodHookParam param, String packageName, boolean test) { array = url.split("[/\\s):]"); for(String hostname : array) { hostname = hostname.trim(); if(hostname.contains(".") && hostname.length() > 5 && hostname.length() < 50) { if(Main.urls.contains(hostname)) { Util.log(packageName, "Detect Ads(url) with hostname: " + hostname + " in " + packageName); if(!test) { param.setResult(new Object()); Main.removeAdView((View) param.thisObject, packageName, false); return true; } break; } } }
urlFiltering()把接收到的url參數解析分段,並和initZygote方法中,從用modulePath下host/output_file裏解析出來 的urls進行對比,有相同的就調用Main.java中的removeAdView()方法,去除AdView。
initZygote方法中得到urls
在host/output_file收集了大量的已知url,在initZygote方法中得到:
public void initZygote(StartupParam startupParam) throws Throwable { pref = new XSharedPreferences(MY_PACKAGE_NAME); Util.pref = pref; MODULE_PATH = startupParam.modulePath; res = XModuleResources.createInstance(MODULE_PATH, null); byte[] array = XposedHelpers.assetAsByteArray(res, "host/output_file"); String decoded = new String(array); String[] sUrls = decoded.split("\n"); urls = new HashSet<String>(); for(String url : sUrls) { urls.add(url); } }
廣告View的去除 removeAdView()
調用removeAdView()時,把接收參數所在對象的View發送過去
Main.removeAdView((View) param.thisObject, packageName, false);
在removeAdView()中對View的高度設置爲0,就不顯示廣告了,若是被去除的View有父類,對它的父類也調用removeAdView()
public static void removeAdView(final View view, final String packageName, final boolean apiBased) { if(convertPixelsToDp(view.getHeight()) > 0 && convertPixelsToDp(view.getHeight()) <= 55) { LayoutParams params = view.getLayoutParams(); params.height = 0; view.setLayoutParams(params); } ViewTreeObserver observer= view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { float heightDp = convertPixelsToDp(view.getHeight()); if(heightDp <= 55) { LayoutParams params = view.getLayoutParams(); params.height = 0; view.setLayoutParams(params); } } }); if(view.getParent() != null && view.getParent() instanceof ViewGroup) { removeAdView((View)view.getParent(), packageName, apiBased); } }