android:一步步实现插件化与热更新

来源:互联网 发布:淘宝宝贝关键词怎么刷 编辑:程序博客网 时间:2024/06/10 10:41

由于插件化开发与热更新最近貌似越来越火,新开的项目准备也使用插件化进行开发!其中遇到不少坑,在这里写了一个小的例子,记录一下开发流程,有助于自己,同时希望能够帮助大家理解,并且对于自身项目接入插件化有所帮助!

插件化

效果:

这里写图片描述

插件化开发的含义:

插件化开发也是将一个项目app拆分成多个模块,这些模块包括宿主和插件。每个模块相当于一个apk,而组件化相当于一个lib。最终发布的时候将宿主apk和插件apk单独打包或者联合打包。

插件化的作用

  • 有效解决方法数超过了一个 Dex 最大方法数 65535 的上限

  • 模块解耦

  • 动态升级

  • 高效并行开发(编译速度更快)

  • 按需加载,内存占用更低

  • 节省升级流量

  • 易于维护

  • 易于团队开发

  • 易于功能扩展

使用框架

这里主要使用small框架实现插件化的,为啥我选择使用small
  • 它目前作者还在进行维护

  • 功能强大(请看下图你就会明白)

这里写图片描述
这里写图片描述

这里写图片描述

开始一步步实现插件化

  • *******首先你需要引入small**********************
需要在根目录下的build.gradle脚本里引入:buildscript  {    dependencies {        classpath 'net.wequick.tools.build:gradle-small:1.3.0-beta3'    }}apply plugin: 'net.wequick.small'small {    aarVersion = '1.3.0-beta3'    buildToAssets = false    android {        compileSdkVersion = 26        buildToolsVersion = "25.0.2"        supportVersion = "25.1.0"    }}
  • ***********新建插件模块******************
File->New->Module来创建插件模块,需要满足:    模块名形如:app.*, lib.*或者web.*    包名包含:.app., .lib.或者.web.    为什么要这样?因为Small会根据包名对插件进行归类,特殊的域名空间如:“.app.” 会让这变得容易。对lib.*模块选择Android Library,其他模块选择Phone & Tablet Module。创建一个插件模块,比如app.main:    修改Application/Library name为App.main    修改Package name为com.example.mysmall.app.main

如果你不理解,请看我的目录结构

这里写图片描述

上面是模块名称,同时你要注意一下包名:

这里写图片描述

在你新建一个Module的时候as自动命名的应该是:tsou.cn.appchat因此你在创建的时候要手动修改一下,可能你开始经常不注意直接下一步直接生成了。进行如下图点击edit 修改一下就可以了

这里写图片描述

  • *********创建bundle.json****************

在你的宿主模块下(通常是app)创建bundle.json如下图

这里写图片描述

bundle.json的内容格式如下

{  "version": "1.0.0",  "bundles": [    {      "uri": "lib.data",      "pkg": "tsou.cn.lib.data"    },    {      "uri": "lib.utils",      "pkg": "tsou.cn.lib.utils"    },    {      "uri": "lib.style",      "pkg": "tsou.cn.lib.style"    },    {      "uri": "lib.layout",      "pkg": "tsou.cn.lib.layout"    },    {      "uri": "lib.icon",      "pkg": "tsou.cn.lib.icon"    },    {      "uri": "home",      "pkg": "tsou.cn.app.home"    },    {      "uri": "chat",      "pkg": "tsou.cn.app.chat",      "rules": {        "FromHomeActivity": "tsou.cn.app.chat.activity.FromHomeActivity"      }    },    {      "uri": "recom",      "pkg": "tsou.cn.app.recom"    },    {      "uri": "me",      "pkg": "tsou.cn.app.me"    }  ]}

特别注意:

  1. 在这里你要注意一下不管是你的插件模块,还是你的依赖模块都需要在这里进行注册,不然你的程序运行会出现错误

    例如:我在这里使用公用的lib.style样式,如果没有注册lib.style那么这个样式编译可以通过,但是运行就会找不到的错误。

  2. 你的宿主(app)moudle不能引入依赖模块(lib),只能是你的插件模块引用你的依赖模块,如下:

    这是我的app宿主:

    这里写图片描述

    这是我的app.chat插件模块:

    这里写图片描述

  3. 列表内容你需要进行编译:

    我这里直接使用的是as来编译:依次为cleanLib->cleanBundle->buildLib->buildBundle

    这里写图片描述

  4. 选择你的app主模块进行运行。


  • ************初始化small****************
在你的宿主app模块中:package tsou.cn.mysmalltest;import android.app.Application;import net.wequick.small.Small;/** * Created by Administrator on 2017/11/27 0027. */public class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        Small.preSetUp(this);    }}
  • *********显示主页面************

    主页面主要是一个viewpager来填充各个插件模块的fragment,代码如下:

package tsou.cn.mysmalltest;import android.os.Bundle;import android.support.design.widget.TabLayout;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentManager;import android.support.v4.app.FragmentStatePagerAdapter;import android.support.v4.view.ViewPager;import android.support.v7.app.AppCompatActivity;import android.view.LayoutInflater;import android.view.View;import android.widget.ImageView;import android.widget.TextView;import net.wequick.small.Small;/** * 使用style等lib的时候切记在bundle.json上继续配置,不然会找不到 * <p> * 例如:提示AppTheme找不到让你进行配置问题 */public class MainActivity extends AppCompatActivity {    private ViewPager mMViewPager;    private TabLayout mToolbarTab;    /**     * 图标     */    private int[] tabIcons = {            R.drawable.tab_home,            R.drawable.tab_weichat,            R.drawable.tab_recommend,            R.drawable.tab_user    };    private static String[] fragments = new String[]{"home", "chat", "recom", "me"};    private String[] tab_array;    private DemandAdapter mDemandAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initData();        initView();        // 给viewpager设置适配器        setViewPagerAdapter();        setTabBindViewPager();        setItem();    }    private void initData() {        tab_array = getResources().getStringArray(R.array.tab_main);    }    private void initView() {        mMViewPager = (ViewPager) findViewById(R.id.mViewPager);        mToolbarTab = (TabLayout) findViewById(R.id.toolbar_tab);    }    private void setViewPagerAdapter() {        mDemandAdapter = new DemandAdapter(getSupportFragmentManager());        mMViewPager.setAdapter(mDemandAdapter);    }    private void setTabBindViewPager() {        mToolbarTab.setupWithViewPager(mMViewPager);    }    private void setItem() {        /**         * 一定要在设置适配器之后设置Icon         */        for (int i = 0; i < mToolbarTab.getTabCount(); i++) {            mToolbarTab.getTabAt(i).setCustomView(getTabView(i));        }    }    public View getTabView(int position) {        View view = LayoutInflater.from(this).inflate(R.layout.item_tab, null);        ImageView tab_image = view.findViewById(R.id.tab_image);        TextView tab_text = view.findViewById(R.id.tab_text);        tab_image.setImageResource(tabIcons[position]);        tab_text.setText(tab_array[position]);        return view;    }    /**     * 适配器     */    public class DemandAdapter extends FragmentStatePagerAdapter {        public DemandAdapter(FragmentManager fm) {            super(fm);        }        @Override        public Fragment getItem(int position) {            /**             * 在使用createObject前,需要再此页面之前已经存在activity,初始化Small,             * 否则获取不到Fragment,都是null             */            Fragment fragment = Small.createObject("fragment-v4", fragments[position],                    MainActivity.this);            return fragment;        }        @Override        public int getCount() {            return tabIcons.length;        }    }}

特别要注意:

  1. 使用Small.createObject(“fragment-v4”, fragments[position],
    MainActivity.this);来获取fragment时,各个插件模块的fragment命名一定要是MainFragment不然会出现空指针异常,这是Small框架的问题,在反射调用时已经写死了,同时需要在bundle.json中配置,一般直接创建在跟包名下面:

    这里写图片描述

  2. 在使用createObject前,需要再此页面之前已经存在activity,初始化Small,例如我这里已经有了个启动页面LaunchActivity,不然也会出现空指针

    这里写图片描述

  3. 如果这个时候要运行代码看效果,记得要执行as编译依次为cleanLib->cleanBundle->buildLib->buildBundle,、

  4. 记住在每次写完代码,运行到手机的时候都要进行此4部,否则新写的代码无效!


  • ********插件之间的数据传递********

一、跳转到Chat模块主页

 在app.chat创建MainActivity,其实插件模块是一个可以独立运行的应用,和平时项目中的MainActivity没有区别,可看做独立的应用看待。切记,在bundle.json中配置,我上面已经配置好了,包括后面要用的FromHomeActivity。并且运行时别忘了上面的那4部,后面不在说明。
 {      "uri": "chat",      "pkg": "tsou.cn.app.chat",      "rules": {        "FromHomeActivity": "tsou.cn.app.chat.activity.FromHomeActivity"      }    }
执行代码如下: Small.setUp(getContext(), new Small.OnCompleteListener() {                    @Override                    public void onComplete() {                        Small.openUri("chat", getContext());                    }                });

二、不带参数跳转到Chat模块指定Activity(FromHomeActivity)

执行代码如下: Small.setUp(getContext(), new Small.OnCompleteListener() {                    @Override                    public void onComplete() {                        /**                         * Small.openUri("chat", getContext());                         * 直接跳转chat模块的主页。                         *                         *   Small.openUri("chat/FromHomeActivity", getContext());                         *   跳转指定页面                         */                        Small.openUri("chat/FromHomeActivity", getContext());                    }                });

三、带参数跳转到Chat模块指定Activity(FromHomeActivity)

执行代码如下: Small.setUp(getContext(), new Small.OnCompleteListener() {                    @Override                    public void onComplete() {                        Small.openUri("chat/FromHomeActivity?name=huangxiaoguo&age=25", getContext());                    }                });
FromHomeActivity中接收数据并使用:    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_from_home);        initData();        initView();    }    private void initData() {        Uri uri = Small.getUri(this);        if (uri != null) {            name = uri.getQueryParameter("name");            age = uri.getQueryParameter("age");        }    }    private void initView() {        mTextview = (TextView) findViewById(R.id.textview);        if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(age)) {            mTextview.setText("name=" + name + ",age=" + age);//            UIUtils.showToast("name=" + name + ",age=" + age);        }

四、small支持本地网页组件

small可以直接支持本地网页,我们不用再自己写一个webview页面了!但是这里的本地网页组件功能很有限,只能显示比较简单的网页,复杂的暂时不支持,会崩溃,具体你可以试试就知道了。
//Small.openUri("http://www.baidu.com", getContext());Small.openUri("https://github.com/wequick/Small/issues", getContext());                break;

五、使用eventBus数据传输

原理不说了,直接上代码
 compile 'org.simple:androideventbus:1.0.5.1'
public interface EvenBusTag {    String EVENT_GET_DATA = "evevt_get_data";}
    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle            savedInstanceState) {        EventBus.getDefault().register(this);        View view = inflater.inflate(R.layout.fragment_home, null);        initView(view);        return view;    }    @Override    public void onDestroyView() {        EventBus.getDefault().unregister(this);        ThreadUtils.cancelLongBackThread();        super.onDestroyView();    }    @Subscriber(tag = EvenBusTag.EVENT_GET_DATA)    public void onEvent(String s) {        UIUtils.showToast(s);    }
   @Override    public void onClick(View v) {        switch (v.getId()) {            default:                break;            case R.id.btn_fack_eventbus:                EventBus.getDefault().post("eventBus从FromHomeActivity中返回数据了", EvenBusTag.EVENT_GET_DATA);                finish();                break;        }    }
友好提示:我的这里的所有公用compile引入和一些自己定义的字段都放在lib.data中,因为我这里只有这个依赖库是专门提供给当前产品使用的,在开发新的产品时别的依赖库方便直接使用,我个人感觉方便点,当然看你自己想法!

六、插件化更新

  1. 修改代码

    我修改的是app.chat中FromHomeActivity

    在参数传递过来的时候,弹个吐司:UIUtils.showToast("name=" + name + ",age=" + age);
  2. 记得修改版本号:

     versionCode 2   1——>2  versionName "1.1"  1.0->1.1注意你修改的哪个插件修改哪个插件的版本即可,不是宿主app模块,我这里修改的是app.chat
  3. 两步:buildLib->buildBundle

  4. 查找so,在你的宿主模块中

    这里写图片描述

  5. 部署服务器,我直接在我的电脑上部署一个tomcat

    这里写图片描述

  6. 服务器的bundle.json

    {  "manifest": {    "bundles": [      {        "pkg": "tsou.cn.lib.data",        "uri": "lib.data"      },      {        "pkg": "tsou.cn.lib.utils",        "uri": "lib.utils"      },      {        "pkg": "tsou.cn.lib.style",        "uri": "lib.style"      },      {        "pkg": "tsou.cn.lib.layout",        "uri": "lib.layout"      },      {        "pkg": "tsou.cn.lib.icon",        "uri": "lib.icon"      },      {        "pkg": "tsou.cn.app.home",        "uri": "home"      },      {        "pkg": "tsou.cn.app.chat",        "rules": {          "FromHomeActivity": "tsou.cn.app.chat.activity.FromHomeActivity"        },        "uri": "chat"      },      {        "pkg": "tsou.cn.app.recom",        "uri": "recom"      },      {        "pkg": "tsou.cn.app.me",        "uri": "me"      }    ],    "version": "1.0.0"  },  "updates": [    {      "pkg": "tsou.cn.app.chat",      "url": "http://192.168.19.125:8080/json/libtsou_cn_app_chat.so"    }  ]}
  7. 实现插件化更新

    /**     * 插件化更新     */    private void checkUpgrade() {        new UpgradeManager(getContext()).checkUpgrade();    }    private static class UpgradeManager {        private static class UpdateInfo {            public String packageName;            public String downloadUrl;        }        private static class UpgradeInfo {            public JSONObject manifest;            public List<UpdateInfo> updates;        }        private interface OnResponseListener {            void onResponse(UpgradeInfo info);        }        private interface OnUpgradeListener {            void onUpgrade(boolean succeed);        }        private static class ResponseHandler extends Handler {            private OnResponseListener mListener;            public ResponseHandler(OnResponseListener listener) {                mListener = listener;            }            @Override            public void handleMessage(Message msg) {                switch (msg.what) {                    case 1:                        mListener.onResponse((UpgradeInfo) msg.obj);                        break;                }            }        }        private ResponseHandler mResponseHandler;        private Context mContext;        private ProgressDialog mProgressDlg;        public UpgradeManager(Context context) {            mContext = context;        }        public void checkUpgrade() {            mProgressDlg = ProgressDialog.show(mContext, "Small", "检查更新...", false, true);            requestUpgradeInfo(Small.getBundleVersions(), new OnResponseListener() {                @Override                public void onResponse(UpgradeInfo info) {                    mProgressDlg.setMessage("升级中...");                    upgradeBundles(info,                            new OnUpgradeListener() {                                @Override                                public void onUpgrade(boolean succeed) {                                    mProgressDlg.dismiss();                                    mProgressDlg = null;                                    String text = succeed ?                                            "升级成功!切换到后台并返回到前台来查看更改"                                            : "升级失败!";                                    UIUtils.showToast(text);                                }                            });                }            });        }        /**         * @param versions         * @param listener         */        private void requestUpgradeInfo(Map versions, OnResponseListener listener) {            System.out.println(versions); // this should be passed as HTTP parameters            mResponseHandler = new ResponseHandler(listener);            ThreadUtils.runOnLongBackThread(new Runnable() {                @Override                public void run() {                    try {                        // Example HTTP request to get the upgrade bundles information.                        // Json format see http://wequick.github.io/small/upgrade/bundles.json                        URL url = new URL("http://192.168.19.125:8080/json/bundle.json");                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();                        StringBuilder sb = new StringBuilder();                        InputStream is = conn.getInputStream();                        byte[] buffer = new byte[1024];                        int length;                        while ((length = is.read(buffer)) != -1) {                            sb.append(new String(buffer, 0, length));                        }                        // Parse json                        JSONObject jo = new JSONObject(sb.toString());                        JSONObject mf = jo.has("manifest") ? jo.getJSONObject("manifest") : null;                        JSONArray updates = jo.getJSONArray("updates");                        int N = updates.length();                        List<UpdateInfo> infos = new ArrayList<UpdateInfo>(N);                        for (int i = 0; i < N; i++) {                            JSONObject o = updates.getJSONObject(i);                            UpdateInfo info = new UpdateInfo();                            info.packageName = o.getString("pkg");                            info.downloadUrl = o.getString("url");                            infos.add(info);                        }                        // Post message                        UpgradeInfo ui = new UpgradeInfo();                        ui.manifest = mf;                        ui.updates = infos;                        Message.obtain(mResponseHandler, 1, ui).sendToTarget();                    } catch (Exception e) {                        e.printStackTrace();                        ThreadUtils.runOnUiThread(new Runnable() {                            @Override                            public void run() {                                mProgressDlg.dismiss();                                mProgressDlg = null;                                UIUtils.showToast("更新失败");                            }                        });                    }                }            });        }        private static class DownloadHandler extends Handler {            private OnUpgradeListener mListener;            public DownloadHandler(OnUpgradeListener listener) {                mListener = listener;            }            @Override            public void handleMessage(Message msg) {                switch (msg.what) {                    case 1:                        mListener.onUpgrade((Boolean) msg.obj);                        break;                }            }        }        private DownloadHandler mHandler;        private void upgradeBundles(final UpgradeInfo info,                                    final OnUpgradeListener listener) {            // Just for example, you can do this by OkHttp or something.            mHandler = new DownloadHandler(listener);            ThreadUtils.runOnLongBackThread(new Runnable() {                @Override                public void run() {                    try {                        // Update manifest                        if (info.manifest != null) {                            if (!Small.updateManifest(info.manifest, false)) {                                Message.obtain(mHandler, 1, false).sendToTarget();                                return;                            }                        }                        // Download bundles                        List<UpdateInfo> updates = info.updates;                        for (UpdateInfo u : updates) {                            // Get the patch file for downloading                            net.wequick.small.Bundle bundle = Small.getBundle(u.packageName);                            File file = bundle.getPatchFile();                            // Download                            URL url = new URL(u.downloadUrl);                            HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();                            InputStream is = urlConn.getInputStream();                            OutputStream os = new FileOutputStream(file);                            byte[] buffer = new byte[1024];                            int length;                            while ((length = is.read(buffer)) != -1) {                                os.write(buffer, 0, length);                            }                            os.flush();                            os.close();                            is.close();                            // Upgrade                            bundle.upgrade();                        }                        Message.obtain(mHandler, 1, true).sendToTarget();                    } catch (IOException e) {                        e.printStackTrace();                        Message.obtain(mHandler, 1, false).sendToTarget();                        ThreadUtils.runOnUiThread(new Runnable() {                            @Override                            public void run() {                                mProgressDlg.dismiss();                                mProgressDlg = null;                                UIUtils.showToast("更新失败");                            }                        });                    }                }            });        }    }

到此你就可以使用small进行插件化开发了……..

热更新

热更新其实来源于插件化,如果你使用了上面的Small进行插件化开发,就可以直接进行插件化更新了!如果你没有使用插件化开发,你就可以只有热更新实现代码的热修复。

这里主要使用的是腾讯的Tinker。

  • 在peoject的build中配置如下:
  dependencies {        classpath 'com.android.tools.build:gradle:2.3.3'        classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }
TINKER_VERSION需要在gradle.properties中进行配置
TINKER_VERSION=1.7.7
  • 接下来是配置app的build
这是我的,可以直接使用:apply plugin: 'com.android.application'//配置java版本def javaVersion = JavaVersion.VERSION_1_7android {    compileSdkVersion 26    buildToolsVersion "26.0.2"    compileOptions {        sourceCompatibility javaVersion        targetCompatibility javaVersion    }    //recommend    dexOptions {        jumboMode = true    }    signingConfigs {        release {            keyAlias 'tsou'            keyPassword 'tsou123'            storeFile file('D:/study/TinkerTest/app/keystore/tinkertest.jks')            storePassword 'tsou123'        }        debug {            keyAlias 'tsou'            keyPassword 'tsou123'            storeFile file('D:/study/TinkerTest/app/keystore/tinkertest.jks')            storePassword 'tsou123'        }    }    defaultConfig {        applicationId "tsou.cn.tinkertest"        minSdkVersion 15        targetSdkVersion 26        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"        multiDexEnabled true        buildConfigField "String", "MESSAGE", "\"I am the base apk\""        //客户端版本更新补丁        buildConfigField "String", "TINKER_ID", "\"2.0\""        buildConfigField "String", "PLATFORM", "\"all\""    }    buildTypes {        release {            minifyEnabled true            signingConfig signingConfigs.release            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }        debug {            debuggable true            minifyEnabled false            signingConfig signingConfigs.debug            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }    sourceSets {        main {            jniLibs.srcDirs = ['libs']        }    }}dependencies {    compile fileTree(include: ['*.jar'], dir: 'libs')    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {        exclude group: 'com.android.support', module: 'support-annotations'    })    compile 'com.android.support:appcompat-v7:26.+'    compile 'com.android.support.constraint:constraint-layout:1.0.2'    testCompile 'junit:junit:4.12'    //可选,用于生成application类    compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }    //tinker的核心库    provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }    compile 'com.android.support:multidex:1.0.1'}def bakPath = file("${buildDir}/bakApk/")//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改ext {    tinkerEnabled = true    tinkerOldApkPath = "${bakPath}/app-release-1110-16-24-35.apk"    tinkerApplyMappingPath = "${bakPath}/app-release-1110-16-24-35-mapping.txt"    tinkerApplyResourcePath = "${bakPath}/app-release-1110-16-24-35-R.txt"    //only use for build all flavor, if not, just ignore this field    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}def getOldApkPath() {    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath}def getApplyMappingPath() {    return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath}def getApplyResourceMappingPath() {    return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath}def buildWithTinker() {    return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled}def getTinkerBuildFlavorDirectory() {    return ext.tinkerBuildFlavorDirectory}/** * tinkerId一定要有 */if (buildWithTinker()) {    apply plugin: 'com.tencent.tinker.patch'    tinkerPatch {        oldApk = getOldApkPath()        ignoreWarning = true        useSign = true        tinkerEnable = buildWithTinker()        buildConfig {            applyMapping = getApplyMappingPath()            applyResourceMapping = getApplyResourceMappingPath()            tinkerId = "1.0"/*getTinkerIdValue()*/            keepDexApply = false        }        dex {            dexMode = "jar"            pattern = ["classes*.dex",                       "assets/secondary-dex-?.jar"]            loader = [                    //use sample, let BaseBuildInfo unchangeable with tinker                    "tinker.sample.android.app.BaseBuildInfo"            ]        }        lib {            pattern = ["lib/*/*.so"]        }        res {            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]            ignoreChange = ["assets/sample_meta.txt"]            largeModSize = 100        }        packageConfig {            configField("patchMessage", "tinker is sample to use")            configField("platform", "all")            configField("patchVersion", "1.0")        }        sevenZip {            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"//        path = "/usr/local/bin/7za"        }    }    List<String> flavors = new ArrayList<>();    project.android.productFlavors.each { flavor ->        flavors.add(flavor.name)    }    boolean hasFlavors = flavors.size() > 0    android.applicationVariants.all { variant ->        def taskName = variant.name        def date = new Date().format("MMdd-HH-mm-ss")        tasks.all {            if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {                it.doLast {                    copy {                        def fileNamePrefix = "${project.name}-${variant.baseName}"                        def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"                        def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath                        from variant.outputs.outputFile                        into destPath                        rename { String fileName ->                            fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")                        }                        from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"                        into destPath                        rename { String fileName ->                            fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")                        }                        from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"                        into destPath                        rename { String fileName ->                            fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")                        }                    }                }            }        }    }    project.afterEvaluate {        if (hasFlavors) {            task(tinkerPatchAllFlavorRelease) {                group = 'tinker'                def originOldPath = getTinkerBuildFlavorDirectory()                for (String flavor : flavors) {                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")                    dependsOn tinkerTask                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")                    preAssembleTask.doFirst {                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"                    }                }            }            task(tinkerPatchAllFlavorDebug) {                group = 'tinker'                def originOldPath = getTinkerBuildFlavorDirectory()                for (String flavor : flavors) {                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")                    dependsOn tinkerTask                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")                    preAssembleTask.doFirst {                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"                    }                }            }        }    }}
注意 修改buildConfigField 
//客户端版本更新补丁buildConfigField "String", "TINKER_ID", "\"2.0\""如果更新客户端补丁需要修改,例如我把1.0改为2.0
  • 配置application
package tsou.cn.tinkertest;import android.app.Application;import android.content.Context;import android.content.Intent;import android.support.multidex.MultiDex;import com.tencent.tinker.anno.DefaultLifeCycle;import com.tencent.tinker.lib.tinker.TinkerInstaller;import com.tencent.tinker.loader.app.ApplicationLike;import com.tencent.tinker.loader.shareutil.ShareConstants;/** * Created by Administrator on 2017/10/27 0027. */@DefaultLifeCycle(application = ".SimpleTinkerInApplication",        flags = ShareConstants.TINKER_ENABLE_ALL,        loadVerifyFlag =true)public class SimpleTinkerInApplicationLike extends ApplicationLike {    public SimpleTinkerInApplicationLike (Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);    }    @Override    public void onBaseContextAttached(Context base) {        super.onBaseContextAttached(base);        MultiDex.install(base);        TinkerInstaller.install(this);    }    @Override    public void onCreate() {        super.onCreate();    }}
SimpleTinkerInApplicationLike 并不是一个application真正的application是@DefaultLifeCycle(application = “.SimpleTinkerInApplication”,这个所以在androidmanifest中配置applica的name
 <application        android:name=".SimpleTinkerInApplication"        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:supportsRtl="true"        android:theme="@style/AppTheme">        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN"/>                <category android:name="android.intent.category.LAUNCHER"/>            </intent-filter>        </activity>    </application>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • 运行你的项目使用release版本

这里写图片描述

效果如下:

这里写图片描述

  • 更改你的build.gradle
//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改ext {    tinkerEnabled = true    tinkerOldApkPath = "${bakPath}/app-release-1201-13-55-26.apk"    tinkerApplyMappingPath = "${bakPath}/app-release-1201-13-55-26-mapping.txt"    tinkerApplyResourcePath = "${bakPath}/app-release-1201-13-55-26-R.txt"    //only use for build all flavor, if not, just ignore this field    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}
  • 更改代码:把“我的”改为“2222”

  • 然后点击 编译

这里写图片描述

  • 生成目录如下

这里写图片描述

  • 把patch_signed_7zip.apk放到手机根目录下,再执行更新

这里写图片描述

  • 热更新代码如下:
package tsou.cn.tinkertest;import android.Manifest;import android.content.pm.PackageManager;import android.os.Bundle;import android.os.Environment;import android.support.annotation.NonNull;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;import com.tencent.tinker.lib.tinker.TinkerInstaller;import java.io.File;public class MainActivity extends AppCompatActivity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        Button loadPath = (Button) findViewById(R.id.loadPath);        tv.setText("2222");        loadPath.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                requestPermi();            }        });    }    private void requestPermi() {        if (ContextCompat.checkSelfPermission(MainActivity.this,                Manifest.permission.READ_EXTERNAL_STORAGE)                != PackageManager.PERMISSION_GRANTED) {            // Should we show an explanation?            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,                    Manifest.permission.READ_EXTERNAL_STORAGE)) {            } else {                ActivityCompat.requestPermissions(MainActivity.this,                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},                        100);            }        } else {            //有权限直接加载apk            loadApk();        }    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        switch (requestCode) {            case 100: {                // If request is cancelled, the result arrays are empty.                if (grantResults.length > 0                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {                    //权限申请成功加载apk                    loadApk();                } else {                }                return;            }        }    }    public void loadApk() {        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";        File file = new File(path);        if (file.exists()) {            Toast.makeText(this, "补丁已经存在", Toast.LENGTH_SHORT).show();            TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);        } else {            Toast.makeText(this, "补丁已经不存在", Toast.LENGTH_SHORT).show();        }    }}

好了到此插件化与热更新都可以实现了…….

android插件化开发Demo:https://gitee.com/huangxiaoguo/androidChaJianHuaKaiFa

android-Tinker热修复Demo:https://gitee.com/huangxiaoguo/android-TinkerReXiuFu