}
}
友好提示:我的这里的所有公用compile引入和一些自己定义的字段都放在lib.data中,因为我这里只有这个依赖库是专门提供给当前产品使用的,在开发新的产品时别的依赖库方便直接使用,我个人感觉方便点,当然看你自己想法!
六、插件化更新
- 修改代码
我修改的是app.chat中FromHomeActivity
在参数传递过来的时候,弹个吐司:
UIUtils.showToast(“name=” + name + “,age=” + age);
- 记得修改版本号:
versionCode 2 1——>2
versionName “1.1” 1.0->1.1
注意你修改的哪个插件修改哪个插件的版本即可,不是宿主app模块,我这里修改的是app.chat
-
两步:buildLib->buildBundle
-
查找so,在你的宿主模块中
- 部署服务器,我直接在我的电脑上部署一个tomcat
- 服务器的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”
}
]
}
- 实现插件化更新
/**
- 插件化更新
*/
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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-pBgRXiZx-1712625334981)]
最后
给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;
[外链图片转存中…(img-eWDTrZ7D-1712625334981)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-cq3uhpk9-1712625334982)]