Android多渠道SDK开发心得(5)——多渠道sdk的调试
一.多渠道sdk的验证
1.新增库验证
新建http库工程,仅包含如下一个类NetManager.java
http/src/main/java/com.tobenull.http.NetManager.java
package com.tobenull.http;import android.util.Log;/** * Created by tobenull on 11/27/17. */public class NetManager { private static NetManager instance = new NetManager(); private NetManager(){} public static NetManager getInstance() { return instance; } public void init() { Log.d("null-tobe", "NetManager->init"); }}
修改各个库之间的依赖关系,如下
sdk->base->http
sdk/build.gradle
...android { ...}dependencies { if (rootProject.ext.debug) { compile project(':base') } else { embedded project(':base') embedded project(':http') } ...}
base/build.gradle
...dependencies { ... compile project(':http')}
在命令窗口输入以下命令
gradle clean main
解压sdk/build/outputs/aar目录下的sdk-alibaba-release.aar和sdk-baidu-release.aar并反编译其classes.jar,均如下:
v com.tobenull v base > BaseManager.class > BuildConfig.class > TLog.class v http > BuildConfig.class > NetManager.class v sdk > BaseSDK.class > BuildConfig.class > SDK.class
而且两个渠道的SDK.class中内容分别为alibaba和baidu两个渠道的源码。
经过以上的步骤,我们只是新增了一个http库并添加了依赖配置,并没有修改合并脚本相关的任何代码,最终构建得到了符合预期的sdk(将新增的http库也合并进来),由此可见,我们的合并脚本是没问题的。
2.新增渠道验证
修改sdk/build.gradle脚本,新增”tencent”渠道,如下
sdk/build.gradle
...android { ... productFlavors { baidu{} alibaba{} tencent{} } ...}...
在命令窗口执行gradle clean main命令后,展开sdk/outputs/aar/目录
v sdk v build ... v outputs v aar ... sdk-alibaba-debug.aar sdk-alibaba-release.aar sdk-baidu-debug.aar sdk-baidu-release.aar sdk-tencent-debug.aar sdk-tencent-release.aar ...
解压sdk-tencent-release.aar包并反编译其中的classes.jar,如下
v com.tobenull v base > BaseManager.class > BuildConfig.class > TLog.class v http > BuildConfig.class > NetManager.class v sdk > BaseSDK.class > BuildConfig.class
因为我们没有在sdk库中新建tencent的渠道,所以打出来的sdk中只有main的内容,如果有兴趣,可以试着仿照baidu或alibaba渠道新建tencent渠道并实现SDK类供第三方调用。
经过上述步骤,我们在没有修改打版脚本的情况下只是新建了一个tencent渠道,执行构建命令就成功地生成了tencent渠道的aar包,说明我们的合并脚本是没有问题的。
二.多渠道sdk的调试
经过前面的文章,我们已经完成了sdk多渠道开发与构建的工作,sdk的调试我们会在app工程中也以多渠道的方式进行。设置{rootProject}/build.gradle中的debug为true、采用正常的库工程依赖方式,当调试没问题时上传代码,服务端设置debug为false进行sdk的多渠道构建。
在将{rootProject}/build.gradle中的debug设为true后,同步后会发现App.java中会报SDK找不到的提示,这是因为我们app工程只是依赖于sdk,没有设置对各个渠道的分别依赖,而SDK.java在sdk库的main中又不存在,需要对多个渠道进行单独依赖,如下:
app/build.gradle
dependencies { if (rootProject.ext.debug) {// compile project(':sdk') baiduCompile project(path: ':sdk', configuration: 'baiduRelease') alibabaCompile project(path: ':sdk', configuration: 'alibabaRelease') tencentCompile project(path: ':sdk', configuration: 'tencentRelease') } else { ... } ...}
在com.android.tools.build:gradle 3.0.0以前的版本,依赖默认使用的都是Compile,而多渠道中可以针对特定的渠道设置特定的依赖,如此处的baiduCompile等。
由于sdk中有多个不同的渠道,所以在进行依赖时需要指定configuration,这样当我们切换app工程中的渠道时、其依赖的sdk也会自动切换到对应的渠道上来。
此时如果报以下错:
Error:Project :app declares a dependency from configuration 'alibabaCompile' to configuration 'alibabaRelease' which is not declared in the descriptor for project :sdk.
需要确保sdk库中配置多渠道的位置设置publishNonDefault为true
sdk/build.gradle
android { ... publishNonDefault true productFlavors { ... } ...}
同步成功后,我们就可以针对不同的渠道通过app工程的调用对各渠道sdk进行调试了。
打开android studio的Build Variants窗口,默认显示如下:
module | Build Variant app | alibabaDebug base | debug http | debug sdk | alibabaRlease
运行app,打开日志过滤”null-“,demo运行日志如下:
12-16 14:46:14.238 10331-10331/com.tobenull.multiflavorsdk D/null-tobe: BaseSDK->init, alibaba12-16 14:46:14.238 10331-10331/com.tobenull.multiflavorsdk D/null-tobe: BaseManager->init12-16 14:46:14.238 10331-10331/com.tobenull.multiflavorsdk D/null-tobe: SDK->initAlibaba, init alibaba...12-16 14:46:14.238 10331-10331/com.tobenull.multiflavorsdk D/null-tobe: MapManager->init, init gaode map...
点击”alibabaDebug”,切换为”baiduDebug”,重新运行app,日志如下:
12-16 14:49:40.882 11903-11903/? D/null-tobe: BaseSDK->init, baidu12-16 14:49:40.882 11903-11903/? D/null-tobe: BaseManager->init12-16 14:49:40.882 11903-11903/? D/null-tobe: SDK->initBaidu, init baidu...
根据上述日志的变化,即可发现demo依赖的渠道已由alibaba变为了baidu.从而,我们可以使用app工程对多个渠道sdk进行调试,当调试通过后,上传库工程源码进行打版、生成可用的aar包。
此外,我们也需要对app进行多渠道配置,main目录存放公共代码、各渠道中存放对应渠道的测试代码。这样一方面可以避免app测试工程在来回切换渠道时由于各渠道提供接口的不一致出现混乱,另一方面我们后续可以根据各渠道和main中的测试源码自动生成demo.
为app工程建立多渠道包
鼠标选中app/src目录、右键->New->Folder->Java Folder,点击Target Source Set下的main,选择alibaba,finish,即完成了alibaba渠道的创建。使用同样的方式建立baidu、tencent渠道。
使用mvp模式,在main中定义统一的接口及view,在各渠道中各自实现相应的presenter.
代码如下:
app/src/main/java/com/tobenull/multiflavorsdk/App.java
package com.tobenull.multiflavorsdk;import android.app.Application;import com.tobenull.sdk.SDK;/** * Created by tobenull on 11/25/17. */public class App extends Application { @Override public void onCreate() { super.onCreate(); SDK.getInstance().init(); }}
app/src/main/java/com/tobenull/multiflavorsdk/interfaces/IMainView.java
package com.tobenull.multiflavorsdk.interfaces;/** * Created by tobenull on 12/16/17. */public interface IMainView { void showResult(String result);}
app/src/main/java/com/tobenull/multiflavorsdk/interfaces/IMainPresenter.java
package com.tobenull.multiflavorsdk.interfaces;import android.widget.BaseAdapter;/** * Created by tobenull on 12/16/17. */public interface IMainPresenter { BaseAdapter getAdapter(); void onItemClick(int position);}
app/src/main/java/com/tobenull/multiflavorsdk/MainActivity.java
package com.tobenull.multiflavorsdk;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.ListView;import android.widget.TextView;import com.tobenull.multiflavorsdk.interfaces.IMainPresenter;import com.tobenull.multiflavorsdk.interfaces.IMainView;public class MainActivity extends AppCompatActivity implements IMainView { private IMainPresenter mainPresenter; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainPresenter = new MainPresentImpl(this, this); textView = (TextView) findViewById(R.id.textview); ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(mainPresenter.getAdapter()); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { mainPresenter.onItemClick(position); } }); } @Override public void showResult(String result) { textView.setText(result); }}
MainActivity布局如下:
app/src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation="vertical" tools:context = "com.tobenull.multiflavorsdk.MainActivity"> <ListView android:id = "@+id/listview" android:layout_width = "match_parent" android:layout_height = "0dp" android:layout_weight = "1" tools:ignore = "Suspicious0dp" /> <ScrollView android:layout_width = "match_parent" android:layout_height = "0dp" android:layout_weight = "1" tools:ignore = "Suspicious0dp"> <TextView android:id = "@+id/textview" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_centerInParent = "true" android:text = "Hello World!" /> ScrollView>LinearLayout>
渠道实现如下:
app/src/alibaba/java/com/tobenull/multiflavorsdk/MainPresentImpl.java
package com.tobenull.multiflavorsdk;import android.content.Context;import android.widget.ArrayAdapter;import android.widget.BaseAdapter;import android.widget.Toast;import com.tobenull.multiflavorsdk.interfaces.IMainPresenter;import com.tobenull.multiflavorsdk.interfaces.IMainView;import com.tobenull.sdk.SDK;/** * Created by tobenull on 12/16/17. */public class MainPresentImpl implements IMainPresenter { private Context context; private IMainView mainView; private BaseAdapter adapter; public MainPresentImpl(Context context, IMainView mainView) { this.context = context; this.mainView = mainView; if (adapter == null) adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, new String[] { "SDK.getInstance().init();", "SDK.getInstance().buy(product);", }); } @Override public BaseAdapter getAdapter() { return adapter; } @Override public void onItemClick(int position) { StringBuffer result = new StringBuffer(); switch (position) { case 0: SDK.getInstance().init(); result.append("调用了SDK.getInstance().init();方法"); break; case 1: String product = "alibaba debug demo"; SDK.getInstance().buy(product); result.append("调用了SDK.getInstance().buy(product);方法\nproduct = " + product); break; } mainView.showResult(result.toString()); }}
运行demo后,依次点击init和buy的测试方法,日志如下:
12-16 02:55:35.189 2309-2309/com.tobenull.multiflavorsdk D/null-tobe: BaseSDK->init, alibaba12-16 02:55:35.189 2309-2309/com.tobenull.multiflavorsdk D/null-tobe: BaseManager->init12-16 02:55:35.189 2309-2309/com.tobenull.multiflavorsdk D/null-tobe: SDK->initAlibaba, init alibaba...12-16 02:55:35.189 2309-2309/com.tobenull.multiflavorsdk D/null-tobe: MapManager->init, init gaode map...12-16 02:55:36.271 2309-2309/com.tobenull.multiflavorsdk D/null-tobe: SDK->search, you want to buy alibaba debug demo
同样的,在Build Variant窗口中切换渠道为baiduDebug,实现IMainPresenter接口如下:
app/src/baidu/java/com/tobenull/multiflavorsdk/MainPresenterImpl.java
package com.tobenull.multiflavorsdk;import android.content.Context;import android.widget.ArrayAdapter;import android.widget.BaseAdapter;import android.widget.Toast;import com.tobenull.multiflavorsdk.interfaces.IMainPresenter;import com.tobenull.multiflavorsdk.interfaces.IMainView;import com.tobenull.sdk.SDK;/** * Created by tobenull on 12/16/17. */public class MainPresentImpl implements IMainPresenter { private Context context; private IMainView mainView; private BaseAdapter adapter; public MainPresentImpl(Context context, IMainView mainView) { this.context = context; this.mainView = mainView; if (adapter == null) adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, new String[] { "SDK.getInstance().init();", "SDK.getInstance().search(key);", }); } @Override public BaseAdapter getAdapter() { return adapter; } @Override public void onItemClick(int position) { StringBuffer result = new StringBuffer(); switch (position) { case 0: SDK.getInstance().init(); result.append("调用了SDK.getInstance().init();方法"); break; case 1: String key = "baidu debug demo"; SDK.getInstance().search(key); result.append("调用了SDK.getInstance().search(key);方法\nkey = " + key); break; } mainView.showResult(result.toString()); }}
运行app,日志如下:
12-16 03:03:49.400 8923-8923/com.tobenull.multiflavorsdk D/null-tobe: BaseSDK->init, baidu12-16 03:03:49.400 8923-8923/com.tobenull.multiflavorsdk D/null-tobe: BaseManager->init12-16 03:03:49.400 8923-8923/com.tobenull.multiflavorsdk D/null-tobe: SDK->initBaidu, init baidu...12-16 03:03:50.970 8923-8923/com.tobenull.multiflavorsdk D/null-tobe: SDK->search, you want to search baidu debug demo
经过上述步骤,我们现在已经实现了在同一个app工程下对多个渠道sdk进行调试。当调试无误后,即可上传库工程源码进行打版。
三.多渠道sdk的优化
经过前面的介绍,我们现在已经可以得心应手地进行多渠道sdk的构建、开发和调试了。现在如果需要新建一个渠道,我们需要在sdk和app工程中分别进行配置,然后分别新建渠道代码,如果能通过一处进行统一管理就堪称完美了。
构建脚本中一共有三处配置需要灵活处理,如下:
sdk/build.gralde
android { ... productFlavors { baidu{} alibaba{} tencent{} } ...}
app/build.gradle
android { ... productFlavors { baidu{ applicationId 'com.baidu.demo' } alibaba{ applicationId 'com.alibaba.demo' } tencent{ applicationId 'com.tencent.demo' } } ...}
app/build.gradle
dependencies { ... baiduCompile project(path: ':sdk', configuration: 'baiduRelease') alibabaCompile project(path: ':sdk', configuration: 'alibabaRelease') tencentCompile project(path: ':sdk', configuration: 'tencentRelease') ...}
我们可以将渠道包信息在{rootProject}/build.gradle中统一定义,然后三处分别动态添加即可。
首先在rootProject中定义一组配置信息,每个配置均有product、packageName和type三个属性,如下:
build.gradle
buildscript { ext { ... SDKProperties = [ [product: 'alibaba', packageName: 'com.alibaba.demo', type: 'jar'], [product: 'baidu', packageName: 'com.baidu.demo', type: 'aarjar'], [product: 'tencent', packageName: 'com.tencent.demo', type: 'aar'], ] ... } ...}
以上属性中product作为渠道的标志,packageName为该渠道对应demo的包名,type属性暂时不用、作为后续生成jar、aar或jar+aar三种sdk产品的标记。
动态添加渠道
sdk/build.gradle
android { ... productFlavors { rootProject.ext.SDKProperties.each { productFlavors.create(it.product) } } ...}
app/build.gradle
android { ... productFlavors { rootProject.ext.SDKProperties.each { productFlavors.create(it.product).setApplicationId(it.packageName) } } ...}
通过rootProject.ext.SDKProperties.each遍历SDKProperties中配置的渠道包信息,productFlavors.create()动态添加渠道包,app工程中创建好渠道包后通过setApplicationId()即可设置该渠道对应demo的包名。
注:此处的it代表的是SDKProperties中的每一项配置([product: ‘xxx’, packageName: ‘com.xxx.demo’, type: ‘xxx’])
动态配置依赖
app/build.gradle
dependencies { if (rootProject.ext.debug) { rootProject.ext.SDKProperties.each { println "configurations: ${it.product}Compile" dependencies.add("${it.product}Compile", project( path: ':sdk', configuration: "${it.product}Release") ) } } else { ... } ...}
与前文写法类似,gradle可以通过”${it.product}Compile”动态生成alibabaCompile、baiduCompile等字符串然后使用dependencies.add()的方式添加相应的依赖
经过上面的处理,我们可以只通过{rootProject}/build.gradle中的SDKProperties对渠道包进行管理。比如,现在需要新增一个product为iqiyi、包名为com.iqiyi.demo的渠道,则只需在SDKProperties中新增一行即可。
如果iqiyi和baidu渠道类似,我们还可以通过脚本自动复制baidu的渠道代码、然后再次基础上进行进一步的开发,类似以下的处理方式:
sdk/build.gradle
rootProject.ext.SDKProperties.each { def product = it.product if (it.packageName == "" || it.packageName.length() <= 0) return // 如果渠道对应的sdk源码包不存在,则从base复制一份,根据base进行修改 if (!file("src/$product").exists()) { copy { from "src/base" into "src/$product" } } // 如果渠道对应的demo源码包不存在,则从base复制一份,根据base进行修改 if (!file("../app/src/$product").exists()) { copy { from "../app/src/base" into "../app/src/$product" } }}
在sdk开发、特别是多渠道sdk开发时,最好建立一个通用的base渠道,通过以上方式会在gradle同步时自动复制base渠道的源码到新建的渠道、新渠道也可以在添加后立马编译执行。
此处我们以baidu渠道作为base渠道进行演示。
新建iqiyi渠道,如下:
build.gradle
buildscript { ext { ... SDKProperties = [ ... [product: 'iqiyi', packageName: 'com.iqiyi.demo', type: 'aar'], ] ... } ...}
同步gradle成功后,在Build Variants窗口将渠道切换为iqiyiDebug,运行demo,日志如下:
12-16 04:57:54.581 32194-32194/? D/null-tobe: BaseSDK->init, baidu12-16 04:57:54.581 32194-32194/? D/null-tobe: BaseManager->init12-16 04:57:54.589 32194-32194/? D/null-tobe: SDK->initBaidu, init baidu...
由此,我们实现了通过一行配置信息自动管理多渠道包的配置。
四.总结
本文首先分别通过新增库依赖和新增渠道、在不改变构建脚本的情况下,生成了符合要求的sdk包,验证了前文sdk多渠道构建脚本的正确性。
接着对多渠道调试进行了探究,通过配置多渠道app工程对多渠道sdk进行了本地调试、通过Build Variants窗口进行各渠道之间的切换。
最后对多个渠道的配置进行了优化,通过动态添加渠道信息、动态添加依赖、动态复制源码的方式,使得多渠道的开发、调试与管理变得更加简单,只需一行配置信息即可进行控制。
至此,关于多渠道sdk的开发、构建与调试心得的介绍也就基本结束了。后续将着重介绍自动生成demo及文档等内容,使得sdk的开发更加标准化,不用再费力地管理demo和文档了。
更多相关文章
- android > 搭建 cordova 环境
- Android(安卓)library projects cannot be launched
- Android中NDK的so文件产生和使用
- Android(安卓)工程编译 Unsupported major.minor version 51.0
- Apache Ant 环境开发Android应用 二
- Android(安卓)Studio多渠道批量打包及代码混淆
- android创建工程时自动创建的V4和V7工程
- Android(安卓)集成 Zxing 条码扫描器
- Android导入自定义的jar包时出现 E/AndroidRuntime(486): java.l