插件化 VirtualAPK 簡介 體驗 MD

Markdown版本筆記 個人GitHub首頁 個人博客 個人微信 個人郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

插件化 VirtualAPK 簡介 體驗 MDhtml


目錄

簡介

GitHub
Release notejava

VirtualAPK 框架接入
VirtualAPK四大組件源碼分析
VirtualAPK 資源加載機制分析android

我的使用體驗:功能強大,遍地是坑!git

VirtualAPK是滴滴出行自研的一款優秀的插件化框架github

VirtualAPK is a powerful yet lightweight plugin framework for Android. It can dynamically load and run an APK file (we call it LoadedPlugin) seamlessly as an installed application. Developers can use any Class, Resources, Activity, Service, Receiver and Provider in LoadedPlugin as if they are registered in app's manifest file.服務器

Supported Features微信

Feature Detail
Supported components Activity, Service, Receiver and Provider
Manually register components in AndroidManifest.xml No need
Access host app classes and resources Supported
PendingIntent Supported
Supported Android features Almost all features
Compatibility Almost all devices
Building system Gradle plugin
Supported Android versions API Level 15+

基本原理架構

  • Activity:在宿主apk中提早佔幾個坑,而後經過「欺上瞞下」的方式啓動插件apk的Activity;由於要支持不一樣的launchMode以及一些特殊的屬性,因此須要佔多個坑。
  • BroadcastReceiver:將靜態註冊的廣播改成動態註冊。
  • Service:經過代理Service的方式去分發;主進程和其餘進程,VirtualAPK使用了兩個代理Service。
  • ContentProvider:經過一個代理Provider進行分發。

基本使用

VirtualAPK 對插件沒有額外的約束,原生的apk便可做爲插件。插件工程編譯生成apk後,便可經過宿主App加載,每一個插件apk被加載後,都會在宿主中建立一個單獨的LoadedPlugin對象。經過這些LoadedPlugin對象,VirtualAPK就能夠管理插件賦予插件新的意義,使其能夠像手機中安裝過的App同樣運行。app

宿主項目的配置

一、在 project 的build.gradle中添加依賴:框架

classpath 'com.android.tools.build:gradle:3.1.4' //這個版本不能修改,不然同步時就會失敗
classpath 'com.didi.virtualapk:gradle:0.9.8.6' //2019-1-14最新版本

二、在 app 模塊的build.gradle中使用插件:

apply plugin: 'com.didi.virtualapk.host'

三、在 app 模塊的build.gradle中添加依賴:

implementation 'com.didi.virtualapk:core:0.9.8'
//注意,宿主項目中須要包含全部插件項目中的support依賴,不然插件編譯不經過(會提示要在宿主中添加依賴)
//但對於其餘依賴則沒有此要求,例如能夠在插件中依賴gson,而無需在宿主中依賴gson

四、在 Application 中初始化插件引擎:

@Override
protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    PluginManager.getInstance(context).init();
}

五、在合適的時機加載插件(APP退出後下次使用前仍須要加載):

PluginManager.getInstance(context).loadPlugin(apkFile);
//當插件入口被調用後,插件的後續邏輯均不須要宿主幹預,均走原生的Android流程。

六、判斷是否已加載插件

LoadedPlugin loadedPlugin = PluginManager.getInstance(this).getLoadedPlugin(PKG); //包名
if (loadedPlugin == null) Toast.makeText(this, "還沒有加載 " + PKG, Toast.LENGTH_SHORT).show();
else Toast.makeText(this, "已加載 " + loadedPlugin.getPackageName(), Toast.LENGTH_SHORT).show();

七、跳轉到插件的Activity中

Intent intent = new Intent();
intent.setClassName(this, "com.didi.virtualapk.demo.aidl.BookManagerActivity");
intent.putExtra("name","包青天");
startActivity(intent);

注意,若是遇到以下提示,能夠沒必要關心,由於並無什麼影響:

Configuration on demand is not supported by the current version of the Android Gradle plugin since you are using Gradle version 4.6 or above.
Suggestion: disable configuration on demand by setting org.gradle.configureondemand=false in your gradle.properties file or use a Gradle version less than 4.6.

插件項目的配置

在VirtualAPK中,插件開發等同於原生Android開發,所以開發插件就和開發APP同樣。
若是有使用nativeActivity須要的用戶請更新使用fix_native_activity分支並修改依賴爲CoreLibrary,將來會合入主線。
構建環境建議直接使用Demo中的配置,插件構建強依賴構建環境,請不要輕易嘗試修改。

一、在 project 的build.gradle中添加依賴:

classpath 'com.android.tools.build:gradle:3.1.4' //這個版本不能修改,不然同步時就會失敗
classpath 'com.didi.virtualapk:gradle:0.9.8.6'  //2019-1-14最新版本,和宿主中用的是同一個依賴

二、在 app 模塊的gradle.properties中(如沒有請建立)添加以下配置:

android.useDexArchive=false

三、在 app 模塊的build.gradle中使用插件:

apply plugin: 'com.didi.virtualapk.plugin'
virtualApk {
    packageId = 0x6f // 插件資源表中的packageId,須要確保不一樣插件有不一樣的packageId.
    targetHost = 'D:/code/PluginDemo/app' // 宿主工程application模塊的路徑,插件的構建須要依賴這個路徑
    applyHostMapping = true //默認爲true,若是插件有引用宿主的類,那麼這個選項可使得插件和宿主保持混淆一致
}

四、構建插件
請經過gradle assemblePlugin來構建插件,assemblePlugin依賴於assembleRelease,這意味着:

  • 插件包均是release包,不支持debug模式的插件包
  • 若是存在多個productFlavors,那麼將會構建出多個插件包
  • 插件包位於build目錄下

打出來的包是很是小的

如下是正常打的包

其實主要區別在於:插件包是不包含宿主中已經存在aar依賴庫res資源的內容的,由於這些內容最終是用的宿主包中的。

必定要給插件設置一個資源別名resourcePrefix,以防止插件中誤用到了宿主中已經存在的資源名,致使解析出錯。
最典型的是默認的activity_main.xml,若是插件和宿主中都有這個佈局文件,那麼打包後會刪除插件中定義的activity_main.xml,因此在運行時使用的是宿主中的activity_main.xml,那麼就極可能會致使調用findViewBuId時崩潰!
宿主若是更改後最好先build一次,由於生成插件包時須要用到宿主構建時生成的文件。

構建插件時可能出現的問題

我經過AS建立了一個最最純淨的項目(默認包含kotlin),結果運行時發現一堆問題。

一、提示設置在app模塊中的gradle.properties中添加android.useDexArchive=false

A problem occurred configuring project ':app'.
> Failed to notify project evaluation listener.
   > Can't using incremental dexing mode, please add 'android.useDexArchive=false' in gradle.properties of :app.
   > Cannot invoke method onProjectAfterEvaluate() on null object

咱們按照上述提示修改便可。

二、修改後再運行出現以下提示:

Failed to notify task execution listener.
> The dependencies 
[
   com.android.support.constraint:constraint-layout:1.1.3,
   com.android.support:support-fragment:28.0.0,
   //後面省略二十個
]
that will be used in the current plugin must be included in the host app first. Please add it in the host app as well.

意思是說,在插件項目中包含的庫也必須在宿主項目存在。能夠發現所有是 support 庫,咱們只需統一宿主和插件的support庫版本就能夠了,好比都用以下最新的設置:

implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'

三、沃日,配置爲宿主的依賴後便開始出現各類問題,clean不行、build不行、手動刪除build目錄也不行,重啓AS也不行

AAPT2 error: check logs for details

查看報錯詳細信息,說什麼資源文件找不到什麼問題,徹底是莫名其妙嘛,爲何會有這個錯呢?
網上搜了一通,找不到解決方案,只找到一種委曲求全的扯淡方案,那就是在project中的gradle.properties中添加android.enableAapt2=false

四、添加完以後clean了一下,結果那個問題沒有了,又出另外一個莫名其妙的錯誤:

Process 'command 'D:\software\android_sdk\build-tools\26.0.2\aapt.exe'' finished with non-zero exit value 1

報錯的緣由可能和咱們上面的操做有關,由於看到有這麼兩行信息:

Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
See https://docs.gradle.org/4.6/userguide/command_line_interface.html#sec:command_line_warnings

五、把全部設置都還原吧,徹底無法搞嘛!
我猜想可能與插件中採用了kotlin而宿主沒有采用有關,因而在宿主中添加了kotlin相關的依賴,結果這貨同步時又報一個錯:

A problem occurred evaluating project ':CoreLibrary'.
> Failed to apply plugin [id 'com.android.library']
   > Configuration on demand is not supported by the current version of the Android Gradle plugin since you are using Gradle version 4.6 or above. Suggestion: disable configuration on demand by setting org.gradle.configureondemand=false in your gradle.properties file or use a Gradle version less than 4.6.

意思是說當前版本的Gradle插件不支持按需配置,日了狗了,什麼鬼呀,搜索了一下,說能夠這樣禁用按需配置:

  • 在你的Project和報錯的CoreLibrary模塊中的gradle.properties 文件中設置 org.gradle.configureondemand=false
  • 在AS的設置中禁用按需配置

配置完成後同步一下發現成功了。

六、繼續構建插件

> Failed to notify project evaluation listener.
   > Can't find C:\Users\baiqi\Desktop\VirtualAPK-master\app\build\VAHost\versions.txt, please check up your host application
       need apply com.didi.virtualapk.host in build.gradle of host application
   > Cannot invoke method onProjectAfterEvaluate() on null object

這個錯誤提示就比較好處理了由於提示找不到versions.txt,而這個文件是構建後由 VirtualAPK 產生的,咱們要先構建一次宿主app,才能夠構建plugin(由於插件構建須要宿主的mapping以及其餘信息),能夠嘗試使用build -> build apk(s)直接構建宿主apk。

七、而後處理以後繼續構建插件又遇到了最初遇到的問題,也就是提示我添加一堆 support 庫,乾脆我把插件中全部用到的 support 庫所有去掉得了,看你還報不報錯!

果不其然,又一個錯誤出來了:

Cannot get property 'id' on null object

這又是什麼鬼?
網上搜了半天,有人說,這個問題是由於插件中佈局文件沒有id,在插件主activity的佈局文件中增長一個view,聲明一個id就能夠了。
然而我按照上述方式設置以後並無任何卵用!

我經過如下指令

gradle assemblePlugin --stacktrace

拿到了以下錯誤信息:

* Exception is:
java.lang.NullPointerException: Cannot get property 'id' on null object
        at com.didi.virtualapk.aapt.ArscEditor.slice(ArscEditor.groovy:66)
        ...

而後又去查看了ArscEditor.groovy中相應的源碼:

這意思大體是說,要確保有一個'attr',不然就會報異常(垃圾代碼都不判空的嗎?)

可是這是什麼垃圾東西呢?我搞了老半天,這個問題始終解決不了!

八、坑實在是太多了,填不完了,從新開始集成吧。
此次我決定將demo中的配置所有遷移過來,而後再一點一點的更新到新版本,或添加新功能,看看到哪一步時會失敗!
然而理想很豐滿現實很骨感,仍舊是遍地錯誤!

九、從新開始集成!
此次我在網上仔細搜了一遍,發現不少人反映,Gradle的 build tools 版本問題會致使失敗,即便使用 demo 中的配置也不行,因此這一次我使用網上說的版本吧。

classpath 'com.android.tools.build:gradle:2.1.3'

gradle-wrapper

distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

然而發現這TMD全是扯淡,可能舊版本須要這麼配置,然而新版本並不須要這麼配置。

十、從新開始集成!
這一次決定在原demo基礎上修改,此次終於成功了。具體配置就是上面所述的。

相關知識

VirtualAPK 的特性

一、功能完備

  • 支持幾乎全部的Android特性;
  • 四大組件均不須要在宿主manifest中預註冊,每一個組件都有完整的生命週期。
    • Activity:支持顯示和隱式調用,支持Activity的themeLaunchMode,支持透明主題;
    • Service:支持顯示和隱式調用,支持Service的startstopbindunbind,並支持跨進程bind插件中的Service;
    • Receiver:支持靜態註冊和動態註冊的Receiver;
    • ContentProvider:支持provider的全部操做,包括CRUDcall方法等,支持跨進程訪問插件中的Provider。
  • 支持自定義View,支持自定義屬性和style,支持動畫;
  • 支持PendingIntent以及和其相關的AlarmNotificationAppWidget
  • 支持插件Application以及插件manifest中的meta-data
  • 支持插件中的so

二、優秀的兼容性

  • 兼容市面上幾乎全部的Android手機(兼容性問題無法保證,從它文檔中遍地的"升級提示"即可以看出來);
  • 資源方面適配小米、Vivo、Nubia等,對未知機型採用自適應適配方案(意思就是說,只象徵性的進行了一些適配);
  • 極少的Binder Hook,目前僅僅hook了兩個Binder:AMSIContentProvider,hook過程作了充分的兼容性適配;
  • 插件運行邏輯和宿主隔離,確保框架的任何問題都不會影響宿主的正常運行(扯淡,一上手就遇到幾個框架致使的崩潰)。

三、入侵性極低

  • 插件開發等同於原生開發,四大組件無需繼承特定的基類;
  • 精簡的插件包,插件能夠依賴宿主中的代碼和資源,也能夠不依賴;
  • 插件的構建過程簡單,經過Gradle插件來完成插件的構建,整個過程對開發者透明。

主流插件化框架的對比

以下是VirtualAPK和主流的插件化框架之間的對比。

特性 DynamicLoadApk DynamicAPK Small DroidPlugin VirtualAPK
支持四大組件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持
組件無需在宿主中預註冊 ×
插件能夠依賴宿主 ×
支持PendingIntent × × ×
Android特性支持 大部分 大部分 大部分 幾乎所有 幾乎所有
兼容性適配 通常 通常 中等
插件構建 部署aapt Gradle插件 Gradle插件

爲何選擇 VirtualAPK

已經有那麼多優秀的開源的插件化框架,滴滴爲何要從新造一個輪子呢?

  • 大部分開源框架所支持的功能還不夠全面
    除了DroidPlugin,大部分都只支持Activity。

  • 兼容性問題嚴重,大部分開源方案不夠健壯
    因爲國內Rom嘗試深度定製Android系統,這致使插件框架的兼容性問題特別多,而目前已有的開源方案中,除了DroidPlugin,其餘方案對兼容性問題的適配程度是不足的。

  • 已有的開源方案不適合滴滴的業務場景
    雖說DroidPlugin從功能的完整性和兼容性上來看,是一款很是完善的插件框架,然而它的使用場景和滴滴的業務不符。
    DroidPlugin側重於加載第三方獨立插件,而且插件不能訪問宿主的代碼和資源。而在滴滴打車中,其餘業務模塊均須要宿主提供的訂單、定位、帳號等數據,所以插件不可能和宿主沒有交互。
    其實在大部分產品中,一個業務模塊實際上並不能垂手可得地獨立出來,它們每每都會和宿主有交互,在這種狀況下,DroidPlugin就有點力不從心了。

基於上述幾點,咱們只能從新造一個輪子,它不但功能全面、兼容性好,還必須可以適用於有耦合的業務插件,這就是VirtualAPK存在的意義。

如何選擇適合的插件化框架

在加載耦合插件方面,VirtualAPK是開源方案的首選,推薦你們使用。

抽象地說

  • 若是你要加載一個插件,而且這個插件無需和宿主有任何耦合,也無需和宿主進行通訊,而且你也不想對這個插件從新打包,那麼推薦選擇DroidPlugin;
  • 除此以外,在同類的開源中,推薦你們選擇VirtualAPK。

通俗易懂地說

  • 若是你是要加載微信、支付寶等第三方APP,那麼推薦選擇DroidPlugin;
  • 若是你是要加載一個內部業務模塊,而且這個業務模塊很難從主工程中解耦,那麼VirtualAPK是最好的選擇。

VirtualAPK 原理簡介

基本原理

  • 合併宿主和插件的ClassLoader。須要注意的是,插件中的類不能夠和宿主重複
  • 合併插件和宿主的資源。重設插件資源的packageId,將插件資源和宿主資源合併
  • 去除插件包對宿主的引用。構建時經過Gradle插件去除插件對宿主的代碼以及資源的引用

四大組件的實現原理

  • Activity
    採用宿主manifest中佔坑的方式來繞過系統校驗,而後再加載真正的activity;
  • Service
    動態代理AMS,攔截service相關的請求,將其中轉給Service Runtime去處理,Service Runtime會接管系統的全部操做;
  • Receiver
    將插件中靜態註冊的receiver從新註冊一遍;
  • ContentProvider
    動態代理IContentProvider,攔截provider相關的請求,將其中轉給Provider Runtime去處理,Provider Runtime會接管系統的全部操做。

以下是VirtualAPK的總體架構圖,更詳細的內容請你們閱讀源碼。

VirtualAPK

插件如何和宿主交互
經過compile相同aar的方式來交互。
好比,宿主工程中compile了以下aar:

compile 'com.didi.foundation:sdk:1.2.0'
compile 'com.didi.virtualapk:core:[newest version]'
compile 'com.android.support:appcompat-v7:22.2.0'

可是插件工程須要訪問宿主sdk中的類和資源,那麼能夠在插件工程中一樣compile sdk的aar,以下:

compile 'com.didi.foundation:sdk:1.2.0'

這樣一來,插件工程就能夠正常地引用sdk了。而且,插件構建的時候會自動將這個aar從apk中剔除

上述就是VirtualAPK中插件和宿主通訊的基本方式。

然而,VirtualAPK仍然有一些小小的約束,以下注意事項,請務必仔細閱讀。

已知約束

目前暫不支持的特性

  • 暫不支持Activity的一些不經常使用特性,好比processconfigChanges等屬性,可是支持themelaunchModescreenOrientation屬性
  • overridePendingTransition(int enterAnim, int exitAnim)這種形式的轉場動畫,動畫資源不能使用插件的(可使用宿主或系統的)
  • 插件中彈通知,須要統一處理,走宿主的邏輯,通知中的資源文件不能使用插件的(可使用宿主或系統的)
  • 插件的Activity中不支持動態申請權限

Activity

支持LaunchMode和theme

  • 透明Activity不能有啓動模式,而且主題中必須含有android:windowIsTranslucent屬性;
<style name="AppTheme.Transparent">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item>
</style>
  • 插件中調用宿主的四大組件,請注意Intent中的包名

VirtualAPK對Intent的處理遵循Android規範,插件之間乃至插件和宿主之間,包名是區分它們的惟一標識。

爲了兼容宿主與插件之間的activity互調的場景,咱們弱化了插件的包名,在插件中經過context.getPackageName()取到的仍然是宿主的包名。所以在下面的例子中,假如宿主的包名是com.didi.virtualapk,而後在插件中啓動一個宿主Activity,仍然可正確的調用:

// 兼容方式
Intent intent = new Intent(this, HostActivity.class);
startActivity(intent);

// 顯式指定包名的方式
Intent intent = new Intent();
intent.setClassName("com.didi.virtualapk", "com.didi.virtualapk.HostActivity");
startActivity(intent);

若是想在插件中去訪問插件的四大組件,那麼就沒有任何要求了,下面的代碼會在插件的一個Activity中嘗試啓動插件中的另外一個Activity:

// 正確的用法,由於此時intent中的包名是插件的包名
Intent intent = new Intent(this, PluginActivity.class);
startActivity(intent);

Service

支持跨進程bind service
無約束

BroadcastReceiver

  • 靜態Receiver將被動態註冊,當宿主中止運行時,外部廣播將沒法喚醒宿主;
  • 因爲動態註冊的緣故,插件中的Receiver必須經過隱式調用來喚起。

ContentProvider

支持跨進程訪問ContentProvider

  • 分狀況,插件調用本身的ContentProvider,若是須要用到call方法,那麼須要將provideruri放到bundle中,不然調用不生效;
Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book");
Bundle bundle = PluginContentResolver.getBundleForCall(bookUri);
getContentResolver().call(bookUri, "testCall", null, bundle);
  • 插件調用宿主和外部的ContentProvider,無約束;

  • 宿主調用插件的ContentProvider,須要將provideruri包裝一下,經過PluginContentResolver.wrapperUri方法,若是涉及到call方法,參考上面所描述的;

String pkg = "com.didi.virtualapk.demo";
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(pkg);
Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book");
bookUri = PluginContentResolver.wrapperUri(plugin, bookUri);
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);

Fragment

推薦你們在Application啓動的時候去加載插件,否則的話,請注意插件的加載時機。

考慮一種狀況,若是在一個較晚的時機去加載插件而且去訪問插件中的資源,請注意當前的Context。好比在宿主Activity(MainActivity)中去加載插件,接着在MainActivity去訪問插件中的資源(好比Fragment),須要作一下顯示的hook,不然部分4.x的手機會出現資源找不到的狀況。

String pkg = "com.didi.virtualapk.demo";
PluginUtil.hookActivityResources(MainActivity.this, pkg);

so文件的加載

爲了提高性能,VirtualAPK 在加載一個插件時並不會主動去釋放插件中的so,除非你在插件apk的manifest中顯式地指定VA_IS_HAVE_LIB爲true,以下所示:

<meta-data
    android:name="VA_IS_HAVE_LIB"
    android:value="true" />

爲了通用性,在armeabi路徑下放置對應的so文件便可知足需求。若是考慮性能請作好各類so文件的適配。

FQA

The directory of host application doesn't exist!
錯誤分析:宿主工程的application模塊的路徑不存在,通常是指路徑配錯了
解決方式:檢測targetHost這個路徑是否正確,相對路徑或者絕對路徑都行

java.lang.ArrayIndexOutOfBoundsException: 2
錯誤分析:請檢查dependencies中aar的依賴方式
解決方式:按以下建議修改

dependencies {
    √ compile 'com.didi.virtualapk:core:0.9.0'
    √ compile project (":CoreLibrary")

    // group和version字段必須有
    √ compile(group:'test', name:'CoreLibrary-release', version:'0.1', ext:'aar')

    × releaseCompile 'com.didi.virtualapk:core:0.9.0'
    × compile(name:'CoreLibrary-release', ext:'aar')
}

編譯插件時空指針:Cannot invoke method getAt() on null object
解決方式:請確保插件中至少有一個本身的資源

插件的activity能正常打開,可是插件中的資源讀取失敗
解決方式:依次檢查:

  • 先檢查 packageId 的取值範圍(在下面)是否正確
  • 再檢查插件依賴的全部com.android.support包在宿主都有顯式依賴,而且版本和宿主保持一致
  • 讀取失敗的資源id是否和宿主的資源重名,重名資源會在構建插件包時被自動剔除

Failed to notify project evaluation listener
解決方式:修改Gradlebuild tools的版本
構建環境建議:

Gradle 2.14.1
com.android.tools.build 2.1.3

java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity
解決方式:構建插件請使用gradle assemblePlugin,而不能直接經過AndroidStudio run出來一個插件apk。

關於Android M及以上版本動態申請權限問題
從Android 6.0開始,系統採用了新的權限機制,爲了保證插件的加載,請保證APP具備SD卡的訪問權限。若是你的app沒有在android 6.0上作足夠的測試,請不要設置targetSdk爲23。
注意:目前暫時不支持在插件中動態申請權限

插件的gradle文件中對於packageID設置有什麼範圍嗎?

  • 採用正常的android資源命名方式,PPTTNNNN:PackageId + TypeId + EntryId。
  • 運行時獲取資源須要經過packageId來映射apk中的資源文件,不一樣apk的packageId值不能相同,因此插件的packageId範圍是介於系統應用(0x01,0x02,...具體佔用多少值視系統而定)和宿主(0x7F)之間。
  • 多個插件的packageId和packageName同樣,在宿主中須要確保是惟一的。

生成的插件apk中會發現有些png圖片是黑色的,大小爲0,這是怎麼回事?
爲了減少包的大小對於那些沒有引用的資源進行壓縮了,在gradle中配置shrinkResources true便可,位置和minifyEnabled true一塊兒。

關於Activity的configchanges
由於configChanges的選項組合太多,坑位比較多,這個暫時不許備支持,由於在平常使用的時候就橫豎經常使用。

iR是什麼意思?
install Release,gradle中的一種小駝峯命名的縮寫方式。若是發現衝突,能夠經過assembleRelease來實現構建宿主工程。

0.9.1版本的VirtualAPK構建插件在構建插件的時候assets目錄下的文件會被刪除
這是0.9.1版本的bug,更高版本已經修復,請更新版本。

宿主和插件同時依賴公共的本地jar文件或library module,支持在構建插件時自動剔除嗎?
不支持。
構建插件的依賴自動剔除功能僅支持內容穩定不變,路徑穩定的資源,而本地的jar或其它資源的路徑和內容都是可變動的,所以沒法直接自動剔除,若是須要剔除,請將資源打包導出部署到maven或其它依賴管理服務器。若是資源不可公開發布,可在內網部署私有maven服務

2019-1-13

相關文章
相關標籤/搜索