一.多渠道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和文档了。

更多相关文章

  1. android > 搭建 cordova 环境
  2. Android(安卓)library projects cannot be launched
  3. Android中NDK的so文件产生和使用
  4. Android(安卓)工程编译 Unsupported major.minor version 51.0
  5. Apache Ant 环境开发Android应用 二
  6. Android(安卓)Studio多渠道批量打包及代码混淆
  7. android创建工程时自动创建的V4和V7工程
  8. Android(安卓)集成 Zxing 条码扫描器
  9. Android导入自定义的jar包时出现 E/AndroidRuntime(486): java.l

随机推荐

  1. Small Structures: 超陡亚阈值摆幅的范德
  2. 帮你光速脱单!程序员找对象指南
  3. 透过 OKR 进行项目过程管理
  4. 十次九输!我的词汇量被自己写的小游戏吊打
  5. Spring MVC项目基本配置
  6. Spark Streaming 在数据平台日志解析功能
  7. 工欲善其事,如何挑选一款趁手的键盘?让你的
  8. 4.14 vSphere许可术语和定义
  9. 【Git版本控制】Git版本控制工具使用说明
  10. PHP入门之 变量与常量