這個方案已經(jīng)棄用了智亮,雖然效率高,但是太麻煩,最后還是修改為編譯期間ASM愉豺。
研究Qigsaw獨立打包 QigsawBundle,開始發(fā)現(xiàn)可以在ActivityLifecycleCallbacks.onActivityPreCreated中注入Resources茫因,后來發(fā)現(xiàn)只有Android10+才行蚪拦,以前的沒有,IDE沒提示所以一直沒發(fā)現(xiàn)冻押。
不想在編譯期間織入代碼驰贷,想要做到獨立打包,所以想找個兼容所有版本的方法洛巢。
如果定義一個基類并在其中重寫 getResources 方法是最簡單且最高效的辦法括袒。但是引用三方庫時,沒法解決了稿茉。
后來想到一個辦法:絕大部分的三方庫的基類都是AppCompatActivity箱熬,如果在公認的基類中增加代碼來實現(xiàn)資源注入,那就太方便了狈邑。
研究發(fā)現(xiàn)這個辦法是確實可行的城须,于是就寫代碼了。
最后原理是這樣的:
絕大部分的項目都引用了appcompat米苹,現(xiàn)在把appcompat庫中的AppCompatActivity類通過ASM修改增加一個注入資源的方法糕伐。然后把修改后的庫打包為 appcompat.qb ,其它保持不變蘸嘶。再把庫發(fā)布到本地或公司倉庫良瞧。最后在項目中替換為新的依賴陪汽。
implementation 'androidx.appcompat:appcompat:1.2.0'//大部分的項目用這個
implementation 'androidx.appcompat:appcompat.qb:1.2.0'//替換為這個
全部手動去替換太麻煩,以下是全局自動替換的方式褥蚯。還可以寫在Root中挚冤,所有子項目都適用,具體方法請參考DEMO 的 ext.injectActivityResource
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.name == 'appcompat') {
//只修改了name赞庶,其它都沒動训挡。
details.useTarget group: details.requested.group, version: details.requested.version, name: 'appcompat.qb'
}
}
}
AppCompatActivity通過反射獲取用戶Application中的injectActivityResource變量。因為AppCompatActivity中的injectActivityResource變量是靜態(tài)的歧强,所以反射代碼基本上只會執(zhí)行一次澜薄,效率不用擔(dān)心。然后在每次返回Resources前摊册,都會先調(diào)用Handler.handleMessage()方法肤京,這個對象是注入進來的相當(dāng)于App.injectActivityResource.handleMessage()
用戶自己實現(xiàn)一個InjectActivityResource類,在其中實現(xiàn)注入Resources就行了茅特。
說起來比較麻煩忘分,看代碼就明白了。
//只列出了改動的代碼白修,其它方法還在
public class AppCompatActivity extends FragmentActivity {
private static Handler injectActivityResource;
private Message msg;
private void initInjectActivityResource() {
if (injectActivityResource != null) return;
Context application = this.getApplicationContext();
try {
Field injectActivityResourceField = application.getClass().getDeclaredField("injectActivityResource");
injectActivityResourceField.setAccessible(true);
injectActivityResource = (Handler) injectActivityResourceField.get(application);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
initInjectActivityResource();
super.onCreate(savedInstanceState);
}
@Override
public Resources getResources() {
if (injectActivityResource != null) {
Resources resources = super.getResources();
if (msg == null) msg = new Message();
msg.obj = resources;
injectActivityResource.handleMessage(msg);
return resources;
}
return super.getResources();
}
}
public class InjectActivityResource extends Handler {
/**
* 這個方法調(diào)用非常頻繁饭庞,所以建議用JAVA寫,以保證沒有多余的代碼熬荆。
*/
@Override
public void handleMessage(@NonNull Message msg) {
Resources resources = (Resources) msg.obj;
Qigsaw.onApplicationGetResources(resources);
System.out.println("injectActivityResource " + resources.hashCode());
}
}
class App : Application() {
@Keep
private val injectActivityResource: Handler = InjectActivityResource()
}
上面也說了絕大部分項目都是用AppCompatActivity作基類的舟山,但也有例外啊卤恳!怎么辦呢累盗?
所有例外的處理方法都是:注入Handler injectActivityResource,然后替換舊依賴突琳。
如果手動去替換也太麻煩了若债,所以寫了個工具(點擊查看項目),把依賴和類名寫進去拆融,點兩下就能發(fā)布到本地或公司庫蠢琳。
特別說明1:要求所有(需要替換的)依賴都保存到庫中勾拉,當(dāng)前項目本地(libs目錄中)的AAR和JAR中不能有需要替換的類扣甲,如果有則需發(fā)布到庫中玉吁。也能做到兼容朵你,但太麻煩,沒寫代碼纷闺。
特別說明2:請用戶自行發(fā)現(xiàn)需要替換的類甜紫,暫時沒工具薪贫,后面會補上。不準備在編譯期間發(fā)現(xiàn)(拖慢編譯速度)已卸,而是在運行期間發(fā)現(xiàn)佛玄。工具原理方案1:通過一個Debug模式注入的Provider讀取manifest的Activity,然后判斷類是否存在累澡,如果存在則先判斷是否為AppCompatActivity的子類梦抢,如果不是則通過反射判斷是否有注入的Handler injectActivityResource變量。
方案2:可以通過一個手動的Gradle任務(wù)來發(fā)現(xiàn)愧哟,檢測方法則用方案1的奥吩,當(dāng)依賴變更時手動執(zhí)行一次就好。
操作流程如下:
- 填寫所有需要修改的依賴和類
- 運行一次InjectorTest.inject()翅雏,會把所有依賴注入代碼然后復(fù)制到當(dāng)前工具項目中。
- 運行publishToMavenLocal(發(fā)布到本地)或publish(發(fā)布到公司庫)
發(fā)布到Nexus的快捷方法:點擊查看
注意事項:
- 同一個依賴中的類寫在一個列表中人芽,比如AppCompatActivity1望几,AppCompatActivity2都寫在appcompat:1.2.0就好,不用每個寫一行萤厅。
- 不用每個版本都寫出來橄抹,只寫當(dāng)前用的,最新的就行惕味。比如appcompat:1.1.0就不用寫出來楼誓。下面只是一個示例。
- 所有新庫名加'.qb'(QigsawBundle的簡寫)
"androidx.appcompat:appcompat:1.2.0"->"androidx.appcompat:appcompat.qb:1.2.0"
@Test
fun InjectorTest.inject() {
val injected = inject(
listOf(
InjectDep("androidx.appcompat:appcompat:1.2.0", listOf(AppCompatActivity::class.java名挥,AppCompatActivity2::class.java)),
InjectDep("androidx.appcompat:appcompatXYZ:1.1.0", listOf(AppCompatActivityXYZ::class.java))
)
)
val folder = File(projectRootDir, "injected")
folder.deleteAll()
injected.forEach {
val single = File(folder, it.pom.name.trimInjectedPom())
it.depFile.moveTo(single)
it.pom.moveTo(single)
}
}
最后
如果本文幫助到了你疟羹,也幫我點個贊吧!
如果你愿意禀倔,還可以贊賞一杯咖啡或一瓶水榄融,非常感覺你的慷慨!