├── .gitignore
├── Apk解析原理系统源码.png
├── Apk解析原理系统源码2.png
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── yuong
│ │ └── pluginload
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── yuong
│ │ │ └── pluginload
│ │ │ ├── MainActivity.java
│ │ │ ├── PluginManager.java
│ │ │ ├── PreferencesUtils.java
│ │ │ └── proxy
│ │ │ ├── ProxyActivity.java
│ │ │ ├── ProxyReceiver.java
│ │ │ └── ProxyService.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── yuong
│ └── pluginload
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── plugin
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── yuong
│ │ └── plugin
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── yuong
│ │ │ └── plugin
│ │ │ ├── Plugin1Activity.java
│ │ │ ├── Plugin2Activity.java
│ │ │ ├── PluginReceiver.java
│ │ │ ├── PluginService.java
│ │ │ ├── PluginStaticReceiver.java
│ │ │ └── base
│ │ │ ├── BaseActivity.java
│ │ │ └── BaseService.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_plugin1.xml
│ │ └── activity_plugin2.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── yuong
│ └── plugin
│ └── ExampleUnitTest.java
├── settings.gradle
└── transferlibrary
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── yuong
│ └── transfer
│ └── ExampleInstrumentedTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── yuong
│ │ └── transfer
│ │ ├── IActivityInterface.java
│ │ ├── IReceiverInterface.java
│ │ └── IServiceInterface.java
└── res
│ └── values
│ └── strings.xml
└── test
└── java
└── com
└── yuong
└── transfer
└── ExampleUnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/Apk解析原理系统源码.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuongzw/PluginLoadDemo/4bd998aa41fe465b0d69497c2acf2237157c4862/Apk解析原理系统源码.png
--------------------------------------------------------------------------------
/Apk解析原理系统源码2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yuongzw/PluginLoadDemo/4bd998aa41fe465b0d69497c2acf2237157c4862/Apk解析原理系统源码2.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PluginLoadDemo
2 | 占位式插件化框架的实现,可以实现宿主Activity跳转插件Activity、插件Activity之间相互跳转,插件服务的注册,插件静态广播及动态广播的注册。
3 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.2"
6 | defaultConfig {
7 | applicationId "com.yuong.pluginload"
8 | minSdkVersion 21
9 | targetSdkVersion 29
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: 'libs', include: ['*.jar'])
24 | implementation 'androidx.appcompat:appcompat:1.0.2'
25 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
26 | testImplementation 'junit:junit:4.12'
27 | androidTestImplementation 'androidx.test.ext:junit:1.1.0'
28 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
29 | compile project(path: ':transferlibrary')
30 | }
31 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/yuong/pluginload/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.yuong.pluginload;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.yuong.pluginload", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yuong/pluginload/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.yuong.pluginload;
2 |
3 | import android.Manifest;
4 | import android.annotation.SuppressLint;
5 | import android.app.ProgressDialog;
6 | import android.content.Intent;
7 | import android.content.pm.ActivityInfo;
8 | import android.content.pm.PackageInfo;
9 | import android.content.pm.PackageManager;
10 | import android.os.Build;
11 | import android.os.Bundle;
12 | import android.os.Environment;
13 | import android.os.Handler;
14 | import android.os.Message;
15 | import android.view.View;
16 | import android.widget.Toast;
17 |
18 | import androidx.annotation.NonNull;
19 | import androidx.annotation.RequiresApi;
20 | import androidx.appcompat.app.AppCompatActivity;
21 | import androidx.core.content.ContextCompat;
22 |
23 | import com.yuong.pluginload.proxy.ProxyActivity;
24 |
25 | import java.io.File;
26 |
27 | public class MainActivity extends AppCompatActivity {
28 |
29 | private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "plugin.apk";
30 | @SuppressLint("HandlerLeak")
31 | private Handler handler = new Handler() {
32 | @Override
33 | public void handleMessage(@NonNull Message msg) {
34 | super.handleMessage(msg);
35 | if (msg.what == 666) {
36 | isLoadSuccess = true;
37 | if (dialog != null && dialog.isShowing()) {
38 | dialog.dismiss();
39 | }
40 | Toast.makeText(MainActivity.this, "加载插件成功!", Toast.LENGTH_SHORT).show();
41 | } else if (msg.what == 0) {
42 | if (dialog != null && dialog.isShowing()) {
43 | dialog.dismiss();
44 | }
45 | Toast.makeText(MainActivity.this, "加载插件失败,请检查插件是否存在!", Toast.LENGTH_SHORT).show();
46 | }
47 | }
48 | };
49 | //是否加载完成
50 | private boolean isLoadSuccess = false;
51 |
52 | private ProgressDialog dialog;
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 | setContentView(R.layout.activity_main);
58 | }
59 |
60 | @RequiresApi(api = Build.VERSION_CODES.M)
61 | public void loadPlugin(View view) {
62 | //判断是否已经赋予权限
63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
64 | if (ContextCompat.checkSelfPermission(this,
65 | Manifest.permission.WRITE_EXTERNAL_STORAGE)
66 | != PackageManager.PERMISSION_GRANTED) {
67 | requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, 666);
68 | } else {
69 | showProgress();
70 | PluginManager.getInstance(this).loadPlugin(handler, path);
71 | }
72 | } else {
73 | showProgress();
74 | PluginManager.getInstance(this).loadPlugin(handler, path);
75 | }
76 |
77 | }
78 |
79 |
80 | public void jumpPluginActivity(View view) {
81 | if (isLoadSuccess) {
82 | //获取插件包的Activity
83 | PackageManager packageManager = getPackageManager();
84 | PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
85 | //获取在manifest文件中注册的第一个activity
86 | ActivityInfo activity = packageArchiveInfo.activities[0];
87 | Intent intent = new Intent(this, ProxyActivity.class);
88 | intent.putExtra("className", activity.name);
89 | startActivity(intent);
90 | } else {
91 | Toast.makeText(this, "请先加载插件!", Toast.LENGTH_SHORT).show();
92 | }
93 |
94 | }
95 |
96 | private void showProgress() {
97 | if (dialog == null) {
98 | dialog = new ProgressDialog(this);
99 | }
100 | dialog.setTitle("加载插件中,请稍后。。。");
101 | dialog.setCancelable(false);
102 | dialog.show();
103 | }
104 |
105 | @Override
106 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
107 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
108 | if (requestCode == 666) {
109 | if (grantResults.length > 0
110 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
111 | showProgress();
112 | PluginManager.getInstance(this).loadPlugin(handler, path);
113 | } else {
114 | Toast.makeText(MainActivity.this, "请打开读取SD卡权限", Toast.LENGTH_SHORT).show();
115 | }
116 | }
117 | }
118 |
119 | public void parsePlugin(View view) {
120 | PluginManager.getInstance(this).parsePlugin(path);
121 | }
122 |
123 | public void sendStaticReceiver(View view) {
124 | Intent intent = new Intent("plugin_static_receiver");
125 | intent.putExtra("str", "我是从宿主中发来的字符");
126 | sendBroadcast(intent);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/yuong/pluginload/PluginManager.java:
--------------------------------------------------------------------------------
1 | package com.yuong.pluginload;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.IntentFilter;
7 | import android.content.pm.ActivityInfo;
8 | import android.content.pm.PackageManager;
9 | import android.content.res.AssetManager;
10 | import android.content.res.Resources;
11 | import android.os.Handler;
12 | import android.util.Log;
13 |
14 | import java.io.File;
15 | import java.lang.reflect.Field;
16 | import java.lang.reflect.Method;
17 | import java.util.ArrayList;
18 |
19 | import dalvik.system.DexClassLoader;
20 |
21 | public class PluginManager {
22 | private static final String TAG = PluginManager.class.getSimpleName();
23 | private static PluginManager instance;
24 | private Context context;
25 | private DexClassLoader dexClassLoader;
26 | private Resources pluginResource;
27 |
28 | private PluginManager(Context context) {
29 | this.context = context;
30 | }
31 |
32 | public static PluginManager getInstance(Context context) {
33 | if (instance == null) {
34 | synchronized (PluginManager.class) {
35 | if (instance == null) {
36 | instance = new PluginManager(context);
37 | }
38 | }
39 | }
40 | return instance;
41 | }
42 |
43 | public void loadPlugin(final Handler handler, final String path) {
44 | new Thread(new Runnable() {
45 | @Override
46 | public void run() {
47 | try {
48 | File file = new File(path);
49 | if (!file.exists()) {
50 | Log.e(TAG, "插件不存在");
51 | return;
52 | }
53 | File pluginDir = context.getDir("plugin", Context.MODE_PRIVATE);
54 | //加载插件的class
55 | dexClassLoader = new DexClassLoader(path, pluginDir.getAbsolutePath(), null, context.getClassLoader());
56 | //加载插件的资源文件
57 | //1、获取插件的AssetManager
58 | AssetManager pluginAssetManager = AssetManager.class.newInstance();
59 | Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
60 | addAssetPath.setAccessible(true);
61 | addAssetPath.invoke(pluginAssetManager, path);
62 | //2、获取宿主的Resources
63 | Resources appResources = context.getResources();
64 | //实例化插件的Resources
65 | pluginResource = new Resources(pluginAssetManager, appResources.getDisplayMetrics(), appResources.getConfiguration());
66 | if (dexClassLoader != null && pluginResource != null) {
67 | handler.sendEmptyMessage(666);
68 | } else {
69 | handler.sendEmptyMessage(0);
70 | }
71 | }catch (Exception e) {
72 | e.printStackTrace();
73 | handler.sendEmptyMessage(0);
74 | }
75 | }
76 | }).start();
77 |
78 | }
79 |
80 | @SuppressLint("PrivateApi")
81 | public void parsePlugin(String pluginPath) {
82 | try {
83 | File file = new File(pluginPath);
84 | if (!file.exists()) {
85 | Log.e(TAG, "插件不存在");
86 | return;
87 | }
88 | //1、解析插件包 public Package parsePackage(File packageFile, int flags)
89 | Class> mPackageParserClass = Class.forName("android.content.pm.PackageParser");
90 | Object mPackageParser = mPackageParserClass.newInstance();
91 | Method parsePackageMethod = mPackageParserClass.getMethod("parsePackage", File.class, int.class);
92 | Object mPackage = parsePackageMethod.invoke(mPackageParser, file, PackageManager.GET_ACTIVITIES);
93 |
94 | //2、获取Package类下的 public final ArrayList receivers = new ArrayList(0); 广播集合
95 | Field mReceiversField = mPackage.getClass().getDeclaredField("receivers");
96 | ArrayList