#去广告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); } }