【Android 熱修復與外掛化 一】帶你入門Android外掛化(附demo)
本文為博主Colin原創文章,歡迎轉載。 https://blog.csdn.net/colinandroid/article/details/79431502
一. 背景
Android外掛化作為每個合格的Android程式設計師都必須會的技術,被各大廠廣泛使用。隨著各大廠對移動網際網路的壟斷,我們漸漸發現app整合的功能越來越多。比如如下幾個app(攜程、淘寶、支付寶):
可以看到每一個app都被集成了無數的功能入口,就拿淘寶來說,“天貓”、“外賣”、“飛豬”、“拍賣”,這任何一個入口都其實是一個app,只不過被整合到“淘寶”這個入口裡了。如果沒有外掛化技術,很難想象淘寶app的size會有多大。很可能有幾個GB!!
再來看看支付寶,可以發現支付寶中提供了很多第三方app的入口,而點選這些入口跳轉的也都是native頁面。應用市場上的支付寶app一共只有二三十MB,而如果這些app都整合到支付寶中,那支付寶的size就不是二三十MB了,那就是二三十GB了!!
本篇blog的主題是介紹Android外掛化技術,並且會提供一個仿支付寶外掛化技術的demo,告訴你支付寶是如何把一個第三方app作為外掛整合到自己的app裡的。
二. 外掛化好處
宿主和外掛分開編譯
編譯時只需要編譯宿主app,外掛app是在編譯好後下發到宿主app裡的。
併發開發
宿主app什麼時候釋出版本跟外掛app什麼時候開發完沒有關係,宿主app只要開發完並且為外掛app提供一個入口就可以了。
動態更新外掛
外掛app在開發完後下發到宿主app裡,點選相應的入口就可以跳轉到最新版的外掛app了。
按需下載模組
解決方法數或變數數爆棚(65536)
三. 隨便一個app都能整合到支付寶嗎?
答案是:不能!
我們來思考,支付寶要跳轉到一個外掛的Activity,而外掛是沒有被安裝的,它沒有上下文,也就沒有生命週期,那麼外掛Activity的生命週期就要由宿主app來控制。為此,我們需要建立一套標準。
四. 外掛化程式結構
我們先來看下外掛化程式結構,瞭解下其大致框架,對程式有巨集觀感受。下面我們直接開始擼程式碼。
五. 動態載入apk
1. 外掛app的activity沒有在宿主app中註冊,該怎麼辦?
插樁,一個空的Activity,專門用來載入外掛app中的activity,這個Activity叫ProxyActivity,後面我會具體去講這個空Activity該如何實現。我們只需要在宿主app裡註冊這個Activity就可以了。
2. 載入外掛app中的Activity
實際場景中外掛apk肯定是由服務端下發後,儲存到SD卡的某個資料夾下。這裡將編譯好的外掛apk放到手機外接SD卡的根目錄中,我們來演示宿主app如何去載入外掛app中的Activity。
六. 資源載入
接下來我們來看下FluginManager的loadPath方法如何實現。如果要實現這個功能,首先想到的肯定是用反射。
可是你別忘記了,外掛app根本就沒有安裝,這裡是無法找到這個Class的。我們需要DexClassLoader來完成Activity類的載入。
PluginManager的getDexClassLoader的實現如下:
講完了如何載入Activity,我們來講下如何載入Activity中用到的資原始檔。我們在日常開發中需要資原始檔時,我們是通過getResources()來獲取。例如載入一個圖片:
getResources().getDrawable()
1
可現在我們需要獲取的是另外一個app的資源,所以這裡就需要自己實現一個getResources()方法。
PluginManager的getResources方法實現如下:
至此,一個外掛app的activity載入功能就實現完成了,下面我們來看如何跳轉。
七. 跳轉到外掛app中的Activity
由於我們需要讀取SD卡中的外掛apk,這裡別忘記加上SD卡的讀寫許可權
這就可以實現跳轉了。下面我們來看下效果
奇怪,為什麼我們跳轉到外掛app的activity是空白的?我們來看下外掛app的activity應該長什麼樣子。
當然這裡我只在外掛app的主activity裡放了一張圖片,並沒有寫複雜的佈局。可是為什麼我們跳過來的是空白頁呢?我們再看下ProxyActivity的程式碼:
package com.ctrip.pluginapplication
import android.content.res.Resources
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
/**
* 殼!專門用來載入外掛Activity
* @author Zhenhua on 2018/3/3.
* @email [email protected] ^.^
*/
class ProxyActivity : AppCompatActivity() {
/**
* 要跳轉的activity的name
*/
private var className = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* step1:得到外掛app的activity的className
*/
className = intent.getStringExtra("className")
/**
* step2:通過反射拿到class,
* 但不能用以下方式,因為外掛app沒有被安裝!
*/
// classLoader.loadClass(className)
// Class.forName(className)
}
override fun getClassLoader(): ClassLoader {
//不用系統的ClassLoader,用dexClassLoader載入
return PluginManager.getInstance().getDexClassLoader() as? ClassLoader
?: super.getClassLoader()
}
override fun getResources(): Resources {
//不用系統的resources,自己實現一個resources
return PluginManager.getInstance().getResources() ?: super.getResources()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
我們發現,我們這裡還沒有在ProxyActivity裡寫邏輯啊,我們只是得到了外掛app的主activity的name,這時activity還沒有生命週期。?我們接著來實現。我們需要讓ProxyActivity控制外掛app的activity的生命週期,所以我們需要得到外掛app的activity的例項,然後去控制其生命週期:
package com.ctrip.pluginapplication
import android.content.res.Resources
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.ctrip.standard.AppInterface
/**
* 殼!專門用來載入外掛Activity
* @author Zhenhua on 2018/3/3.
* @email [email protected] ^.^
*/
class ProxyActivity : AppCompatActivity() {
/**
* 要跳轉的activity的name
*/
private var className = ""
private var appInterface: AppInterface? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* step1:得到外掛app的activity的className
*/
className = intent.getStringExtra("className")
/**
* step2:通過反射拿到class,
* 但不能用以下方式
* classLoader.loadClass(className)
* Class.forName(className)
* 因為外掛app沒有被安裝!
* 這裡我們呼叫我們重寫過多classLoader
*/
var activityClass = classLoader.loadClass(className)
var constructor = activityClass.getConstructor()
var instance = constructor.newInstance()
appInterface = instance as?AppInterface
appInterface?.attach(this)
var bundle = Bundle()
appInterface?.onCreate(bundle)
}
override fun onStart() {
super.onStart()
appInterface?.onStart()
}
override fun onResume() {
super.onResume()
appInterface?.onResume()
}
override fun onDestroy() {
super.onDestroy()
appInterface?.onDestroy()
}
override fun getClassLoader(): ClassLoader {
//不用系統的ClassLoader,用dexClassLoader載入
return PluginManager.getInstance().getDexClassLoader() as? ClassLoader
?: super.getClassLoader()
}
override fun getResources(): Resources {
//不用系統的resources,自己實現一個resources
return PluginManager.getInstance().getResources() ?: super.getResources()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
這時我們就可以成功跳轉了。ok,外掛化實現完成。我們來看下效果。
這裡附上demo(點選下載),如有任何疑問可留言提問,博主每天都會檢視。
~~~~華麗麗的分割線:在外掛app中實現更多功能~~~
之前我們的外掛app的activity其實就只是載入了一個imageView,我們現在來實現這樣一個功能:“點選ImageView,彈出一個toast”。
程式碼如下:
我們來看下效果。。
然而,點選竟然crash了。我們來貼下錯誤日誌:
原來我們在外掛Activity中不能用自己的上下文,我們應該用that!!
程式碼已經更新到github上,歡迎下載體驗。
---------------------
作者:Colin_Mindset
來源:CSDN
原文:https://blog.csdn.net/colinandroid/article/details/79431502
版權宣告:本文為博主原創文章,轉載請附上博文連結!