插件化 VirtualAPK 簡介 體驗 MD


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

插件化 VirtualAPK 簡介 體驗 MD


目錄

簡介

GitHub
Release note

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

個人使用體驗:功能強大,遍地是坑!

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

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一樣運行。

宿主項目的配置

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

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

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

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

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

implementation 'com.didi.virtualapk:core:0.9.8'
//注意,宿主項目中需要包含所有插件項目中的support依賴,否則插件編譯不通過(會提示要在宿主中添加依賴)
//但對於其他依賴則沒有此要求,例如可以在插件中依賴gson,而無需在宿主中依賴gson

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

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

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

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

6、判斷是否已加載插件

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();

7、跳轉到插件的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中的配置,插件構建強依賴構建環境,請不要輕易嘗試修改。

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

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

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

android.useDexArchive=false

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

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

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

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

打出來的包是非常小的

以下是正常打的包

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

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

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

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

1、提示設置在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

我們按照上述提示修改即可。

2、修改后再運行出現如下提示:

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'

3、沃日,配置為宿主的依賴后便開始出現各種問題,clean不行、build不行、手動刪除build目錄也不行,重啟AS也不行

AAPT2 error: check logs for details

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

4、添加完之后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

5、把所有設置都還原吧,完全沒法搞嘛!
我猜測可能與插件中采用了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的設置中禁用按需配置

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

6、繼續構建插件

> 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。

7、然后處理之后繼續構建插件又遇到了最初遇到的問題,也就是提示我添加一堆 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',否則就會報異常(垃圾代碼都不判空的嗎?)

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

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

9、重新開始集成!
這次我在網上仔細搜了一遍,發現很多人反映,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全是扯淡,可能舊版本需要這么配置,然而新版本並不需要這么配置。

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

相關知識

VirtualAPK 的特性

1、功能完備

  • 支持幾乎所有的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

2、優秀的兼容性

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

3、入侵性極低

  • 插件開發等同於原生開發,四大組件無需繼承特定的基類;
  • 精簡的插件包,插件可以依賴宿主中的代碼和資源,也可以不依賴;
  • 插件的構建過程簡單,通過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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM