Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 第三方應用 廣告攔截功能實現

Android 第三方應用 廣告攔截功能實現

編輯:關於Android編程

前段時間,公司制造的機器裡應用裝有不良廣告,嚴重影響了兒童客戶使用者的思想健康,導致被人投訴。於是乎,就有了想研發一款類似於360廣告屏蔽的應用的念頭。嗯,事情就是這樣,現在切入主題。

目前市場上有很多安全軟件,它們攔截第三方應用廣告的方式都不一樣,比如說有 以so 注入方式來攔截彈出廣告
現在我們來看下這種方式的詳細情況:

要做到攔截,首先我們得知道廣告是怎麼出來的,原來第三方應用大部分是以加入廣告jar形式加入廣告插件,然後在AndroidManifest中聲明廣告service或者在程序中執行廣告Api,廣告插件再通過Http請求去加載廣告。在java中,有四種訪問網絡的接口,如apache的http庫(如下介紹),這幾種方式首先都會通過getaddrinfo函數獲取域名地址,然後通過connect函數連接到服務器讀取廣告信息。

WebView(源碼文件在frameworks/base/core/java/android/webkit/WebView.java)。通過WebView類的void loadUrl(String url)、void postUrl(String url, byte[] postData)、void loadData(String data, String mimeType, String encoding)、void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)、void evaluateJavascript(String script, ValueCallback resultCallback)等加載網頁。 apache-http(源碼目錄在external/apache-http/ , HttpGet 和 HttpPost類)。通過external/apache-http/src/org/apache/http/impl/client/DefaultRequestDirector.java中的DefaultRequestDirector類的HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context)方法執行訪問的網絡的動作。 okhttp(源碼目錄在external/okhttp/)。通過external/okhttp/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java中的HttpEngine類的private void connect(Request request) throws IOException方法連接網絡。 URL(源碼在libcore/luni/src/main/java/java/net/URL.java)。通過libcore/luni/src/main/java/java/net/URL.java中的URL類的URLConnection openConnection() throws IOException方法和URLConnection openConnection(Proxy proxy) throws IOException方法連接網絡。

然後再來說說動態庫注入,具體什麼是動態庫注入,以及如何注入,網上有很多文章,這裡就不介紹。動態庫注入攔截呢,主要是攔截getaddrinfo,根據條件返回錯誤來攔截網絡請求,達到攔截作用。不過需要注意一點就是攔截之前要確定你所攔截的動態庫是否是你需要攔截的庫?例如A程序調用了動態庫BO和CO,而BO和CO都調用了connect函數,此時需要攔截BO的請求,需要注入到BO動態庫並修改GOT表,而不是注入到CO中。

攔截HTTP方式廣告在多數廣告包中,應用程序首先會通過apache的http庫或JDK中的http方法先將廣告數據下載過來,然後通過WebView顯示。這種方式通過注入攔截進程的/system/lib/libjavacore.so可實現廣告地址攔截。

攔截WebView方式廣告廣告插件也可以直接通過WebView加載URL,通過分析WebView加載流程可知它的網絡處理過程均交給libchromium_net庫來完成。因此通過注入libjavacore.so是無法實現攔截,而是需要注入到/system/lib/libchromium_net.so。

通過這種方式已經完全能夠攔截掉第三方APP廣告,但存在一些問題:

1.廣告商可以通過JNI方式調用系統getaddrinfo與connect實現自己的解析與連接過程的動態庫,從而跳過libjavacore.so導致攔截無效。
2.攔截WebView方式廣告雖然能夠不顯示廣告,但通常仍然會有浮動框顯示”網頁無法打開”,從而影響美觀。
3.最重要的是我們機器是沒有root權限的!!

第三個問題直接導致了放棄了這種注入做法。
來來去去一段時間後,目前是采用android 系統本地掃描第三方應用廣告形式。具體怎麼做,請往下看!

如果對這種方式不了解的話,建議先看下這篇 Android系統掃描帶廣告應用的做法。

所以具體廣告插件掃描方案是匹配包名+類名形式的:
1.掃描本地所有第三方應用,列出一個應用中的所有類,將包名+類名方式與廣告插件特征庫進行匹配
2.將匹配出來的應用所帶廣告特征,通過系統提供傳入接口,將這些規則設置進去。(當然,系統代碼是需要改的,做了一些處理,主要是在上面介紹中的幾種訪問網絡方式上做了判斷處理)

這種方案的關鍵在於廣告特征庫的完善,廣告插件特征庫收集越全,掃描出來的廣告插件就可以越准確。所幸,公司有幾位大神,做過類似的事情,所以工作簡單了多些。

獲取第三方應用:

   /** 
     * 查詢機器內非本公司應用 
     */  
    public List getAllLocalInstalledApps() {  
        List apps = new ArrayList();  
        if(pManager == null){
            return apps;
        }
        //獲取所有應用  
        List paklist = pManager.getInstalledPackages(0);  
        for (int i = 0; i < paklist.size(); i++) {  
            PackageInfo pak = (PackageInfo) paklist.get(i);

           //屏蔽掉公司內部應用
           //...

           //判斷是否為非系統預裝的應用程序  
            if ((pak.applicationInfo.flags & pak.applicationInfo.FLAG_SYSTEM) <= 0) {  
                // customs applications 
                apps.add(pak);
            }  
        }  
        return apps;  
    }

獲取某個應用的廣告特征:

public static List getClassNameByDex(Context context,
            String packageName) {

        List datalist = new ArrayList();
        String path = null;
        try {
            path = context.getPackageManager().getApplicationInfo(packageName,
                    0).sourceDir;// 獲得某個程序的APK路徑
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        try {
            if(TextUtils.isEmpty(path)){
                return datalist;
            }
            DexFile dexFile = new DexFile(path);// get dex file of APK
            Enumeration entries = dexFile.entries();
            while (entries.hasMoreElements()) {// travel all classes
                String className = (String) entries.nextElement();
                String totalname = packageName + .+className;
                datalist.add(totalname);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return datalist;
    }

將應用中的所有類名與特征庫進行匹配:

for (PackageInfo info : infolsit) {
        if (info == null) {
            continue;
        }
        data = getClassNameByDex(context,info.packageName);
        if(data == null){
            Log.d(TAG,getAdFlagForLocalApp()  類名解析出錯+info.packageName);
            continue;
        }
        sgPgmap = new HashMap();
        for (String clsname : data) {
            for (ADSInfo adinfo : flaglist) {
                String flag = adinfo.getAdFlag();  //廣告樣本庫的某一標識 
                String adpg = adinfo.getAdName();  //廣告樣本庫的某一包名
                if (clsname.contains(adpg)) {  //匹配類名與廣告特征庫裡的匹配符,看是否包含關系
                    sgPgmap.put(flag,info.packageName);
                }
            }
        }
        if(sgPgmap.size() > 0){
            //AdsPgInfo  一個對應應用裡包含了多少個標識
            adspginfo = new AdsPgInfo(info.packageName, sgPgmap);
            pglist.add(adspginfo);
        }
    }

ps: 在匹配時,有一個很注意的點,有時候單單類名匹配不准,或者會漏掉某些廣告,所以應該加上包名,再去匹配特征庫裡的匹配符,這樣才能百無一漏。

在此舉例一個指智廣告的特征(特征顯示形式可自定義,只要符合自己的解析策略即可):

ads.banner.zhidian#指智廣告#com/adzhidian/#ad.zhidian3g.cn
ads.banner.zhidian 為該類型廣告標識,主要是為了匹配時應用對應標識的簡潔性,不用直接跟著一群特征到處跑。。 指智廣告 該廣告名稱 com/adzhidian/ 該廣告用來匹配應用中類名的匹配符,當應用中某一(包名+類名)包含該匹配符時,說明了該應用包含該廣告 ad.zhidian3g.cn 需要傳給系統的一個規則特征。

匹配出所有應用的所屬規則特征後,接下來需要傳給系統了,系統將滿足需求的幾個接口提供出來。這邊涉及到修改系統層代碼,我就主要講下實現思路,會貼出關鍵的幾個代碼。
實現思路:系統根據應用層傳入的應用包名以及規則,將其緩存,在webview或http處請求時,對其進行判斷處理。

添加某應用規則接口:

/**
 * add Adblock url of package pkgName
 */
 private boolean addAdblockUrlInner(String pkgName, String url) {
    synchronized (mAdblockEntries) {
      HashMap pkgEntry = mAdblockEntries.get(pkgName);
     if (pkgEntry == null) {
        pkgEntry = new HashMap();
        if (pkgEntry == null) {
            Slog.e(TAG, addAdblockUrl():new HashMap() fail!);
            return false;
        }
        mAdblockEntries.put(pkgName, pkgEntry);
     }
     UrlEntry entry = pkgEntry.get(url);
     if (entry == null) {
        pkgEntry.put(url, new UrlEntry(0, false));
      } else {
        entry.deleted = false;
      }
   }
   return true;
}

WebView類postUrl處判斷處理:

/**
 * Loads the given URL.
 *
 * @param url the URL of the resource to load
 */
   public void loadUrl(String url) {
        checkThread();
        if (!isAddressable(url)) {
           return;
        }
       if (DebugFlags.TRACE_API) Log.d(LOGTAG, loadUrl= + url);
       if(!isChromium && url.startsWith(file://)){
       Log.e(WebView.java, loadurl setLocalSWFMode);
       mProvider.setLocalSWFMode();
   }

 /**
  * Returns true if the url is not included by adblock service
  */
 private boolean isAddressable(String url) {
     boolean addressable = true;
     AdblockManager adblockManager = AdblockManager.getInstance();
     if (adblockManager != null) {
       String adblockUrl =  adblockManager.containedAdblockUrl(ActivityThread.currentPackageName(), url);
     if (adblockUrl != null) {
         addressable = false;
         adblockManager.increaseNumberOfTimes(ActivityThread.currentPackageName(), adblockUrl);
      }
      }
      return addressable;
  }

由於系統代碼這部分的改動並非是我改的,更深細節處的理論就不清楚了。
應用層的廣告特征庫為了可以持續更新,建議可以做成網絡更新方式。
據此,廣告攔截功能實現就完成了,可能會有瑕疵,不過持續優化中。 有大神如果有更好的攔截實現跟策略,請您麻煩私信我,讓我好好請教,非常感謝。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved