1. 程式人生 > >Android路由的實現

Android路由的實現

最近在做一個專案,因為有多個功能模組,所以遇到了一個困難:當Moudle A依賴Moudle B,Moudle B依賴Moudle C,Moudle C依賴MoudleD,Moudle D為殼App,但是當我們需要在Moudle B呼叫Moudle C的時候,跳轉不過去,因為找不到這個類,因此有了Android路由這個概念的提出,即我們可以在任意一個Moudel呼叫任意Moudel的Activity以及Services等元件。
瞭解了這個概念的時候,我還是有一點懵,因為完全沒有一點思路。後來看了兩篇相關的文章:

學習了大神的程式碼,我還是打算自己來寫一下!
我的想法是新建一個Moudle,然後所有的專案都依賴於這個Moudle,然後在Mooudle中掃描所有的類,得到類名,最後載入這個類,得到Class< ?>物件,後面我們載入的時候就可以直接調方法得到類了! 程式碼如下:



import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import dalvik.system.DexFile;

/**
 * Created by 魏興 on 2017/5/8.
 */

public class
ClassContainer extends AppCompatActivity {
private static final String TAG = "ClassContainer"; private static List<String> classesName = new ArrayList<>(); private static List<Class<?>> classes = new ArrayList<>(); /** * 掃描類,並儲存類名稱及物件 * @param
context * @param packageNames */
public static void scan(Context context,String[] packageNames) { try { String str = context.getPackageResourcePath(); DexFile df = new DexFile(context.getPackageResourcePath()); Enumeration<String> n=df.entries(); while(n.hasMoreElements()){ String className = n.nextElement(); for (String packageName:packageNames) { if (className.contains(packageName)) {//在當前所有可執行的類裡面查詢包含有該包名的所有類 // com.example.zhaoshuang.camera.MyVideoView$1 if(className.contains("$")){//記憶體中有多個類,原因未知,此處也排除了靜態內部類 int length = className.indexOf("$"); className = className.substring(0,length); } classesName.add(className); Class cls = Thread.currentThread().getContextClassLoader().loadClass(className); classes.add(cls); break; } } } } catch (IOException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 獲取類物件 * @param packageName * @return */ public static Class<?> getClass(String packageName){ try { for (int i=0;i<classesName.size();i++){ String name = classesName.get(i); if(name.contains(packageName)){ Log.d(TAG, "getClass: "+classes.get(i).getName()); return classes.get(i).newInstance().getClass(); // return Thread.currentThread().getContextClassLoader().loadClass("com.luckytry.hybrid.myapplication.controller.FormActivity"); } } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } }

這裡遇到了幾個坑,第一個就是類名於實際不一樣,比如com.example.zhaoshuang.camera.MyVideoView我只有一個,但是實際載入的時候載入了5次,後來才知道因為我的MyVideoView類中有多個匿名內部類,但是我在儲存類的時候,沒有儲存到期望的類。導致後面開啟Activity的時候,出了問題。

com.example.zhaoshuang.camera.MyVideoView$1
com.example.zhaoshuang.camera.MyVideoView$2
com.example.zhaoshuang.camera.MyVideoView$3
com.example.zhaoshuang.camera.MyVideoView$4
com.example.zhaoshuang.camera.MyVideoView$5

後來就在上面的程式碼中,修正了匿名內部類,直接儲存了普通類。該程式碼需要在Applicaition中執行,耗時約800毫秒(儲存了200個類,未儲存的類包括系統包及依賴庫的包沒有統計)。
然後當我們需要呼叫元件的時候,直接呼叫方法即可,程式碼如下:

//註冊
@Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
...
        ClassContainer.scan(this,new String[]{"com.luckytry.luckylibrary.MyAplication",             "com.luckytry.hybrid.myapplication","com.example.zhaoshuang.camera",
                "com.luckytry.luckylibrary"});

    }

//呼叫
@Override
    public void onClick(View v) {
        int i = v.getId();
       ...
        }else if(i == R.id.rl_iv_sumbit){
            Log.d(TAG, "onClick: 提交照片");

                Intent intent = new Intent(this, ClassContainer.getClass("controller.FormActivity"));
                startActivity(intent);
            finish();
        }
    }

測試了一下,木有問題。後來在優化的過程中又發現新的問題:就是我們的類名是從dex檔案中得到的。

/data/app/com.luckytry.hybrid.mainapplication-2.apk

隱患在我們的APK比較大的時候,方法超過了65536個的時候,dex需要分包(後來測試的過程中發現努比亞6.0的機子,以及三星7.0的機子拿到的dex檔案都和上面的dex檔案不一樣,程式碼沒有任何變化,但是dex檔名稱有變化——“/data/app/com.luckytry.hybrid.mainapplication-2/base.apk”,而且這個dex檔案沒有class檔案),也就是說目前這個方法不確定是否能有效的將全部Activity掃描出來。
這個時候,有兩個辦法解決:

  • 手動將所有的類名儲存下來
  • 不再儲存類名,當我們需要Class< ?>物件的時候,手動的給類名的全部字元,而不是部分,如下:
//以前
 Intent intent = new Intent(this, ClassContainer.getClass("controller.FormActivity"));
 startActivity(intent);
//現在
Intent intent = new Intent(this, ClassContainer.getClass("com.luckytry.hybrid.myapplication.controller.FormActivity"));
 startActivity(intent);

最後,我發現這個路由Moudle根本木有必要單獨新建,直接將ClassContainer類寫在Moudle A的工具類,甚至工具類都不用,一句程式碼就搞定了,越來越簡單了!

Module2Module相比較而言,該專案有一個缺點就是硬編碼,就是說我們將所有的class類名都寫在了程式碼中,這一點不應該;優點是程式碼非常簡單,維護及擴充套件的時候,自己比較清楚,不會遇到異常了自己抓瞎。

後續填坑:

上面提到硬編碼,於是我將繼續將程式碼做了一些優化,首先第一步是增加一個自定義註解:

/**
 * 路由器註解
 * Created by 魏興 on 2017/5/8.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAnnptation {
    String name();

}

接下來我們在需要用到的Activity(就是需要任意Moudle均能呼叫到這個Activity)增加我們剛剛自定義的路由註解,例如:

/**
 * 拍照
 * Created by 魏興 on 2017/4/17.
 */
@ClassAnnptation(name = "CaremaActivity")
public class CaremaActivity extends Activity {}

在完成了註解的新增以後,我們還需要註冊註解,意思就是將所有的註解新增到一個容器內,待後續呼叫,這個方法與之前的方法引數完全一致,不一樣的只是類名:

/**
     * class容器
     */
    private static Map<String,Class<?>> map = new HashMap<>();
/**
     * 掃描類,並新增到路由器
     * @param context
     * @param packageNames
     */
    public static void scan(Context context, String[] packageNames) {
        try {
            DexFile df = new DexFile(context.getPackageResourcePath());
            Enumeration<String> n=df.entries();
            while(n.hasMoreElements()){
                String className = n.nextElement();
                for (String packageName:packageNames) {
                    if (className.contains(packageName)) {//在當前所有可執行的類裡面查詢包含有該包名的所有類
//                        com.example.zhaoshuang.camera.MyVideoView$1
                        if(className.contains("$")){//記憶體中有多個類,原因未知,此處也排除了靜態內部類
                            int length = className.indexOf("$");
                            className = className.substring(0,length);
                        }

                        Class cls = Thread.currentThread().getContextClassLoader().loadClass(className);
                        addRouter(cls);
                        break;
                    }
                }
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

/**
     * 新增到路由器
     * @param cls
     */
    private static void addRouter(Class<?> cls){
        if(cls == null){
            return;
        }
        ClassAnnptation clsa =cls.getAnnotation(ClassAnnptation.class);
        if(clsa!=null){
            if(map.containsValue(cls))
                return;
            String name = clsa.name();
            if(!map.containsKey(name)){
                map.put(name,cls);
            }else{
                Class<?> ocls = map.get(name);
                throw  new IllegalArgumentException(cls.getName()+" 註解的key與"+ocls.getName()+"重複,請重新設定key");
            }

        }
/**
     * 根據key獲取到路由器容器的Class<?>
     * @param key
     * @return
     */
    public static Class<?> getRouter(String key){
        if(map.containsKey(key)){
            return map.get(key);
        }
        return null;
    }
    }

接下來我們獲取Class< ?>物件時就不需要直接傳人完整的類名或者部分包名+類名了,而是直接輸入我們設定的別名,註冊與呼叫如下:

//註冊註解越早越好,建議放在Application裡面
new Thread(){
            @Override
            public void run() {
                super.run();
                Router.scan(getApplicationContext(),new String[]{"com.luckytry.luckylibrary.MyAplication",
                        "com.luckytry.hybrid.myapplication","com.example.zhaoshuang.camera",
                        "com.luckytry.luckylibrary"});
            }
        }.start();

//呼叫方法
@Override
public void onClick(View v) {
    v.getId();
    switch (v.getId()) {
        case R.id.action_a://繪製按鈕
            Intent polygon = new Intent(this,Router.getRouter("PolygonActivity"));
            polygon.putExtra(ParameterUtil.belong,0);
            startActivity(polygon);

            break;
          }
}

如此貌似完美了,但是我的想法是將這部分設定為一個庫,可以供其它專案使用,而且需要實現編譯時註解,這樣就不用手動註解了,但是後來發現一個問題:

Created with Raphaël 2.1.0Moudle AMoudle BMoudle CMoudle D打包APP

我的所有Moudle是佇列的形式排列的A依賴B,B依賴C,C依賴D,D依賴APP,而一般情況下不會有這種情況。所以在這種情況我可以將所有的Class< ?>物件放在底部的Moudle A,這樣所有的Moudle都可以呼叫到需要被註解的。舉個栗子:本來正常情況下是Moudle A被Moudle B呼叫,而MoudleA不能呼叫Moudle B的東西,這個時候Moudle B也需要呼叫Moudle D裡面的功能,也是不能實現的,需要在打包App裡面建一個處理器,來處理這些呼叫關係。

看到這裡應該明白我們的設計的路由器的問題了,就是我們的路由有一定的侷限性,不能被所有的專案拿來使用。因此就算我通過編譯時註解或者是執行時註解將Class< ?>物件儲存下來了,卻苦於找不到儲存的地方。也許可以通過持久化儲存來解決,嗯,以後有時間再嘗試一下!到這裡大家應該和我一起對這個Android路由有了非常深刻的理解了,今後不管是使用人家的庫或者自己編寫方法來實現,都不再是難題了!
參考文章:

相關推薦

【轉】 Android路由實現

~~ 下載 bin 一個 setup 自己的路 rul 簡單 sync 本文轉自: http://blog.csdn.net/qibin0506/article/details/53373412 前幾個月有幸參加了CSDN組織的MDCC移動開發者大會, 一天下來我最大的收獲

Android路由實現

本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。 前幾個月有幸參加了CSDN組織的MDCC移動開發者大會, 一天下來我最大的收穫就是了解到了模組化開發, 回來之後我就一直在思考模組化的一些優點, 不說別的, 提供一種可插拔的開發方式

關於Android路由實現

ace uid space php andro spa pac .cn album http://pic.cnhubei.com/space.php?uid=1774&do=album&id=1349287http://pic.cnhubei.com/spa

Android路由實現

最近在做一個專案,因為有多個功能模組,所以遇到了一個困難:當Moudle A依賴Moudle B,Moudle B依賴Moudle C,Moudle C依賴MoudleD,Moudle D為殼App,但是當我們需要在Moudle B呼叫Moudle C的時候,跳

阿里ARouter路由實現Android模組化開發

概述 從 2016 年開始,模組化在 Android 社群越來越多的被提及。隨著移動平臺的不斷髮展,移動平臺上的軟體慢慢走向複雜化,體積也變得臃腫龐大,為了降低大型軟體複雜性和耦合度,同時也為了適應模組重用、多團隊並行開發測試等等需求,模組化在 Android

MRouter-Android路由簡單實現

路由的意義: 1. 模組間解耦,不能在程式碼中寫死Activity類名。 2. 動態配置業務需求,現在都是業務模組化開發了。 1. 註解 我們這次編寫的MRoute主要使用了編譯時註解技術,註解在我們日常使用的框架中都有體現。 執行

Android路由框架設計與實現

http://www.sixwolf.net/blog/2016/03/23/Android%E8%B7%AF%E7%94%B1%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1/

AngularJS路由實現單頁面跳轉

href vid 左邊欄 ref 按順序 -1 生活用品 func 為我 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <titl

用iptables做軟路由實現共享上網

iptables 軟路由 共享上網 我們平時使用的大多數家用路由器都是通過NAT(Network Address Translation,網絡地址轉換)功能實現共享上網的,iptables是linux內核裏整合的一個ip信息包過濾系統,使用iptables配置nat就可以實現和家用路由器一樣的上網效果。

Android實現陰影效果

sta 技術 wid 語言 mat floating 大表 ado sch 在Android L推出後,Google提出了全新的設計語言:材質設計。其中很重要的一點就是陰影效果的使用,你可以為每一個View設置一個elevation值,相當於除了x、y之外的z值,z值決定

gradle打包android實現外部導入簽名文件、多渠道打包、導入ant腳本)

gradle打包 tree clu download csdn ssp pes 基礎 net 近期一直在做android自己主動打包,之前已經完畢了用純命令行的形式打包、原生態ant腳本打包。和基於android的SDK的打包。而且實現了多渠道打包,後來同

Android 封裝實現各種樣式對話框

dismiss class demo position sdn 是否 ets override listener 先上圖 實現代碼 package com.jock.alertdialog; import android.app.Activity; impor

Android簡單實現將手機圖片上傳到server中

sdk etc mov 創建 ast bmi 以及 lena ews 在本例中。將會簡單的實現安卓手機將圖片上傳到server中。本例使用到了 server端:PHP+APACHE 客戶端:JAVA 先簡單實現一下server端的上傳並測試上傳

android實現毛筆效果(View 中畫圖)

方法 繪畫 object android中 validate 滑動 一個 lineto 效果 近期有一個項目設計一個APP實現通過觸摸屏實現毛筆寫字效果。傳統的繪畫板程序直接通過Path的moveTo和LineTo便可實現簡單的線條繪畫程序。然而要達到毛筆的筆鋒效果

android:Notification實現狀態欄的通知

generate file widget flash track click 短消息 img example 在使用手機時,當有未接來電或者新短消息時,手機會給出響應的提示信息,這些提示信息一般會顯示到手機屏幕的狀態欄上。 Android也提供了用於處理這些信息的

Android ViewPager實現多個圖片水平滾動

oncreate iss block 如果 del ner ide extends 大小 1.示意圖 2.實現分析 (1).xml配置 <!-- 配置container和pager的clipChildre

Android MarsDaemon實現進程及Service常駐

小米 target 輕量 sda 定義 win 同進程 service 自己的 前段時間。就討論過關於怎樣讓Service常駐於內存而不被殺死,最後的結論就是使用JNI實現守護進程,可是不得不說的是,在沒有改動系統源代碼的情況下,想真正實現殺不死服務,是一件非常難的

Android直播實現 Android端推流、播放

size input 準備 預覽 不必要 targe height 不出 oar 最近想實現一個Android直播,但是對於這方面的資料都比較零碎,一開始是打算用ffmpeg來實現編碼推流,在搜集資料期間,找到了幾個強大的開源庫,直接避免了jni的代碼,集成後只用少量的ja

Android實現線程異步小技巧

使用 msg xtend util rri wsh ride 執行 java 方式不止一種,這裏使用的是Timer類,創建一個定時器。我們經常需要獲得移動設備端口的顯示屏信息,但是onCreate()方法執行的時候,OnShow()方法不一定執行了,也就是說,在執行Oncr

Android快速實現上傳項目到Github

alt+ 紅色 選擇 index.php 就會 打開 ads 倉庫 http 本文為skylinelin原創,轉載請註明出處! 一、簡介 現在在網上瀏覽關於Git的文章,基本上都是使用命令行(Git Bash),命令行效率是很高的,但是有一定的復雜性,現在我們看如何用A