談?wù)凙pp的統(tǒng)一跳轉(zhuǎn)和ARouter

作者:菜刀文
簡書:http://www.reibang.com/u/ccabae3e72f2

Ali-ARouter github/正緯

App中每次頁面跳轉(zhuǎn),都需要調(diào)用統(tǒng)一導(dǎo)航, 它用的非常頻繁, 有必要對它進行一下梳理. 讓他能用起來簡單方便, 同時能支持各種常用的跳轉(zhuǎn)業(yè)務(wù)場景.

一. Android跳轉(zhuǎn)遇到的問題

1.intent-filter跳轉(zhuǎn)不好管理

 Intent intent = new Intent();  
 intent.setAction(Intent.ACTION_SENDTO);  
 intent.setData(Uri.parse("smsto:10086"));  
 context.startActivity(intent);  

如果項目分多個Module, Activity需要在各自Module的AndroidManifest.xml中聲明,容易重復(fù),不好統(tǒng)一管理.

2.Activity class跳轉(zhuǎn)耦合性高

//通過設(shè)置目標class跳轉(zhuǎn) 
Intent intent = new Intent();  
intent.setClass(context,TargetActivity.class);  
context.startActivity(intent);  

A如果要跳轉(zhuǎn)到TargetActivity, A要引用到TargetActivity. 造成:

  1. 如果項目多個Module開發(fā),底層module不能跳轉(zhuǎn)到高層Activity
  1. 如果TargetActivity類名變化, 對應(yīng)調(diào)用方都需要改動

3. 混合開發(fā)時,H5/Weex跳轉(zhuǎn)新界面不方便

內(nèi)置H5要跳轉(zhuǎn) Native頁面, 通過JsBidge把目標信息傳過來.

兩種方式:

方式1: 直接提供目標Activity的 Action 跳過去.
方式2: Native維護一個<描述,Activity信息>的Map, H5傳過來Activiy的"描述", Native在Map中查到后,進行跳轉(zhuǎn).

方式1的問題:

一般H5會同時在"Android/ios"容器中, 所以最好的實踐是:H5做跳轉(zhuǎn)時不需要區(qū)分平臺和版本. 如果利用Action跳轉(zhuǎn),

1)Action命名要符合兩個平臺的規(guī)范
2)如果Native不支持目標Action,還需要做跳轉(zhuǎn)失敗后處理.

方式2的問題:

1)維護<描述,Activity信息>的列表麻煩事,需要單獨角色管理.
2)同樣存的"Activity信息"也有問題1,2中提到的問題

都有的問題:

處理跳轉(zhuǎn)的Bridge類,可能拿不到context,這需要拿Application的Context,大家都判斷略嫌麻煩.

4.跳轉(zhuǎn)到"未知頁面"的統(tǒng)一處理

比如2.0版本新加了"消息"功能,App1.0版本沒有.
此時1.0版本的App中,"H5/push" 嘗試打開"消息"頁面, 肯定是不支持的. 這時候有幾種策略:

  1. H5/Push能判斷Native支持頁面的能力,如果不支持,就不調(diào)用
  2. Native收到調(diào)用未知頁面, 不做任何動作.
  3. Native收到調(diào)用未知頁面, 提示這是新版功能,建議更新版本.

5. 業(yè)務(wù)降級/重定向

  1. 比如A/B測試:
    Native可以根據(jù)配置, 跳轉(zhuǎn)不同的實現(xiàn)頁面
  2. 業(yè)務(wù)降級:
    某個業(yè)務(wù)本來Native實現(xiàn), 降級為H5實現(xiàn), 這時候跳轉(zhuǎn)時切換到H5頁面.

6.統(tǒng)一加參

跳轉(zhuǎn)到目標頁面前,能統(tǒng)一加參數(shù).
實現(xiàn)比如打點, 添加通用參數(shù)操作.

7.外部調(diào)用的統(tǒng)一入口

考慮這種業(yè)務(wù)場景: App有 A,B,C三個頁面, 提供給外部調(diào)用.
這時候一般兩種實現(xiàn)方式:

方式1: A,B,C的Activity 在AndroidManifest.xml中export=true,并且設(shè)置 intent-filter
方式2: App設(shè)置一個統(tǒng)一的Router-Activity, 外部跳轉(zhuǎn)到A,B,C 都統(tǒng)一先統(tǒng)一到Router-Activity, 他在拉起A,B,C

方式1分析:

除非真的提供通用的功能(拍照/圖片處理/..)給外部調(diào)用, 否則export一個Activity是不必要也不安全的. 為了安全,App不會export大量的Activity. 這意味著通過這種機制, 外部能調(diào)用內(nèi)部的功能較少.

方式2分析:

優(yōu)點:

  1. 只暴露了一個Router-Activity. 安全和好管理.
  2. Router-Activity里面可以做一些調(diào)用者的安全校驗, 如果校驗通過可以運行跳轉(zhuǎn)App的全部頁面. 這樣給能外部調(diào)用app更多頁面的機會, 也兼顧了安全.

缺點:
外部跳轉(zhuǎn)需要一個Activity中轉(zhuǎn)一下,直觀上感覺效率低一些. 但是實際感覺基本沒有影響.

二. 明確需求

根據(jù)問題和業(yè)務(wù)場景, 我們的"統(tǒng)一跳轉(zhuǎn)"的需求也基本明確:

  1. "聲明/使用" 簡單.
  1. 適用多module開發(fā),避免直接依賴.
  2. 統(tǒng)一協(xié)議, 適用"H5/Weex/Native" 跳轉(zhuǎn) "Native", 對"Android/ios"兩個平臺協(xié)議應(yīng)該是一樣的.
  3. 有統(tǒng)一的外部調(diào)用入口
  4. 能對"不支持"的跳轉(zhuǎn)統(tǒng)一處理
  5. 支持跳轉(zhuǎn)前預(yù)處理
  6. 支持重定向

三.解決方案ARouter

ARouter
ARouter-github 很好的解決了上述問題.
下面是他的對應(yīng)的方案.

1.使用簡單:

  1. 每個Activity在類中自聲明,"代碼-路徑"對應(yīng)一目了然
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}
  1. 跳轉(zhuǎn)新頁面簡單,不需要知道目標ActivityContext,intent-filter,目標的Activity
ARouter.getInstance().build(path).with(bundle).navigation();   

2.頁面利于統(tǒng)一管理

所有頁面可以統(tǒng)一定義. 一目了然

String PAGE_MAIN = "/navigateTo/main";
String PAGE_H5 = "/navigateTo/h5";
String PAGE_WEEX = "/navigateTo/weex";
...

3.便于設(shè)置統(tǒng)一Activity承載外部跳轉(zhuǎn)

public class SchameFilterActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //安全/版本校驗
    ....
    
    Uri uri = getIntent().getData();
    ARouter.getInstance().build(uri).navigation();
    finish();
    }
}

4.處理"未知頁面"的跳轉(zhuǎn)結(jié)果

 ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
    @Override
    public void onFound(Postcard postcard) {
      ...
    }
    @Override
    public void onLost(Postcard postcard) {
        //可以處理,提示升級版本之類
    }
});

5. 自定義全局降級策略

// 實現(xiàn)DegradeService接口侯谁,并加上一個Path內(nèi)容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
  @Override
  public void onLost(Context context, Postcard postcard) {
    // do something.
  }

  @Override
  public void init(Context context) {

  }
}

6. 重寫跳轉(zhuǎn)URL實現(xiàn)重定向

// 實現(xiàn)PathReplaceService接口,并加上一個Path內(nèi)容任意的注解即可
@Route(path = "/xxx/xxx") // 必須標明注解
public class PathReplaceServiceImpl implements PathReplaceService {
    /**
     * For normal path.
     *
     * @param path raw path
     */
    String forString(String path) {
    return path;    // 按照一定的規(guī)則處理之后返回處理后的結(jié)果
    }

   /**
    * For uri type.
    *
    * @param uri raw uri
    */
   Uri forUri(Uri uri) {
    return url;    // 按照一定的規(guī)則處理之后返回處理后的結(jié)果
   }
}

技術(shù)分析

1.建立 Url-Activity 的對應(yīng)關(guān)系

ARouter最后是通過下面方式跳轉(zhuǎn)的.

//_ARouter.java  
Intent intent = new Intent(currentContext,postcard.getDestination());
intent.putExtras(postcard.getExtras());

所以要AROUTER需要維護一個 Path和Activity class的對應(yīng)關(guān)系.
他利用

  1. javapoet 在編譯時候生成類信息
  2. 初始化時,收集主創(chuàng)Path/Activity信息. 所有信息存在WareHouse中.
screenshot.png

2. 跳轉(zhuǎn)流程

screenshot.png

其他技術(shù)

1. 屬性設(shè)置在gradle.properties中

BUILDTOOLS_VERSION=25.0.0

使用:

compile "com.android.support:support-v4:${SUPPORT_LIB_VERSION}"   
buildToolsVersion BUILDTOOLS_VERSION

2.TreeMap

HashMap通過hashcode對其內(nèi)容進行快速查找,而 TreeMap中所有的元素都保持著某種固定的順序府寒,如果你需要得到一個有序的結(jié)果你就應(yīng)該使用TreeMap(HashMap中元素的排列順序是不固定的)

3.Instrumentation的使用

你可以將Instrumentation理解為一種沒有圖形界面
的株搔,具有啟動能力的,用于監(jiān)控其他類(用Target
Package聲明)的工具類纵隔。任何想成為Instrumentation的類必須繼承android.app.Instrumentation捌刮。

下面是這個類的解釋:

“Base class for implementing application instrumentation code. When running with instrumentation turned on, this class will be instantiated for you before any of the application code, allowing you to monitor all of the interaction the system has with the application. An Instrumentation implementation is described to the system through an AndroidManifest.xml's tag.“

4.volatile

Java多線程/并發(fā)09绅作、淺談volatile

  1. volatile重要工作是避免線程臟讀:當線程對volatile變量進行讀操作時蛾派,會先將2. 自己工作內(nèi)存中的變量置為無效,之后再通過主內(nèi)存拷貝新值到工作內(nèi)存中使用梭依。
  2. volatile解決的是變量在多個線程之間的可見性役拴,但不能完全保證數(shù)據(jù)的原子性钾埂。
    現(xiàn)在JVM經(jīng)過優(yōu)化,已不會出現(xiàn)liveness failure 姜性。所以沒事別用volatile部念。

5. CountDownLatch

CountDownLatch的一個非常典型的應(yīng)用場景是:有一個任務(wù)想要往下執(zhí)行,但必須要等到其他的任務(wù)執(zhí)行完畢后才可以繼續(xù)往下執(zhí)行儡炼。假如我們這個想要繼續(xù)往下執(zhí)行的任務(wù)調(diào)用一個CountDownLatch對象的await()方法查蓉,其他的任務(wù)執(zhí)行完自己的任務(wù)后調(diào)用同一個CountDownLatch對象上的countDown()方法豌研,這個調(diào)用await()方法的任務(wù)將一直阻塞等待,直到這個CountDownLatch對象的計數(shù)值減到0為止鬼佣。

6.獲取CPU個數(shù)

CPU_COUNT = Runtime.getRuntime().availableProcessors()

7.捕捉線程異常

// 捕獲多線程處理中的異常
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        ARouter.logger.info(Consts.TAG, "Running task appeared exception! Thread [" + thread.getName() + "], because [" + ex.getMessage() + "]");
       }
   }); 

8.javapoet

build  classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 

使用 annotationProcessor
dependencies {
    annotationProcessor project(':arouter-compiler')
}

9.Activity啟動

int flags = postcard.getFlags();
    if (-1 != flags) {
      intent.setFlags(flags);
    } else if (!(currentContext instanceof Activity)) {     
    // Non activity, need less one flag.
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} 

10. 訪問者模式

//設(shè)置WareHouse

public interface IRouteGroup {
    /**
     * Fill the atlas with routes in group.
     */
    void loadInto(Map<String, RouteMeta> atlas);
}

iGroupInstance.loadInto(Warehouse.routes);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坷随,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌温眉,老刑警劉巖类溢,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件露懒,死亡現(xiàn)場離奇詭異,居然都是意外死亡砂心,警方通過查閱死者的電腦和手機懈词,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辩诞,“玉大人坎弯,你說我怎么就攤上這事∫朐荩” “怎么了抠忘?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長外永。 經(jīng)常有香客問我崎脉,道長,這世上最難降的妖魔是什么囚灼? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任汪厨,我火速辦了婚禮织中,結(jié)果婚禮上狭吼,老公的妹妹穿的比我還像新娘。我一直安慰自己疲吸,他們只是感情好摘悴,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著孵运,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上探膊,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼雕憔。 笑死分瘦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的去团。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼取刃!你這毒婦竟也來了璧疗?” 一聲冷哼從身側(cè)響起漆魔,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后欠拾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘶卧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒙袍,到底是詐尸還是另有隱情消恍,我是刑警寧澤狠怨,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布记盒,位于F島的核電站孽鸡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏巷疼。R本人自食惡果不足惜嚼沿,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一擅编、第九天 我趴在偏房一處隱蔽的房頂上張望谭贪。 院中可真熱鬧俭识,春花似錦缚态、人聲如沸宙橱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宝冕。三九已至地梨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扑眉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工卓缰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留计呈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像总寒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炫欺,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,185評論 25 707
  • 多謝 風(fēng)水 同學(xué)提出的問題转晰。我重新研究了下源代碼士飒,糾正下我之前存在的錯誤的理論。于是對之前的文章做了修正,之前對大...
    陸小飛閱讀 2,459評論 5 5
  • 明確金錢對自己的意義。 大多數(shù)人并不清楚自己想要的是什么,他們只知道自己想得到更多的東西染乌。這種情況下你最好把自己的...
    簡愛清兒閱讀 273評論 0 0
  • 還沒有開始學(xué)荡碾,我已經(jīng)開始臨摹了填具,雖然有點丑匆骗,還是記錄下來吧
    青青青桃閱讀 352評論 2 5
  • 我是個剛踏入大學(xué)一個月的學(xué)生 高中時代我向往著大學(xué)的美好生活 那種傳說中自由解放無憂無慮的日子 我把大學(xué)想象的很自...
    hhhhhhjk閱讀 200評論 1 0