Android多渠道SDK开发心得(4)——多渠道sdk的开发与构建
2017-11-26 12:14
1551 查看
转载请注明出处:http://blog.csdn.net/u011370390/article/details/78636574
网上关于apk多渠道开发有很多说明,但关于sdk的则少的可怜,在开发过程中遇到问题时、多数时间也就只能参考外文资料及自己琢磨。
sdk/build.gradle
指定完后同步gradle,马上就会报错
‘generateReleaseAssets’这个task找不到,这可是在配置sdk多渠道开发前,fat-aar.gradle中合并assets文件的task,怎么会找不到呢?
于是将sdk中该productFlavors代码块注释,在app中添加试试
app/build.gradle
同步成功后,在命令后下输出一下命令:
会发现好多task中都带有”Baidu”或”Alibaba”这两个渠道名称,
而注释掉app中的productFlavors后就没有了该渠道字样,这说明Gradle在构建Android的过程中,其构建脚本并非固定的,而是随着渠道名称实时变化的、这样才能针对不同的渠道完成相应的构建。
于是,我们再打开sdk/build.gradle中的productFlavors代码块,回到刚刚报的这个错上面来
既然不能找到这个’generateReleaseAssets’ task,那我们就尝试着给它加上一个渠道号’generateBaiduReleaseAssets’,再同步,会发现报以下错:
虽然还在报错,但明显不是先前的那个task了,而这个task也是fat-aar.gradle中的task,于是,我们考虑将该.gradle文件中所有系统相关的task都加上“Baidu“这个渠道号。
一般渠道号都是加在驼峰命名法的第一个驼峰处,如:’generateReleaseAssets’ –> ‘generateBaiduReleaseAssets’
只有’transformNativeLibsWithSyncJniLibsForBaiduRelease’这个task是个例外,加在For后面。
gradle刷新成功后,在命令窗口执行以下命令:
出现以下错误
合并AndroidManifest.xml时minSdkVersion版本不一致引起的,在base和sdk两个的AndroidManifest.xml中各自指定minSdkVersion为14:
再次执行gradle clean main,报如下错:
打对应的jar时找不到了,展开bundles目录,如下:
很显然,加上多渠道包后,打aar包的流程与发现了一个小变化,此时,我们先将该jar包的路径改为’Baidu’渠道的路径,如下
{rootProject}/build.gradle
此时再执行gradle clean main命令,终于成功了,展开sdk/build/outputs/aar目录,如下:
很显然,生成的jar包和so.zip是从”baidu”这个渠道中生成的,而自动生成的4个aar包是否能满足我们的要求呢?
展开sdk/outputs/intermediates/classes目录下baidu和alibaba的release目录,如下:
可以发现,此时base库中的.class文件并没有合并到任何一个渠道的sdk中来,不管是解压生成的各渠道aar包并反编译其classes.jar还是直接反编译bundles中各渠道包中的classes.jar都能确认这一点。问题很可能出现在fat-aar.gradle中
sdk/fat-aar.gradle
该脚本中定义的路径是写死的,结合前面的发现,base库的.class确认进入了sdk/build/intermediates/classes的release目录。此时,我们暂且先将fat-aar.gradle中的这两个路径做如下修改:
再次执行gradle clean main命令,成功后,会发现
即生成的sdk-baidu-release.aar包中成功打进了base的.class文件,而其他3个aar中则只包含sdk中的.class.很好理解,我们只对baidu这一渠道的release包进行了合并处理,如果需要对alibaba渠道的包也合并base库,就需要将fat-aar.gradle中的task按照’Alibaba’再复制一份即可。这是不用动脑筋的办法。
但如果以后还需新增tencent的渠道,也得再复制一份task才行,显然,这样不利于脚本的复用以及项目的维护。
至此,我们完成了sdk单渠道的构建。
于是,首先需要完成的工作就是自动获取sdk中所有的渠道,参考fat-aar中获取依赖库的过程
sdk/fat-aar.gradle
我们也试着通过android下productFlavors相关的属性来获取其所有的渠道
sdk/fat-aar.gradle
在命令窗口执行gradle clean,成功后会发现结果中包含如下日志:
即表示我们成功获取到了sdk中所包含的渠道!
接下来,面对这些写死的配置,我们需要对使其与对应的渠道关联起来。要么设置一个代表当前渠道的变量,当渠道发现变化时通过其替换所有的配置;要么将每个渠道的关键配置都用一个Map对应起来。
目前我们采用的是后一种方案,将3个最重要的配置目录(classes、bundles和manifests)用三个HashMap,使其路径与渠道能够互相对应起来
在获取到渠道后,通过putAt将渠道名与该渠道对应的特定路径关联起来。
注:在gradle脚本中,双引号中可以通过$符号进行赋值,如果是对字符串的一部分进行赋值,可以通过${}来实现。如:上面脚本中,如果product为baidu,则”…/classes/$product/release”等价于classes/baidu/release”,而”…/bundles/${product}Release”则等价于”…/bundles/baiduRelease”.此外,单引号中的字符串是纯字符串,不能进行赋值。
接下来,就遇到了一个大问题,对于’generateBaiduReleaseAssets’这种动态的task,又该如何生成呢?
在查阅gradle如何动态生成task时,意外发现了下面这种通过字符串查找task的写法:
于是突发奇想,产生了下述的写法
结果这个地方编译也没报错,于是将其他的自定义embeded相关task也都改为这种方式,在自定义task中又出现了另外一个问题,比如:
在合并jni、assets这些资源时,也需要根据不同的渠道配置不同的bundles目录。同时,不同渠道的bundle${Product}Release都依赖与同一个’embedJniLibs’也是很不合理的,最好能根据不同的渠道自动生成该渠道对应的”embedJniLibs${Product}” task,使各个渠道在打包时能够互不影响。
这样就相当于又提出了一个新的问题:如何根据渠道动态生成类似”embedJniLibs${Product}”这样的task呢?
鉴于前面tasks.getByName,想到了tasks是否也存在能够根据名称动态创建task的方法呢?
于是,就又将所有”embedded”相关的自定义task改为通过上述方式进行创建。最终,经过修改的fat-aar.gradle核心部分如下完整代码:
sdk/build.gradle
同样的流程,让每一个渠道在构建时都能够按照fat-aar.gradle的流程完成所有模块的合并操作。
在命令窗口运行gradle clean main命令,将生成的sdk-baidu-release.aar与sdk-alibaba-release.aar解压后反编译其classes.jar,可以确认base和sdk中的所有源码都已合并进去。
转载请注明出处:http://blog.csdn.net/u011370390/article/details/78636574
1.新增一个渠道,看其是否能够正常完成新渠道的构建。
2.新增依赖库,看其能否正常完成新依赖库的合并。
但在做这两项验证之前,我们还有个更重要的工作需要做——sdk的多渠道开发。
前文的构建实际上并没有体现出多渠道之间的不同,仅仅只是针对两个不同的渠道通过合并生成了两个名称不一样、内容完全一样的aar包。如果仅仅只是名称不一样,前面的工作其实本质上是毫无意义的。
好了,下面我们就来进行多渠道开发,使得通过同一次构建能够生成源码、内容不一样的sdk.
最终通过构建出来的sdk,能分别实现上述不同的功能,我们的目的就算达到了。
1.鼠标选中sdk/src目录;
2.右键->New->Folder->Java Folder;
3.点击”Target Source Set”下面的main,选中alibaba渠道;
4.点击右下角的Finish;
5.重复上述步骤,创建baidu渠道.
在sdk/src/main/java/com.tobenull.sdk路径下新建BaseSDK.java,如下:
sdk/src/main/java/com.tobenull.sdk.BaseSDK.java
同时删除该目录下原先的SDK.java
打开Android Studio的Build Variants窗口,设置sdk的module为baiduRelease或baiduDebug.在sdk/src/baidu/java路径下新建集成自BaseSDK的类SDK.java
sdk/src/baidu/java/com.tobenull.sdk.SDK.java
在sdk/src/baidu目录下新建assets目录,目录下新建baidu.conf配置文件。
在Build Variants窗口中切换sdk的module为alibabaRelease或alibabaDebug.在sdk/src/alibaba目录下新建assets目录,目录下新建alibaba.conf配置文件;在sdk/src/alibaba目录下新建jniLibs/armeabi目录,目录下放入libgaode.so文件。
将gaode-map.jar放入sdk/libs目录下,该jar包中仅有一个MapManager.java,如下:
为使该jar包只被打进alibaba渠道包,需要修改sdk/build.gradle文件,如下;
sdk/build.gradle
在sdk/src/alibaba/java目录下新建继承自BaseSDK的com.tobenull.sdk.SDK.java,如下:
sdk/src/alibaba/java/com.tobenull.sdk/SDK.java
至此,sdk多渠道开发需求已完成,接下来就到了见证奇迹的时刻了!
由于解压aar包得到的文件与bundles目录一样,此处就展开bundles目录,如下:
展开classes目录,如下:
两个渠道虽然生成的.class都是一样的,但是点进各自的SDK.class,可以发现其中一个是alibaba的,另一个是baidu的。其实在各自的渠道包中也可以添加一些独有的类更进一步地以示区别。这个工作我们就不在此处做了。
baidudemo使用sdk-baidu-release.aar
运行日志:
alibabademo使用sdk-alibaba-release.aar
运行日志:
这两个demo此处仅作为验证使用,在总结后续文章的过程中提交代码时会将其删除,后续我们将添加自动生成demo的功能,可以直接使用Android Studio打开生成的demo运行。这样就不用专门对demo工程进行维护了(自动生成的demo工程中的代码将与app工程中不同渠道的代码始终保持一致)。
然后,又以sdk单渠道aar包生成的方案为基础,进一步修改fat-aar.gradle中代码,使之支持多渠道sdk的构建。
接着,本文又对sdk多渠道开发做了一个简单的示范,并使用前面多渠道sdk构建的方案对该示范进行了构建,使之同时生成了代码不同、资源文件不同的多渠道aar包,并使用两个不同的demo对生成对不同aar包进行了验证。
最后,本来还有两个有待验证及若干需要优化的问题,鉴于本文长度过长,就将其留到后续文章中进行进一步说明与解决。
如果觉得本文对你进行android sdk开发有一定帮助,欢迎star该项目!
转载请注明出处:http://blog.csdn.net/u011370390/article/details/78636574
网上关于apk多渠道开发有很多说明,但关于sdk的则少的可怜,在开发过程中遇到问题时、多数时间也就只能参考外文资料及自己琢磨。
一.sdk多渠道构建
1.apk多渠道构建
官方文档2.sdk单渠道构建
与apk多渠道开发类似,sdk也需要先指定productFlavors,首先新建两个渠道baidu和alibabasdk/build.gradle
android { ... productFlavors { baidu{} alibaba{} } ... }
指定完后同步gradle,马上就会报错
v Gradle 'aar' project refresh failed Error:Could not get unknown property 'generateReleaseAssets' for project ':sdk' of type org.gradle.api.Project.
‘generateReleaseAssets’这个task找不到,这可是在配置sdk多渠道开发前,fat-aar.gradle中合并assets文件的task,怎么会找不到呢?
... // Merge Assets generateReleaseAssets.dependsOn embedAssets embedAssets.dependsOn prepareReleaseDependencies ...
于是将sdk中该productFlavors代码块注释,在app中添加试试
app/build.gradle
android { ... productFlavors { baidu{} alibaba{} } ... }
同步成功后,在命令后下输出一下命令:
gradle task
会发现好多task中都带有”Baidu”或”Alibaba”这两个渠道名称,
> Task :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Android tasks ------------- androidDependencies - Displays the Android dependencies of the project. signingReport - Displays the signing info for each variant. sourceSets - Prints out all the source sets defined in this project. Build tasks ----------- assemble - Assembles all variants of all applications and secondary packages. assembleAlibaba - Assembles all Alibaba builds. assembleAndroidTest - Assembles all the Test applications. assembleBaidu - Assembles all Baidu builds. assembleDebug - Assembles all Debug builds. assembleRelease - Assembles all Release builds. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. clean - Deletes the build directory. cleanBuildCache - Deletes the build cache directory. compileAlibabaDebugAndroidTestSources compileAlibabaDebugSources compileAlibabaDebugUnitTestSources compileAlibabaReleaseSources compileAlibabaReleaseUnitTestSources compileBaiduDebugAndroidTestSources compileBaiduDebugSources compileBaiduDebugUnitTestSources compileBaiduReleaseSources compileBaiduReleaseUnitTestSources compileDebugAndroidTestSources compileDebugSources compileDebugUnitTestSources compileReleaseSources compileReleaseUnitTestSources extractDebugAnnotations - Extracts Android annotations for the debug variant into the archive file extractReleaseAnnotations - Extracts Android annotations for the release variant into the archive file mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests. Build Setup tasks ----------------- init - Initializes a new Gradle build. wrapper - Generates Gradle wrapper files. Help tasks ---------- buildEnvironment - Displays all buildscript dependencies declared in root project 'aar'. components - Displays the components produced by root project 'aar'. [incubating] dependencies - Displays all dependencies declared in root project 'aar'. dependencyInsight - Displays the insight into a specific dependency in root project 'aar'. dependentComponents - Displays the dependent components of components in root project 'aar'. [incubating] help - Displays a help message. model - Displays the configuration model of root project 'aar'. [incubating] projects - Displays the sub-projects of root project 'aar'. properties - Displays the properties of root project 'aar'. tasks - Displays the tasks runnable from root project 'aar' (some of the displayed tasks may belong to subprojects). Install tasks ------------- installAlibabaDebug - Installs the DebugAlibaba build. installAlibabaDebugAndroidTest - Installs the android (on device) tests for the AlibabaDebug build. installBaiduDebug - Installs the DebugBaidu build. installBaiduDebugAndroidTest - Installs the android (on device) tests for the BaiduDebug build. installDebugAndroidTest - Installs the android (on device) tests for the Debug build. uninstallAlibabaDebug - Uninstalls the DebugAlibaba build. uninstallAlibabaDebugAndroidTest - Uninstalls the android (on device) tests for the AlibabaDebug build. uninstallAlibabaRelease - Uninstalls the ReleaseAlibaba build. uninstallAll - Uninstall all applications. uninstallBaiduDebug - Uninstalls the DebugBaidu build. uninstallBaiduDebugAndroidTest - Uninstalls the android (on device) tests for the BaiduDebug build. uninstallBaiduRelease - Uninstalls the ReleaseBaidu build. uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build. Verification tasks ------------------ check - Runs all checks. connectedAlibabaDebugAndroidTest - Installs and runs the tests for alibabaDebug on connected devices. connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices. connectedBaiduDebugAndroidTest - Installs and runs the tests for baiduDebug on connected devices. connectedCheck - Runs all device checks on currently connected devices. connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices. deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers. deviceCheck - Runs all device checks using Device Providers and Test Servers. lint - Runs lint on all variants. lintAlibabaDebug - Runs lint on the AlibabaDebug build. lintAlibabaRelease - Runs lint on the AlibabaRelease build. lintBaiduDebug - Runs lint on the BaiduDebug build. lintBaiduRelease - Runs lint on the BaiduRelease build. lintDebug - Runs lint on the Debug build. lintRelease - Runs lint on the Release build. test - Run unit tests for all variants. testAlibabaDebugUnitTest - Run unit tests for the alibabaDebug build. testAlibabaReleaseUnitTest - Run unit tests for the alibabaRelease build. testBaiduDebugUnitTest - Run unit tests for the baiduDebug build. testBaiduReleaseUnitTest - Run unit tests for the baiduRelease build. testDebugUnitTest - Run unit tests for the debug build. testReleaseUnitTest - Run unit tests for the release build. To see all tasks and more detail, run gradle tasks --all To see more detail about a task, run gradle help --task <task> BUILD SUCCESSFUL in 5s 1 actionable task: 1 executed
而注释掉app中的productFlavors后就没有了该渠道字样,这说明Gradle在构建Android的过程中,其构建脚本并非固定的,而是随着渠道名称实时变化的、这样才能针对不同的渠道完成相应的构建。
于是,我们再打开sdk/build.gradle中的productFlavors代码块,回到刚刚报的这个错上面来
v Gradle 'aar' project refresh failed Error:Could not get unknown property 'generateReleaseAssets' for project ':sdk' of type org.gradle.api.Project.
既然不能找到这个’generateReleaseAssets’ task,那我们就尝试着给它加上一个渠道号’generateBaiduReleaseAssets’,再同步,会发现报以下错:
v Gradle 'aar' project refresh failed Error:Could not get unknown property 'prepareReleaseDependencies' for project ':sdk' of type org.gradle.api.Project.
虽然还在报错,但明显不是先前的那个task了,而这个task也是fat-aar.gradle中的task,于是,我们考虑将该.gradle文件中所有系统相关的task都加上“Baidu“这个渠道号。
一般渠道号都是加在驼峰命名法的第一个驼峰处,如:’generateReleaseAssets’ –> ‘generateBaiduReleaseAssets’
只有’transformNativeLibsWithSyncJniLibsForBaiduRelease’这个task是个例外,加在For后面。
gradle刷新成功后,在命令窗口执行以下命令:
gradle clean main
出现以下错误
FAILURE: Build failed with an exception. * Where: Script '/Users/tobenull/xxx/xxx/aar/sdk/fat-aar.gradle' line: 499 * What went wrong: Execution failed for task ':sdk:embedManifests'. > java.lang.RuntimeException: Manifest merger failed : uses-sdk:minSdkVersion 1 cannot be smaller than version 14 declared in library /Users/tobenull/xxx/xxx/aar/base/build/intermediates/bundles/default/AndroidManifest.xml Suggestion: use tools:overrideLibrary="com.tobenull.base" to force usage
合并AndroidManifest.xml时minSdkVersion版本不一致引起的,在base和sdk两个的AndroidManifest.xml中各自指定minSdkVersion为14:
<uses-sdk android:minSdkVersion="14" />
再次执行gradle clean main,报如下错:
FAILURE: Build failed with an exception. * What went wrong: Cannot expand ZIP '/Users/tobenull/xxx/xxx/aar/sdk/build/intermediates/bundles/default/classes.jar' as it does not exist.
打对应的jar时找不到了,展开bundles目录,如下:
v sdk v build ... v intermediates ... v bundles > alibabaDebug > alibabaRelease > baiduDebug v baiduRelease aidl > assets > jni libs > res AndroidManifest.xml classes.jar R.txt ...
很显然,加上多渠道包后,打aar包的流程与发现了一个小变化,此时,我们先将该jar包的路径改为’Baidu’渠道的路径,如下
{rootProject}/build.gradle
task createJar(type: Jar) { from zipTree('sdk/build/intermediates/bundles/baiduRelease/classes.jar') into('assets') { from fileTree(dir: 'sdk/build/intermediates/baiduRelease/default/assets') } baseName = "SDK-${rootProject.ext.buildTime}-release" destinationDir = file('sdk/build/outputs/aar') } task zipSo(type: Zip) { from('sdk/build/intermediates/bundles/baiduRelease/jni') baseName = "SDK-${rootProject.ext.buildTime}-so" destinationDir = file('sdk/build/outputs/aar') }
此时再执行gradle clean main命令,终于成功了,展开sdk/build/outputs/aar目录,如下:
v sdk v build ... v outputs v aar SDK-20171126_155451.574-release.jar SDK-20171126_155451.574-so.zip sdk-alibaba-debug.aar sdk-alibaba-release.aar sdk-baidu-debug.aar sdk-baidu-release.aar ...
很显然,生成的jar包和so.zip是从”baidu”这个渠道中生成的,而自动生成的4个aar包是否能满足我们的要求呢?
展开sdk/outputs/intermediates/classes目录下baidu和alibaba的release目录,如下:
v sdk v build ... v intermediates > ... ... v classes v alibaba > debug v release > android v com v tobenull v base R.class v sdk BuildConfig.class R.class SDK.class v baidu > debug v release > android v com v tobenull v base R.class v sdk BuildConfig.class R.class SDK.class v release v com v tobenull v base BaseManager.class BuildConfig.class TLog.class ...
可以发现,此时base库中的.class文件并没有合并到任何一个渠道的sdk中来,不管是解压生成的各渠道aar包并反编译其classes.jar还是直接反编译bundles中各渠道包中的classes.jar都能确认这一点。问题很可能出现在fat-aar.gradle中
sdk/fat-aar.gradle
// Change backslash to forward slash on windows ... ext.classs_release_dir = "$build_dir/intermediates/classes/release"; ext.bundle_release_dir = "$build_dir/intermediates/bundles/release"; ...
该脚本中定义的路径是写死的,结合前面的发现,base库的.class确认进入了sdk/build/intermediates/classes的release目录。此时,我们暂且先将fat-aar.gradle中的这两个路径做如下修改:
ext.classs_release_dir = "$build_dir/intermediates/classes/baidu/release"; ext.bundle_release_dir = "$build_dir/intermediates/bundles/baidu/release";
再次执行gradle clean main命令,成功后,会发现
v sdk v build v intermediates > ... ... v classes v alibaba > debug v release > android v com v tobenull v base R.class v sdk BuildConfig.class R.class SDK.class v baidu > debug v release > android v com v tobenull v base BaseManager.class BuildConfig.class R.class TLob.class v sdk BuildConfig.class R.class SDK.class ...
即生成的sdk-baidu-release.aar包中成功打进了base的.class文件,而其他3个aar中则只包含sdk中的.class.很好理解,我们只对baidu这一渠道的release包进行了合并处理,如果需要对alibaba渠道的包也合并base库,就需要将fat-aar.gradle中的task按照’Alibaba’再复制一份即可。这是不用动脑筋的办法。
但如果以后还需新增tencent的渠道,也得再复制一份task才行,显然,这样不利于脚本的复用以及项目的维护。
至此,我们完成了sdk单渠道的构建。
3.sdk多渠道构建
完成了单渠道的构建,多渠道的构建只需考虑如何遍历多个渠道,并对各渠道独立执行上述单渠道的构建过程即可。于是,首先需要完成的工作就是自动获取sdk中所有的渠道,参考fat-aar中获取依赖库的过程
sdk/fat-aar.gradle
def dependencies = new ArrayList(configurations.embedded.resolvedConfiguration.firstLevelModuleDependencies) dependencies.reverseEach { ... }
我们也试着通过android下productFlavors相关的属性来获取其所有的渠道
sdk/fat-aar.gradle
... def products = new ArrayList(android.productFlavors) println "products: " + products.size() if (dependencies.size() > 0) { products.each { String product = it.name String Product = product.capitalize() println "Product: $Product, product: $product" ... } }
在命令窗口执行gradle clean,成功后会发现结果中包含如下日志:
... products: 2 Product: Alibaba, product: alibaba Product: Baidu, product: baidu ...
即表示我们成功获取到了sdk中所包含的渠道!
接下来,面对这些写死的配置,我们需要对使其与对应的渠道关联起来。要么设置一个代表当前渠道的变量,当渠道发现变化时通过其替换所有的配置;要么将每个渠道的关键配置都用一个Map对应起来。
...
ext.exploded_aar_dir = "$build_dir/intermediates/exploded-aar";
ext.classs_release_dir = "$build_dir/intermediates/classes/baidu/release"; ext.bundle_release_dir = "$build_dir/intermediates/bundles/baidu/release";
ext.manifest_aaapt_dir = "$build_dir/intermediates/manifests/aapt/release";
ext.generated_rsrc_dir = "$build_dir/generated/source/r/release";
ext.base_r2x_dir = "$build_dir/fat-aar/release/";
...
目前我们采用的是后一种方案,将3个最重要的配置目录(classes、bundles和manifests)用三个HashMap,使其路径与渠道能够互相对应起来
... //ext.classs_release_dir = "$build_dir/intermediates/classes/baidu/release"; //ext.bundle_release_dir = "$build_dir/intermediates/bundles/baidu/release"; //ext.manifest_aaapt_dir = "$build_dir/intermediates/manifests/aapt/release"; ext.class_release_dir = new HashMap<String, String>() ext.bundle_release_dir = new HashMap<String, String>() ext.manifest_aaapt_dir = new HashMap<String, String>() ... afterEvaluate { ... def products = new ArrayList(android.productFlavors) println "products: " + products.size() if (dependencies.size() > 0) { products.each { String product = it.name String Product = product.capitalize() println "Product: $Product, product: $product" ext.class_release_dir.putAt(product, "$build_dir/intermediates/classes/$product/release") ext.bundle_release_dir.putAt(product, "$build_dir/intermediates/bundles/${product}Release") ext.manifest_aaapt_dir.putAt(product, "$build_dir/intermediates/manifests/aapt/$product/release") } ... } ... }
在获取到渠道后,通过putAt将渠道名与该渠道对应的特定路径关联起来。
注:在gradle脚本中,双引号中可以通过$符号进行赋值,如果是对字符串的一部分进行赋值,可以通过${}来实现。如:上面脚本中,如果product为baidu,则”…/classes/$product/release”等价于classes/baidu/release”,而”…/bundles/${product}Release”则等价于”…/bundles/baiduRelease”.此外,单引号中的字符串是纯字符串,不能进行赋值。
接下来,就遇到了一个大问题,对于’generateBaiduReleaseAssets’这种动态的task,又该如何生成呢?
在查阅gradle如何动态生成task时,意外发现了下面这种通过字符串查找task的写法:
tasks.getByName('generateReleaseAssets')
于是突发奇想,产生了下述的写法
// Merge Assets // generateBaiduReleaseAssets.dependsOn embedAssets // embedAssets.dependsOn prepareBaiduReleaseDependencies tasks.getByName("generate${Product}ReleaseAssets").dependsOn embedAssets embedAssets.dependsOn "prepare${Product}ReleaseDependencies"
结果这个地方编译也没报错,于是将其他的自定义embeded相关task也都改为这种方式,在自定义task中又出现了另外一个问题,比如:
task embedJniLibs << { println "Running FAT-AAR Task :embedJniLibs" embeddedAarDirs.each { aarPath -> println "======= Copying JNI from $aarPath/jni" // Copy JNI Folders copy { from fileTree(dir: "$aarPath/jni") into file("$bundle_release_dir/jni") } } }
在合并jni、assets这些资源时,也需要根据不同的渠道配置不同的bundles目录。同时,不同渠道的bundle${Product}Release都依赖与同一个’embedJniLibs’也是很不合理的,最好能根据不同的渠道自动生成该渠道对应的”embedJniLibs${Product}” task,使各个渠道在打包时能够互不影响。
bundleBaiduRelease.dependsOn embedJniLibs embedJniLibs.dependsOn transformNativeLibsWithSyncJniLibsForBaiduRelease
这样就相当于又提出了一个新的问题:如何根据渠道动态生成类似”embedJniLibs${Product}”这样的task呢?
鉴于前面tasks.getByName,想到了tasks是否也存在能够根据名称动态创建task的方法呢?
tasks.create("embedJniLibs${Product}").doLast { }
于是,就又将所有”embedded”相关的自定义task改为通过上述方式进行创建。最终,经过修改的fat-aar.gradle核心部分如下完整代码:
sdk/build.gradle
ext.class_release_dir = new HashMap<String, String>()
ext.bundle_release_dir = new HashMap<String, String>()
ext.manifest_aaapt_dir = new HashMap<String, String>()
afterEvaluate {
// the list of dependency must be reversed to use the right overlay order.
def dependencies = new ArrayList(configurations.embedded.resolvedConfiguration.firstLevelModuleDependencies) dependencies.reverseEach { ... }
def products = new ArrayList(android.productFlavors)
println "products: " + products.size()
if (dependencies.size() > 0) {
products.each {
String product = it.name
String Product = product.capitalize()
println "Product: $Product, product: $product"
ext.class_release_dir.putAt(product, "$build_dir/intermediates/classes/$product/release")
ext.bundle_release_dir.putAt(product, "$build_dir/intermediates/bundles/${product}Release")
ext.manifest_aaapt_dir.putAt(product, "$build_dir/intermediates/manifests/aapt/$product/release")
// Merge Assets
tasks.getByName("generate${Product}ReleaseAssets").dependsOn embedAssets
embedAssets.dependsOn "prepare${Product}ReleaseDependencies"
// Embed Resources by overwriting the inputResourceSets
tasks.create("embedLibraryResources$Product").doLast {
println "Running FAT-AAR Task :embedLibraryResources"
def oldInputResourceSet = tasks.getByName("package${Product}ReleaseResources").inputResourceSets
tasks.getByName("package${Product}ReleaseResources").conventionMapping.map("inputResourceSets") {
getMergedInputResourceSets(oldInputResourceSet)
}
}
tasks.getByName("package${Product}ReleaseResources").dependsOn "embedLibraryResources$Product"
tasks.getByName("embedLibraryResources$Product").dependsOn "prepare${Product}ReleaseDependencies"
// Embed JNI Libraries
tasks.create("embedJniLibs$Product").doLast {
println "Running FAT-AAR Task :embedJniLibs"
embeddedAarDirs.each { aarPath ->
println "======= Copying JNI from $aarPath/jni"
// Copy JNI Folders
copy {
from fileTree(dir: "$aarPath/jni")
into file(bundle_release_dir.getAt(product) + "/jni")
}
}
}
tasks.getByName("bundle${Product}Release").dependsOn "embedJniLibs$Product"
tasks.getByName("embedJniLibs$Product").dependsOn "transformNativeLibsWithSyncJniLibsFor${Product}Release"
// Merge Embedded Manifests
tasks.create("embedManifests$Product").doLast {
...
}
tasks.getByName("bundle${Product}Release").dependsOn "embedManifests$Product"
tasks.getByName("embedManifests$Product").dependsOn "process${Product}ReleaseManifest"
// Merge proguard files
tasks.create("embedProguard$Product").doLast {
...
}
tasks.getByName("embedLibraryResources$Product").dependsOn "embedProguard$Product"
tasks.getByName("embedProguard$Product").dependsOn tasks.getByName("prepare${Product}ReleaseDependencies")
// Generate R.java files
tasks.getByName("compile${Product}ReleaseJavaWithJavac").dependsOn generateRJava
generateRJava.dependsOn "process${Product}ReleaseResources"
// Bundle the java classes
tasks.create("collectRClass$Product").doLast {
println "COLLECTRCLASS"
delete base_r2x_dir
mkdir base_r2x_dir
copy {
from class_release_dir.getAt(product)
include embeddedRClasses
into base_r2x_dir
}
}
tasks.create("embedJavaJars$Product").dependsOn("collectRClass$Product").doLast {
println "Running FAT-AAR Task :embedJavaJars: "
embeddedAarFiles.each { artifact ->
println "Running FAT-AAR Task :embedJavaJars, AbsolutePath: " + artifact.file.getAbsolutePath()
FileTree aarFileTree = zipTree(artifact.file.getAbsolutePath())
def aarFile = aarFileTree.files.find { it.name.contains("classes.jar") }
println "Running FAT-AAR Task :embedJavaJars, aarFile: " + aarFile.name
copy {
from zipTree(aarFile)
into class_release_dir.getAt(product)
}
}
embeddedAarDirs.each { aarPath ->
println "Running FAT-AAR Task :embedJavaJars, aarPath: $aarPath"
// Copy all additional jar files to bundle lib
FileTree jars = fileTree(dir: "$aarPath", include: '*.jar', exclude: 'classes.jar')
jars += fileTree(dir: "$aarPath/libs", include: '*.jar')
println "jars: $jars, size: " + jars.size()
jars.each {
println "jars: " + it.name + ", " + it.absolutePath
def jarFile = it.absoluteFile
copy {
from zipTree(jarFile)
into class_release_dir.getAt(product)
}
}
}
}
tasks.getByName("bundle${Product}Release").dependsOn "embedJavaJars$Product"
tasks.getByName("embedJavaJars$Product").dependsOn "compile${Product}ReleaseJavaWithJavac"
// If proguard is enabled, run the tasks that bundleRelease should depend on before proguard
if (tasks.findByPath('proguardRelease') != null) {
tasks.getByName(proguardRelease).dependsOn "embedJavaJars$Product"
} else if (tasks.findByPath('transformClassesAndResourcesWithProguardForRelease') != null) {
transformClassesAndResourcesWithProguardForRelease.dependsOn "embedJavaJars$Product"
}
}
}
}
task embedAssets << {
println "Running FAT-AAR Task :embedAssets"
embeddedAarDirs.each { aarPath ->
// Merge Assets
android.sourceSets.main.assets.srcDirs += file("$aarPath/assets")
}
}
task generateRJava << {
...
}
private void save(XmlDocument xmlDocument, File out) {
...
}
...
同样的流程,让每一个渠道在构建时都能够按照fat-aar.gradle的流程完成所有模块的合并操作。
在命令窗口运行gradle clean main命令,将生成的sdk-baidu-release.aar与sdk-alibaba-release.aar解压后反编译其classes.jar,可以确认base和sdk中的所有源码都已合并进去。
转载请注明出处:http://blog.csdn.net/u011370390/article/details/78636574
二.sdk多渠道开发
前文讲的都是sdk的多渠道构建过程,最终实现了对于给定的两个渠道baidu和alibaba合并所依赖module各项资源、生成独立aar包的效果。其实严格来说,工作并没有做完,我们还需进行两项验证:1.新增一个渠道,看其是否能够正常完成新渠道的构建。
2.新增依赖库,看其能否正常完成新依赖库的合并。
但在做这两项验证之前,我们还有个更重要的工作需要做——sdk的多渠道开发。
前文的构建实际上并没有体现出多渠道之间的不同,仅仅只是针对两个不同的渠道通过合并生成了两个名称不一样、内容完全一样的aar包。如果仅仅只是名称不一样,前面的工作其实本质上是毫无意义的。
好了,下面我们就来进行多渠道开发,使得通过同一次构建能够生成源码、内容不一样的sdk.
1.sdk多渠道开发需求
首先,我们假定针对baidu和alibaba两个sdk有以下几个不同需求点:baidu: 1.初始化除了走公共的初始化流程外,还需走baidu的初始化流程; 2.提供搜索接口可供第三方调用。 alibaba:1.初始化除了走公共的初始化流程外,还需走alibaba的初始化流程; 2.提供购物接口可供第三方调用; 3.接入gaode地图的jar包及相应so文件。
最终通过构建出来的sdk,能分别实现上述不同的功能,我们的目的就算达到了。
2.sdk多渠道开发过程
建立多渠道包1.鼠标选中sdk/src目录;
2.右键->New->Folder->Java Folder;
3.点击”Target Source Set”下面的main,选中alibaba渠道;
4.点击右下角的Finish;
5.重复上述步骤,创建baidu渠道.
在sdk/src/main/java/com.tobenull.sdk路径下新建BaseSDK.java,如下:
sdk/src/main/java/com.tobenull.sdk.BaseSDK.java
package com.tobenull.sdk; import com.tobenull.base.BaseManager; import com.tobenull.base.TLog; /** * Created by tobenull on 11/26/17. */ public abstract class BaseSDK { protected abstract String getProductFlavor(); protected void init() { TLog.d("BaseSDK->init, " + getProductFlavor()); BaseManager.getInstance().init(); } }
同时删除该目录下原先的SDK.java
打开Android Studio的Build Variants窗口,设置sdk的module为baiduRelease或baiduDebug.在sdk/src/baidu/java路径下新建集成自BaseSDK的类SDK.java
sdk/src/baidu/java/com.tobenull.sdk.SDK.java
package com.tobenull.sdk; import com.tobenull.base.TLog; /** * Created by tobenull on 11/26/17. */ public class SDK extends BaseSDK { private static SDK mInstance = new SDK(); private SDK() {} public static SDK getInstance() { return mInstance; } @Override public void init() { super.init(); initBaidu(); } private void initBaidu() { TLog.d("SDK->initBaidu, init baidu..."); } @Override protected String getProductFlavor() { return "baidu"; } public void search(String key) { TLog.d("SDK->search, you want to search " + key); } }
在sdk/src/baidu目录下新建assets目录,目录下新建baidu.conf配置文件。
在Build Variants窗口中切换sdk的module为alibabaRelease或alibabaDebug.在sdk/src/alibaba目录下新建assets目录,目录下新建alibaba.conf配置文件;在sdk/src/alibaba目录下新建jniLibs/armeabi目录,目录下放入libgaode.so文件。
将gaode-map.jar放入sdk/libs目录下,该jar包中仅有一个MapManager.java,如下:
package com.tobenull.gaode; import android.util.Log; public class MapManager { private static MapManager instance = new MapManager(); private MapManager() { } public static MapManager getInstance() { return instance; } public void init() { Log.d("null-tobe", "MapManager->init, init gaode map..."); } }
为使该jar包只被打进alibaba渠道包,需要修改sdk/build.gradle文件,如下;
sdk/build.gradle
dependencies { if (rootProject.ext.debug) { compile project(':base') } else { embedded project(':base') } // compile fileTree(dir: 'libs', include: ['*.jar']) alibabaCompile file('libs/gaode-map.jar') ... }
在sdk/src/alibaba/java目录下新建继承自BaseSDK的com.tobenull.sdk.SDK.java,如下:
sdk/src/alibaba/java/com.tobenull.sdk/SDK.java
package com.tobenull.sdk; import com.tobenull.base.TLog; import com.tobenull.gaode.MapManager; /** * Created by tobenull on 11/26/17. */ public class SDK extends BaseSDK { private static SDK mInstance = new SDK(); private SDK() {} public static SDK getInstance() { return mInstance; } @Override protected String getProductFlavor() { return "alibaba"; } @Override public void init() { super.init(); initAlibaba(); } private void initAlibaba() { TLog.d("SDK->initAlibaba, init alibaba..."); MapManager.getInstance().init(); } public void buy(String product) { TLog.d("SDK->search, you want to buy " + product); } }
至此,sdk多渠道开发需求已完成,接下来就到了见证奇迹的时刻了!
3.sdk多渠道开发构建
在命令窗口执行以下命令gradle clean main
由于解压aar包得到的文件与bundles目录一样,此处就展开bundles目录,如下:
v sdk v build ... v intermediates ... v bundles > alibabaDebug v alibabaRelease aidl v assets alibaba.conf base.conf sdk.conf v jni v armeabi libbase.so libgaode.so libsdk.so v libs gaode-map.jar > res AndroidManifest.orig.xml AndroidManifest.xml classes.jar R.txt > baiduDebug v baiduRelease aidl v assets baidu.conf base.conf sdk.conf v jni v armeabi libbase.so libsdk.so libs > res AndroidManifest.orig.xml AndroidManifest.xml classes.jar R.txt
展开classes目录,如下:
v sdk v build ... v intermediates ... v classes v alibaba > debug v release > android v com v tobenull v base BaseManager.class BuildConfig.class R.class TLog.class v sdk BaseSDK.class BuildConfig.class R.class SDK.class v baidu > debug v release > android v com v tobenull v base BaseManager.class BuildConfig.class R.class TLog.class v sdk BaseSDK.class BuildConfig.class R.class SDK.class ...
两个渠道虽然生成的.class都是一样的,但是点进各自的SDK.class,可以发现其中一个是alibaba的,另一个是baidu的。其实在各自的渠道包中也可以添加一些独有的类更进一步地以示区别。这个工作我们就不在此处做了。
4.验证生成的两个多渠道sdk
新建两个app module进行验证baidudemo使用sdk-baidu-release.aar
运行日志:
11-26 22:10:51.721 30315-30315/com.tobenull.baidudemo D/null-tobe: BaseSDK->init, baidu 11-26 22:10:51.721 30315-30315/com.tobenull.baidudemo D/null-tobe: BaseManager->init 11-26 22:10:51.721 30315-30315/com.tobenull.baidudemo D/null-tobe: SDK->initBaidu, init baidu... 11-26 22:10:54.549 30315-30315/com.tobenull.baidudemo D/null-tobe: SDK->search, you want to search BAIDU
alibabademo使用sdk-alibaba-release.aar
运行日志:
11-26 22:17:05.869 32627-32627/com.tobenull.alibabademo D/null-tobe: BaseSDK->init, alibaba 11-26 22:17:05.870 32627-32627/com.tobenull.alibabademo D/null-tobe: BaseManager->init 11-26 22:17:05.870 32627-32627/com.tobenull.alibabademo D/null-tobe: SDK->initAlibaba, init alibaba... 11-26 22:17:05.870 32627-32627/com.tobenull.alibabademo D/null-tobe: MapManager->init, init gaode map... 11-26 22:17:08.960 32627-32627/com.tobenull.alibabademo D/null-tobe: SDK->search, you want to buy Clothes
这两个demo此处仅作为验证使用,在总结后续文章的过程中提交代码时会将其删除,后续我们将添加自动生成demo的功能,可以直接使用Android Studio打开生成的demo运行。这样就不用专门对demo工程进行维护了(自动生成的demo工程中的代码将与app工程中不同渠道的代码始终保持一致)。
三.sdk多渠道开发构建的总结
本文基于fat-aar.gradle合并库工程生成aar包的方案,首先修改其脚本使其适用于sdk指定单渠道aar包的生成。然后,又以sdk单渠道aar包生成的方案为基础,进一步修改fat-aar.gradle中代码,使之支持多渠道sdk的构建。
接着,本文又对sdk多渠道开发做了一个简单的示范,并使用前面多渠道sdk构建的方案对该示范进行了构建,使之同时生成了代码不同、资源文件不同的多渠道aar包,并使用两个不同的demo对生成对不同aar包进行了验证。
最后,本来还有两个有待验证及若干需要优化的问题,鉴于本文长度过长,就将其留到后续文章中进行进一步说明与解决。
如果觉得本文对你进行android sdk开发有一定帮助,欢迎star该项目!
转载请注明出处:http://blog.csdn.net/u011370390/article/details/78636574
相关文章推荐
- Android多渠道SDK开发心得(6)——sdk日志注入构建时间戳
- Android多渠道SDK开发心得(2)——合并的aar包sdk
- Android多渠道SDK开发心得(3)——生成jar包和so
- [置顶] Android多渠道SDK开发心得
- Android多渠道SDK开发心得(5)——多渠道sdk的调试
- Android多渠道SDK开发心得(7)——自动生成demo
- Android多渠道SDK开发心得(8)——关于sdk资源的引用
- Android多渠道SDK开发心得(9)——自动生成文档
- Android多渠道SDK开发心得(1)——最简单的aar包sdk
- 关于做Android+J2ee系统集成开发的一点心得
- Android开发环境搭建——Android SDK Manager 下载API
- ArcGIS Runtime SDK for Android开发之调用GP服务(异步调用)
- Android Service 开发实践过程中的心得点滴记录
- Android 开发环境配置图文教程(jdk+eclipse+android sdk)
- Android开发环境安装SDK后提示parseSdkContent failed的解决办法
- 史上最全Android开发资料:资源、UI、函数库、测试、构建全套教程
- Android 开发环境配置图文教程(jdk+eclipse+android sdk)
- eclipse 搭建Android 开发环境(ADT安装和sdk下载,选择)
- Unity Android平台下插件/SDK开发通用流程