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