去广告APP学习笔记2——APP的功能实现

原创
2015/05/17 22:16
阅读数 297

#去广告APP学习笔记2 ###By 月泽

PrefsFragment模块中的刷新方法refresh()调用了CheckBoxAdapter,CheckBoxAdapter里实现了对去广告操作点击事件的响应。

在CheckBoxAdapter中,url过滤的点击事件响应为:

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();
                    }
                });

其中

pref = PreferenceManager.getDefaultSharedPreferences(mContext);

所以选中url过滤的Checkbox过后,会改变PrefsFragment中的SharedPreference

选中Checkbox,点击Dialog中的Button确认:

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();

确认后关闭对话框

到这里,视图上的流程就结束了,接下来是功能上的流程

##去广告功能的实现过程

这里要先讲zygote

###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);
    }
}
展开阅读全文
打赏
0
0 收藏
分享

作者的其它热门文章

加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部