android:使用small一步步实现插件化与热更新(1),2024年最新应届生面试销售岗位的面试问题技巧

}

}

友好提示:我的这里的所有公用compile引入和一些自己定义的字段都放在lib.data中,因为我这里只有这个依赖库是专门提供给当前产品使用的,在开发新的产品时别的依赖库方便直接使用,我个人感觉方便点,当然看你自己想法!

六、插件化更新

  1. 修改代码

我修改的是app.chat中FromHomeActivity

在参数传递过来的时候,弹个吐司:

UIUtils.showToast(“name=” + name + “,age=” + age);

  1. 记得修改版本号:

versionCode 2 1——>2

versionName “1.1” 1.0->1.1

注意你修改的哪个插件修改哪个插件的版本即可,不是宿主app模块,我这里修改的是app.chat

  1. 两步:buildLib->buildBundle

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

这里写图片描述

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

这里写图片描述

  1. 服务器的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”

}

]

}

  1. 实现插件化更新

/**

  • 插件化更新

*/

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 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 infos = new ArrayList(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 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_7

android {

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 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 = “ p r o j e c t . n a m e − {project.name}- project.name{variant.baseName}”

def newFileNamePrefix = hasFlavors ? “ f i l e N a m e P r e f i x " : " {fileNamePrefix}" : " fileNamePrefix":"{fileNamePrefix}-${date}”

def destPath = hasFlavors ? file(“ b a k P a t h / {bakPath}/ bakPath/{project.name}- d a t e / {date}/ date/{variant.flavorName}”) : bakPath

from variant.outputs.outputFile

into destPath

rename { String fileName ->

fileName.replace(“ f i l e N a m e P r e f i x . a p k " , " {fileNamePrefix}.apk", " fileNamePrefix.apk","{newFileNamePrefix}.apk”)

}

from “ b u i l d D i r / o u t p u t s / m a p p i n g / {buildDir}/outputs/mapping/ buildDir/outputs/mapping/{variant.dirName}/mapping.txt”

into destPath

rename { String fileName ->

fileName.replace(“mapping.txt”, “${newFileNamePrefix}-mapping.txt”)

}

from “ b u i l d D i r / i n t e r m e d i a t e s / s y m b o l s / {buildDir}/intermediates/symbols/ 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 = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-release.apk”

project.tinkerPatch.buildConfig.applyMapping = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-release-mapping.txt”

project.tinkerPatch.buildConfig.applyResourceMapping = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- 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 = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-debug.apk”

project.tinkerPatch.buildConfig.applyMapping = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-debug-mapping.txt”

project.tinkerPatch.buildConfig.applyResourceMapping = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- 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”>

  • 运行你的项目使用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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
ApkPath = “${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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-OBSpZRyk-1712625334978)]
[外链图片转存中…(img-RmPfiyst-1712625334979)]
[外链图片转存中…(img-m2bN91EP-1712625334979)]
[外链图片转存中…(img-ogJJqVuD-1712625334980)]
[外链图片转存中…(img-0jgSrENj-1712625334980)]
[外链图片转存中…(img-vH2UX8kG-1712625334980)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-pBgRXiZx-1712625334981)]

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

[外链图片转存中…(img-eWDTrZ7D-1712625334981)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-cq3uhpk9-1712625334982)]

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值