Skip to content

Commit

Permalink
refactor(dynamic): 抽出dynamic-apk模块以便复用于其他动态加载apk的场景
Browse files Browse the repository at this point in the history
添加`projects/sample/hello`示例演示其用法。

close #598
  • Loading branch information
LinXueyuanStdio authored and shifujun committed Sep 10, 2021
1 parent a499e0d commit 159ab13
Show file tree
Hide file tree
Showing 48 changed files with 1,886 additions and 15 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ task clean(type: Delete) {
dependsOn gradle.includedBuild('core').task(':activity-container:clean')
dependsOn gradle.includedBuild('core').task(':transform:clean')
dependsOn gradle.includedBuild('core').task(':transform-kit:clean')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-apk:clean')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-host:clean')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader:clean')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader-impl:clean')
Expand Down
19 changes: 19 additions & 0 deletions buildScripts/gradle/maven.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ task buildSdk() {
dependsOn gradle.includedBuild('core').task(':load-parameters:assembleRelease')
dependsOn gradle.includedBuild('core').task(':utils:assemble')

dependsOn gradle.includedBuild('dynamic').task(':dynamic-apk:jarReleasePackage')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-host:jarReleasePackage')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader:jarReleasePackage')
dependsOn gradle.includedBuild('dynamic').task(':dynamic-loader-impl:assembleRelease')
Expand Down Expand Up @@ -281,6 +282,22 @@ publishing {
setScm(scm)
}
}
dynamicApk(MavenPublication) {
groupId dynamicGroupId
artifactId 'apk'
version publicationVersion
artifact("$dynamicPath/dynamic-apk/build/outputs/jar/dynamic-apk-release.jar")
artifact sourceJar("dynamicApk", "$dynamicPath/dynamic-apk")

pom.withXml {
def root = asNode()
def dependencies = root.appendNode('dependencies')
dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion))

def scm = root.appendNode('scm')
setScm(scm)
}
}
dynamicHost(MavenPublication) {
groupId dynamicGroupId
artifactId 'host'
Expand All @@ -291,6 +308,7 @@ publishing {
pom.withXml {
def root = asNode()
def dependencies = root.appendNode('dependencies')
dependencies.append(getDependencyNode('compile', dynamicGroupId, 'apk', publicationVersion))
dependencies.append(getDependencyNode('compile', coreGroupId, 'common', publicationVersion))
dependencies.append(getDependencyNode('compile', coreGroupId, 'utils', publicationVersion))

Expand Down Expand Up @@ -455,6 +473,7 @@ setGeneratePomFileAndDepends('activityContainer')
setGeneratePomFileAndDepends('transformKit')
setGeneratePomFileAndDepends('transformKitTest')
setGeneratePomFileAndDepends('transform')
setGeneratePomFileAndDepends('dynamicApk')
setGeneratePomFileAndDepends('dynamicHost')
setGeneratePomFileAndDepends('dynamicHostMultiLoaderExt')
setGeneratePomFileAndDepends('dynamicLoader')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
6 changes: 6 additions & 0 deletions projects/sample/dynamic-apk/sample-hello-api-holder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
演示如何将自定义接口动态化,使得宿主能够使用apk中的实现

sample-hello-api:定义宿主api接口
sample-hello-api-holder:将 api 动态化,宿主通过这个包提供的方法来获取apk中的实现

宿主引入 apk 包,implementation project(':sample-hello-api-holder')
31 changes: 31 additions & 0 deletions projects/sample/dynamic-apk/sample-hello-api-holder/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion project.COMPILE_SDK_VERSION

defaultConfig {
minSdkVersion project.MIN_SDK_VERSION
targetSdkVersion project.TARGET_SDK_VERSION
versionCode project.VERSION_CODE
versionName project.VERSION_NAME

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
// 使用 api 而不是 compileOnly:发布 aar 时会传递依赖,而不是打包进 aar
api 'com.tencent.shadow.core:utils'
api 'com.tencent.shadow.core:common'
api 'com.tencent.shadow.dynamic:dynamic-apk'

api project(':sample-hello-api')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.tencent.shadow.sample.apk.hello" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Tencent is pleased to support the open source community by making Tencent Shadow available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.tencent.shadow.sample.apk.hello;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;

import com.tencent.shadow.core.common.Logger;
import com.tencent.shadow.core.common.LoggerFactory;
import com.tencent.shadow.sample.api.hello.IHelloWorld;
import com.tencent.shadow.sample.api.hello.IHelloWorldImpl;

import java.io.File;

import static com.tencent.shadow.core.utils.Md5.md5File;


public final class DynamicHello implements IHelloWorld {

final private HelloWorldUpdater mUpdater;
private IHelloWorldImpl mHelloWorldImpl;
private String mCurrentImplMd5;
private static final Logger mLogger = LoggerFactory.getLogger(DynamicHello.class);

public DynamicHello(HelloWorldUpdater updater) {
if (updater.getLatest() == null) {
throw new IllegalArgumentException("构造DynamicPluginManager时传入的PluginManagerUpdater" +
"必须已经已有本地文件,即getLatest()!=null");
}
mUpdater = updater;
}

@Override
public void sayHelloWorld(Context context, TextView textView) {
if (mLogger.isInfoEnabled()) {
mLogger.info("sayHelloWorld context:" + context);
}
updateImpl(context);
mHelloWorldImpl.sayHelloWorld(context, textView);
mUpdater.update();
}

public void release() {
if (mLogger.isInfoEnabled()) {
mLogger.info("release");
}
if (mHelloWorldImpl != null) {
mHelloWorldImpl.onDestroy();
mHelloWorldImpl = null;
}
}

private void updateImpl(Context context) {
File latestImplApk = mUpdater.getLatest();
String md5 = md5File(latestImplApk);
if (mLogger.isInfoEnabled()) {
mLogger.info("TextUtils.equals(mCurrentImplMd5, md5) : " + (TextUtils.equals(mCurrentImplMd5, md5)));
}
if (!TextUtils.equals(mCurrentImplMd5, md5)) {
HelloImplLoader implLoader = new HelloImplLoader(context, latestImplApk);
IHelloWorldImpl newImpl = implLoader.load();
Bundle state;
if (mHelloWorldImpl != null) {
state = new Bundle();
mHelloWorldImpl.onSaveInstanceState(state);
mHelloWorldImpl.onDestroy();
} else {
state = null;
}
newImpl.onCreate(state);
mHelloWorldImpl = newImpl;
mCurrentImplMd5 = md5;
}
}

public IHelloWorld getHelloWorkdImpl() {
return mHelloWorldImpl;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Tencent is pleased to support the open source community by making Tencent Shadow available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.tencent.shadow.sample.apk.hello;

import android.content.Context;

import com.tencent.shadow.core.common.InstalledApk;
import com.tencent.shadow.dynamic.apk.ApkClassLoader;
import com.tencent.shadow.dynamic.apk.ChangeApkContextWrapper;
import com.tencent.shadow.dynamic.apk.ImplLoader;
import com.tencent.shadow.sample.api.hello.HelloFactory;
import com.tencent.shadow.sample.api.hello.IHelloWorldImpl;

import java.io.File;

final class HelloImplLoader extends ImplLoader {
//指定实现类在apk中的路径
private static final String FACTORY_CLASS_NAME = "com.tencent.shadow.dynamic.impl.HelloFactoryImpl";
private static final String[] REMOTE_PLUGIN_MANAGER_INTERFACES = new String[]
{
"com.tencent.shadow.core.common",
//注意将宿主自定义接口加入白名单
"com.tencent.shadow.sample.api.hello"
};
final private Context applicationContext;
final private InstalledApk installedApk;

HelloImplLoader(Context context, File apk) {
applicationContext = context.getApplicationContext();
File root = new File(applicationContext.getFilesDir(), "HelloImplLoader");
File odexDir = new File(root, Long.toString(apk.lastModified(), Character.MAX_RADIX));
odexDir.mkdirs();
installedApk = new InstalledApk(apk.getAbsolutePath(), odexDir.getAbsolutePath(), null);
}

IHelloWorldImpl load() {
ApkClassLoader apkClassLoader = new ApkClassLoader(
installedApk,
getClass().getClassLoader(),
loadWhiteList(installedApk),
1
);

Context contextForApi = new ChangeApkContextWrapper(
applicationContext,
installedApk.apkFilePath,
apkClassLoader
);

try {
HelloFactory factory = apkClassLoader.getInterface(
HelloFactory.class,
FACTORY_CLASS_NAME
);
return factory.build(contextForApi);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
protected String[] getCustomWhiteList() {
return REMOTE_PLUGIN_MANAGER_INTERFACES;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Tencent is pleased to support the open source community by making Tencent Shadow available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.tencent.shadow.sample.apk.hello;

import java.io.File;
import java.util.concurrent.Future;

/**
* apk文件升级器
* <p>
* 注意这个类不负责什么时候该升级 实现IHelloWorld的apk文件,
* 它只提供需要升级时的功能,如下载和向远端查询文件是否还可用。
*/
public interface HelloWorldUpdater {
/**
* 更新
*/
Future<File> update();

/**
* 获取本地最新可用的
* @return <code>null</code>表示本地没有可用的
*/
File getLatest();
}
1 change: 1 addition & 0 deletions projects/sample/dynamic-apk/sample-hello-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
5 changes: 5 additions & 0 deletions projects/sample/dynamic-apk/sample-hello-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
演示如何将自定义接口动态化,使得宿主能够使用apk中的实现

sample-hello-api:定义宿主api接口
sample-hello-api-holder:将 api 动态化,宿主通过这个包提供的方法来获取apk中的实现

22 changes: 22 additions & 0 deletions projects/sample/dynamic-apk/sample-hello-api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion project.COMPILE_SDK_VERSION

defaultConfig {
minSdkVersion project.MIN_SDK_VERSION
targetSdkVersion project.TARGET_SDK_VERSION
versionCode project.VERSION_CODE
versionName project.VERSION_NAME

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
21 changes: 21 additions & 0 deletions projects/sample/dynamic-apk/sample-hello-api/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.tencent.shadow.sample.api.hello" />
Loading

0 comments on commit 159ab13

Please sign in to comment.