怎麼將 Android 程序做成插件化的形式?

只是一個純技術上的疑惑:Android程序每次更新都要下載一個完整的apk,而很多時候軟體只是更新了一個小功能而已,這樣的話,就顯得很麻煩。能不能把android程序做成主程序+插件化的形式呢,這樣才利於小功能的擴展啊


插件化技術發展到現在其實已經很成熟了,但是相應的問題,如果沒有真正地去實踐過,根本不了解其中有多少問題,會牽涉到多少技術細節,多少被外人膜拜的外表光鮮的技術大牛都被『插件化』這三個字折磨地死去活來,這對於 Android 整個生態的損害也讓人無法忽視。

昨天的 MDCC ,馮森林老師提出了一個很有意思的思路『組件化』。

我們首先要想一下,我們做插件化的目的是什麼?

  1. 為了滿足產品隨時上線的需求?
  2. 為了修復因為我們對自己要求不嚴格而寫出來的 bug ?
  3. 為了向人炫耀自己的技術實力?

很抱歉,如果是為了這些目的,那就真的太對不起自己是『開發者』這個如此高逼格的身份了。

做插件化真正的目的:是為了去適應並行開發,是為了解耦各個模塊,是為了避免模塊之間的交叉依賴,是為了加快編譯速度,從而提高並行開發效率。

明確了這些,我們再來看插件化的結果,每個模塊都支持獨立運行測試,分為穩定的 release 版本和不穩定的 snapshot 版本,每個模塊都高度解耦,沒有交叉依賴,不會出現一個模塊依賴了另一個模塊,其中一個人改了這個模塊的代碼,對另一個模塊造成影響。這時候,我們再看馮老師的『組件化』思想。

那麼這個『組件化』是什麼意思呢?我說下我自己的理解,可能不對,還請指教:

通過 gradle 配置的方式,將打 debug 包和 release 包分開。這樣會有一個好處,開發一個模塊,在 debug 的時候,可以打成一個 apk ,獨立運行測試,可以完全獨立於整個宿主 APP 的其他所有組件;待到要打 release 包的時候,再把這個模塊作為一個 library ,打成 aar ,作為整個宿主 APP 的一部分。而 debug 和 release 的切換都是通過 gradle 配置,可以做到無縫切換。至於模塊之間的跳轉,可以用別名的方式,而不是用 Activity 和 Fragment 類名。這樣所有的模塊和宿主 APP 都是完全解耦的,徹底解決了並行開發的可能造成的交叉依賴等問題。

按照這個思路,我們再來看看一些其他的細節:

  1. 在 Android 里有一個比較爽的一點是,作為 library 的時候,aar 里的引用依賴,在宿主 Application 里也有同樣的引用依賴,並不會打包兩份到宿主 Application 里;
  2. 模塊之間的跳轉,除了使用別名的方式,我能想到的還有另外一種方式,同樣是通過 gradle 腳本,將跳轉用到的類打成一個 jar ,作為一個 API 服務提供給其他模塊作為編譯期依賴(provided)引入;
  3. 各個 library 在 debug 的時候作為 apk ,要獨立打包運行測試,這時就需要有一個啟動 Activity ,而 library 是不需要的,我的想法是放置兩個 AndroidManifest.xml ,使用 sourceSets 分別在 debug 和 release 的時候載入不同的 AndroidManifest.xml 。

怎麼樣?看上去是不是很像插件化 Atlas ?然而這個方案沒有任何『黑科技』,不牽涉任何 hook ,跟 Atlas 的區別就是無需關心不同的 Context ,無需再關心類、資源怎麼去載入,無需關心 Context 的安全問題,無需關心不同機型的兼容適配...技術成本可能連 Atlas 的十分之一都不到!

感興趣的話,可以看看這個 slide 分享,地址在這兒(http://www.slideshare.net/oasisfeng/from-containerization-to-modularity),我也寫了個小 sample 去實現這個想法(GitHub - liangzhitao/ComponentizationApp: A Componentization App.)。


完全可以,而且在淘寶、微信等 App 已經實現並應用,主要利用 Java ClassLoader 的原理,對於 Android 來說是 DexClassLoader,如下

DexClassLoader pluginClassLoader = new DexClassLoader(dexPath, optimizedDirectory, libraryPath, parentClassLoader);

可動態載入的內容包括 apk、dex、jar 等

我也利用這個原理及開源項目實現了一個版本,並且整理了 Android 插件化的作用、概念以及不錯的資料(包括開源項目)和解決方案。

其中包括 65535 問題,Android 插件化、Android 組件化、Android 動態載入、Android 動態升級;介紹 DexClassLoader 和 PathClassLoader 的區別;如何解決生命周期管理、資源訪問問題,如何消除公共依賴

詳細可見我的博客:利用 DexClassLoader 實現 Android 插件化,從而達到動態載入


看到很多人說可以做,能實現,這個大方向我是贊同的。
然而我不希望在2016年這樣的時代背景下,你因為看到知乎的答覆,而走彎路。
目前插件化理論上是絕對OK的,而且大公司的產品也有不少已經在用。
但真正做過你才會知道這裡面有多坑,用於實踐也會有很多的限制,如果只是為了彰顯功能的先進,而你的團隊已經沒啥事可以做了,那你去做吧,我不攔著。
主要還是給開發人手緊張,有其他留存、活躍、收入指標等更重要的事情去做,建議還是不要折騰插件化了。鄙人所在團隊中等規模,來自一家互聯網上市公司,準確的說有些地方也已經用上插件化,但並沒有再去耗費人力去維護了,和項目本身性質也有關,受益不大,投入卻不少,自然不會太耗費成本在這了。


這是完全可能的,實際上現在許多應用都實現了類似的插件化設計方式。

插件化的基礎是動態載入class,Google官方的教程在這裡:http://android-developers.blogspot.com/2011/07/custom-class-loading-in-dalvik.html

但在實現中仍然需要注意一些細節,如 http://stackoverflow.com/questions/1001944/android-remote-code-loading中所提到的,dexOutputDir 需要具有寫許可權,在上面的教程中用的是 getDir("dex", Context.MODE_PRIVATE)。

另外,這種方式還有一些已知的問題,比如在 Honeycomb 上會無法使用,見 Issue 15893: http://code.google.com/p/android/issues/detail?id=15893

補充一個開源的示常式序:http://code.google.com/p/android-custom-class-loading-sample/。實踐前請先熟悉使用 Ant。


題主,你問的是插件化,問題描述的卻是增量更新,插件化和eclipse安裝插件差不多,但eclipse自身更新卻不是通過安裝插件來更新的,chrome也一樣。
這有一篇關於增量更新算是比較好的文章:淺析android應用增量升級
關於插件化的文章,可以看一看這個:Android apk動態載入機制的研究

這兩種方式各有利弊:
1、增量更新對於更新頻繁的軟體來說,版本越多,需要做的差分包就越多,這樣每次更新驗證的工作量會非常大;
2、插件化:這個我感覺效率會有點低,並且每一個插件都是一個apk,插件多的時候管理起來也麻煩。


理論上是可以的。 邀請我回答,本來打算寫個demo驗證一下的,最近太忙,就耽擱了,找了一下Android文檔,所以這個理論上是可行的。

既然以apk結尾,主從應用包的方式被大家認定為非插件模式,那麼什麼是插件模式,就要看Android是否允許你LoadClass了。

1. 什麼樣的Class可以Load?
Java 裡面直接把 .class 文件打包到 .jar 文件裡面就可以了,但是 Android 的 Dalvik VM 是不認 Java 的 byte code 的,所以不能直接這麼打包,而要用 dx 工具轉成 Dalvik byte code 才可以。當然,dx 工具轉了之後,jar 包裡面就不是 .class 文件了,而是 .dex 文件。

2. 選擇Class Loader

  • DexClassLoader

可以載入 apk, jar 或者 dex 文件
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:
File dexOutputDir = context.getDir("dex", 0);
Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.

DexClassLoader cl = new DexClassLoader(jarFile.toString(), "/sdcard/test", null, ClassLoader.getSystemClassLoader());

  • PathClassLoad

只能載入已經安裝到 Android 系統中的 apk 文件,也就是 /data/app 目錄下的 apk 文件。其它位置的文件載入的時候都會出現 ClassNotFoundException

PathClassLoader cl = new PathClassLoader(jarFile.toString(), "/data/app/", ClassLoader.getSystemClassLoader());

參考文檔
http://developer.android.com/reference/dalvik/system/DexClassLoader.html
http://blog.csdn.net/quaful/article/details/6096951
http://blog.csdn.net/quaful/article/details/6094940


很多sdk類似就是插件了吧,嵌入應用中


360手機助手也開源了他們的插件話框架,DroidPlugin


建議你看下這個框架,singwhatiwanna/dynamic-load-apk · GitHub,目前是開源中做得比較好的


推薦一個輕量、跨平台的組件化框架:wequick/Small · GitHub


淘寶的框架是用了osgi的bundle概念,整個應用框架生命周期完整。


可以參考這個專題:http://www.androidblog.cn/index.php/Index/detail/id/16#


翻譯組成員作品:安卓應用程序插件化開發框架(android-application-plug-ins-frame-work)
http://androidbox.sinaapp.com/index.php/project/android-plugs.html


現有的apkplug框架是一個非常成熟的方案,提供api編程介面也有很多app在使用了。推薦去看看http://www.apkplug.com


1、java 裡面直接把 .class 文件打包到 .jar 文件裡面就可以了,但是 Android 的 Dalvik VM 是不認 Java 的 byte code 的,所以不能直接這麼打包,而要用 dx 工具轉成 Dalvik byte code 才可以。當然,dx 工具轉了之後,jar 包裡面就不是 .class 文件了,而是 .dex 文件。

2、可以做成server 利用broadcast,pendingIntent,Intent去通信,再provider數據共享過濾器設置下就能實現這樣的效果。

3、國內的各大應用市場的安卓客戶端就是這麼做的,由市場客戶端可以下載各個功能客戶端,在市場里可以對這些功能客戶端進行更新、刪除、打開操作。其實如果需求是定製化的應用市場,比如「辦公應用市場」,在功能性的規則介面定義好之後,可以增加更多的業務邏輯,比如說「從市場客戶端開啟功能客戶端的具體某個頁面」,或者「從市場客戶端調用功能客戶端的某個功能」。


已經有這樣的實現了,Qihoo360/DroidPlugin: A plugin framework on android,Run any third-party apk without installation, modification or repackage,這個是360公司的插件化框架,你如果用過360手機衛士的話,應該可以發現它用了插件化載入額外的功能。
這是開源中國對該項目的中文介紹:
http://www.oschina.net/p/droid-plugin


360手機助手已經開源了他們的插件話免安裝框架,目前插件化機制最好的框架:Qihoo360/DroidPlugin: A plugin framework on android,Run any third-party apk without installation, modification or repackage


試試這個,Qihoo360/DroidPlugin · GitHub


實現插件化的方案網上可以找到很多,無非是代理Activity加動態載入類和用AssetManager載入資源,但大部分的實現是插件要依賴一個插件sdk來開發,比如Activity要繼承一個插件sdk的Activity,關鍵是如何減少這個耦合感覺就有點難度,看有沒大神給點思路


現在網上關於android插件的案例很多,原理幾乎都大同小異,無非都是主程序啟動代理Activity然後通過DexClassLoader綁定插件上的組件。


推薦閱讀:

30 歲才開始學習編程靠譜嗎?
2013 年 7 月的 Struts2 漏洞實際帶來多大影響?
為什麼寫程序的時候可以堅持很久,但是學習數學就很難保持注意力?
為什麼大多數中國高校不直接使用英文原版教材教學?
把代碼寫的太靈活不好嗎?為什麼會被上司批評?

TAG:編程 | Android 應用 | Android 開發 | Java 編程 | Android |