├── .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 receivers = (ArrayList) mReceiversField.get(mPackage); 97 | 98 | //3、遍历所有的静态广播 99 | //Activity 该Activity 不是四大组件里面的activity,而是一个Java bean对象,用来封装清单文件中的activity和receiver 100 | for (Object mActivity : receivers) { 101 | //4、获取该广播的全类名 即 android:name属性后面的值 102 | // /** 103 | // * Public name of this item. From the "android:name" attribute. 104 | // */ 105 | // public String name; 106 | 107 | // public static final ActivityInfo generateActivityInfo(Activity a, int flags, 108 | // PackageUserState state, int userId) 109 | //先获取到 ActivityInfo类 110 | Class mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState"); 111 | Object mPackageUserState = mPackageUserStateClass.newInstance(); 112 | 113 | Method generateActivityInfoMethod = mPackageParserClass.getMethod("generateActivityInfo", mActivity.getClass(), 114 | int.class, mPackageUserStateClass, int.class); 115 | //获取userId 116 | Class mUserHandleClass = Class.forName("android.os.UserHandle"); 117 | //public static @UserIdInt int getCallingUserId() 118 | int userId = (int) mUserHandleClass.getMethod("getCallingUserId").invoke(null); 119 | 120 | //执行此方法 由于是静态方法 所以不用传对象 121 | ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, mPackageUserState, userId); 122 | String receiverClassName = activityInfo.name; 123 | Class receiverClass = getClassLoader().loadClass(receiverClassName); 124 | BroadcastReceiver receiver = (BroadcastReceiver) receiverClass.newInstance(); 125 | 126 | //5、获取 intent-filter public final ArrayList intents;这个是intent-filter的集合 127 | //静态内部类反射要用 $+类名 128 | //getField(String name)只能获取public的字段,包括父类的; 129 | //而getDeclaredField(String name)只能获取自己声明的各种字段,包括public,protected,private。 130 | Class mComponentClass = Class.forName("android.content.pm.PackageParser$Component"); 131 | Field intentsField = mActivity.getClass().getField("intents"); 132 | ArrayList intents = (ArrayList) intentsField.get(mActivity); 133 | for (IntentFilter intentFilter : intents) { 134 | //6、注册广播 135 | context.registerReceiver(receiver, intentFilter); 136 | } 137 | 138 | } 139 | 140 | }catch (Exception e) { 141 | e.printStackTrace(); 142 | } 143 | 144 | } 145 | 146 | public Resources getResource() { 147 | return pluginResource; 148 | } 149 | 150 | public DexClassLoader getClassLoader() { 151 | return dexClassLoader; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/yuong/pluginload/PreferencesUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Project Name:SimonFramework 3 | * File Name:PreferencesUtils.java 4 | * Package Name:com.simon.framework.utils 5 | * Date:2016-5-19 下午2:24:22 6 | * Copyright (c) 2016, simon@cmonbaby.com All Rights Reserved. 7 | */ 8 | package com.yuong.pluginload; 9 | 10 | import android.content.Context; 11 | import android.content.SharedPreferences; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * 自定义Preferences存储器工具类 17 | *
    18 | * Preference Name 19 | *
  • you can change preference name by {@link #PREFERENCE_NAME}
  • 20 | *
21 | *
    22 | * Put Value 23 | *
  • put string {@link #putString(Context, String, String)}
  • 24 | *
  • put int {@link #putInt(Context, String, int)}
  • 25 | *
  • put long {@link #putLong(Context, String, long)}
  • 26 | *
  • put float {@link #putFloat(Context, String, float)}
  • 27 | *
  • put boolean {@link #putBoolean(Context, String, boolean)}
  • 28 | *
29 | *
    30 | * Get Value 31 | *
  • get string {@link #getString(Context, String)}, {@link #getString(Context, String, String)}
  • 32 | *
  • get int {@link #getInt(Context, String)}, {@link #getInt(Context, String, int)}
  • 33 | *
  • get long {@link #getLong(Context, String)}, {@link #getLong(Context, String, long)}
  • 34 | *
  • get float {@link #getFloat(Context, String)}, {@link #getFloat(Context, String, float)}
  • 35 | *
  • get boolean {@link #getBoolean(Context, String)}, {@link #getBoolean(Context, String, boolean)}
  • 36 | *
37 | *
    38 | * Other Opration 39 | *
  • remove String...keys {@link #removeSomeThing(Context, String...)}
  • 40 | *
  • clearSP {@link #clearSP(Context)}
  • 41 | *
  • contains {@link #contains(Context, String)}
  • 42 | *
  • getAll {@link #getAll(Context)}
  • 43 | *
44 | *
    45 | * Important Usage 46 | *
  • put {@link #put(Context, String, Object)}
  • 47 | *
  • get {@link #get(Context, String, Object)}
  • 48 | *
49 | * 50 | * @Title PreferencesUtils 51 | * @Package com.cmonbaby.framework.utils 52 | * @Description 自定义Preferences存储器工具类 53 | * @author simon 54 | * @date 2016-5-19 下午2:24:22 55 | * @since JDK1.8 SDK6.0.1 56 | * @version V2.3.4 57 | */ 58 | public class PreferencesUtils { 59 | 60 | public static String PREFERENCE_NAME = "yuongzw"; 61 | 62 | /** 63 | * put string preferences 64 | * 65 | * @param context 66 | * @param key The name of the preference to modify 67 | * @param value The new value for the preference 68 | * @return True if the new values were successfully written to persistent storage. 69 | */ 70 | public static boolean putString(Context context, String key, String value) { 71 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 72 | SharedPreferences.Editor editor = settings.edit(); 73 | editor.putString(key, value); 74 | return editor.commit(); 75 | } 76 | 77 | /** 78 | * get string preferences 79 | * 80 | * @param context 81 | * @param key The name of the preference to retrieve 82 | * @return The preference value if it exists, or "". Throws ClassCastException if there is a preference with this 83 | * name that is not a string 84 | * @see #getString(Context, String, String) 85 | */ 86 | public static String getString(Context context, String key) { 87 | return getString(context, key, ""); 88 | } 89 | 90 | /** 91 | * get string preferences 92 | * 93 | * @param context 94 | * @param key The name of the preference to retrieve 95 | * @param defaultValue Value to return if this preference does not exist 96 | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with 97 | * this name that is not a string 98 | */ 99 | public static String getString(Context context, String key, String defaultValue) { 100 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 101 | return settings.getString(key, defaultValue); 102 | } 103 | 104 | /** 105 | * put int preferences 106 | * 107 | * @param context 108 | * @param key The name of the preference to modify 109 | * @param value The new value for the preference 110 | * @return True if the new values were successfully written to persistent storage. 111 | */ 112 | public static boolean putInt(Context context, String key, int value) { 113 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 114 | SharedPreferences.Editor editor = settings.edit(); 115 | editor.putInt(key, value); 116 | return editor.commit(); 117 | } 118 | 119 | /** 120 | * get int preferences 121 | * 122 | * @param context 123 | * @param key The name of the preference to retrieve 124 | * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this 125 | * name that is not a int 126 | * @see #getInt(Context, String, int) 127 | */ 128 | public static int getInt(Context context, String key) { 129 | return getInt(context, key, -1); 130 | } 131 | 132 | /** 133 | * get int preferences 134 | * 135 | * @param context 136 | * @param key The name of the preference to retrieve 137 | * @param defaultValue Value to return if this preference does not exist 138 | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with 139 | * this name that is not a int 140 | */ 141 | public static int getInt(Context context, String key, int defaultValue) { 142 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 143 | return settings.getInt(key, defaultValue); 144 | } 145 | 146 | /** 147 | * put long preferences 148 | * 149 | * @param context 150 | * @param key The name of the preference to modify 151 | * @param value The new value for the preference 152 | * @return True if the new values were successfully written to persistent storage. 153 | */ 154 | public static boolean putLong(Context context, String key, long value) { 155 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 156 | SharedPreferences.Editor editor = settings.edit(); 157 | editor.putLong(key, value); 158 | return editor.commit(); 159 | } 160 | 161 | /** 162 | * get long preferences 163 | * 164 | * @param context 165 | * @param key The name of the preference to retrieve 166 | * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this 167 | * name that is not a long 168 | * @see #getLong(Context, String, long) 169 | */ 170 | public static long getLong(Context context, String key) { 171 | return getLong(context, key, -1); 172 | } 173 | 174 | /** 175 | * get long preferences 176 | * 177 | * @param context 178 | * @param key The name of the preference to retrieve 179 | * @param defaultValue Value to return if this preference does not exist 180 | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with 181 | * this name that is not a long 182 | */ 183 | public static long getLong(Context context, String key, long defaultValue) { 184 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 185 | return settings.getLong(key, defaultValue); 186 | } 187 | 188 | /** 189 | * put float preferences 190 | * 191 | * @param context 192 | * @param key The name of the preference to modify 193 | * @param value The new value for the preference 194 | * @return True if the new values were successfully written to persistent storage. 195 | */ 196 | public static boolean putFloat(Context context, String key, float value) { 197 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 198 | SharedPreferences.Editor editor = settings.edit(); 199 | editor.putFloat(key, value); 200 | return editor.commit(); 201 | } 202 | 203 | /** 204 | * get float preferences 205 | * 206 | * @param context 207 | * @param key The name of the preference to retrieve 208 | * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this 209 | * name that is not a float 210 | * @see #getFloat(Context, String, float) 211 | */ 212 | public static float getFloat(Context context, String key) { 213 | return getFloat(context, key, -1); 214 | } 215 | 216 | /** 217 | * get float preferences 218 | * 219 | * @param context 220 | * @param key The name of the preference to retrieve 221 | * @param defaultValue Value to return if this preference does not exist 222 | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with 223 | * this name that is not a float 224 | */ 225 | public static float getFloat(Context context, String key, float defaultValue) { 226 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 227 | return settings.getFloat(key, defaultValue); 228 | } 229 | 230 | /** 231 | * put boolean preferences 232 | * 233 | * @param context 234 | * @param key The name of the preference to modify 235 | * @param value The new value for the preference 236 | * @return True if the new values were successfully written to persistent storage. 237 | */ 238 | public static boolean putBoolean(Context context, String key, boolean value) { 239 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 240 | SharedPreferences.Editor editor = settings.edit(); 241 | editor.putBoolean(key, value); 242 | return editor.commit(); 243 | } 244 | 245 | /** 246 | * get boolean preferences, default is false 247 | * 248 | * @param context 249 | * @param key The name of the preference to retrieve 250 | * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this 251 | * name that is not a boolean 252 | * @see #getBoolean(Context, String, boolean) 253 | */ 254 | public static boolean getBoolean(Context context, String key) { 255 | return getBoolean(context, key, false); 256 | } 257 | 258 | /** 259 | * get boolean preferences 260 | * 261 | * @param context 262 | * @param key The name of the preference to retrieve 263 | * @param defaultValue Value to return if this preference does not exist 264 | * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with 265 | * this name that is not a boolean 266 | */ 267 | public static boolean getBoolean(Context context, String key, boolean defaultValue) { 268 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 269 | return settings.getBoolean(key, defaultValue); 270 | } 271 | 272 | /** 273 | * get all key and values 274 | * @param context 275 | * @return Map of all key and values 276 | */ 277 | public static Map getAll(Context context) { 278 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 279 | return settings.getAll(); 280 | } 281 | 282 | /** 283 | * check sp's key already exists if save 284 | * @param context 285 | * @param key The name of the preference to retrieve 286 | * @return true is exists 287 | */ 288 | public static boolean contains(Context context, String key) { 289 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 290 | return settings.contains(key); 291 | } 292 | 293 | /** 294 | * remove SomeThing from preferences 295 | * 296 | * @param context context 297 | * @param keys keys The name of the preference to retrieve 298 | */ 299 | public static boolean removeSomeThing(Context context, String...keys) { 300 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 301 | SharedPreferences.Editor editor = settings.edit(); 302 | if (keys == null) 303 | return false; 304 | for (String k : keys) { 305 | editor.remove(k); 306 | } 307 | editor.commit(); 308 | return true; 309 | } 310 | 311 | /** 312 | * clear all params from preferences 313 | * @param context context 314 | * @return True if the clear values were successfully. 315 | */ 316 | public static boolean clearSP(Context context) { 317 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 318 | SharedPreferences.Editor editor = settings.edit(); 319 | editor.clear(); 320 | editor.commit(); 321 | return true; 322 | } 323 | 324 | /** 325 | * Methods to save data, we need to get the specific type of the preservation of the data, 326 | * and then call different methods based on type 327 | * 328 | * @param context context 329 | * @param key The need to store the key 330 | * @param object The need to store the value 331 | */ 332 | public static void put(Context context, String key, Object object) { 333 | if (object instanceof String) { 334 | putString(context, key, (String) object); 335 | } else if (object instanceof Integer) { 336 | putInt(context, key, (Integer) object); 337 | } else if (object instanceof Boolean) { 338 | putBoolean(context, key, (Boolean) object); 339 | } else if (object instanceof Float) { 340 | putFloat(context, key, (Float) object); 341 | } else if (object instanceof Long) { 342 | putLong(context, key, (Long) object); 343 | } else { 344 | putString(context, key, object.toString()); 345 | } 346 | } 347 | 348 | /** 349 | * Methods to save the data, we get the specific type of the data saved according to the default values, 350 | * and then call the method to obtain the value of the method 351 | * 352 | * @param context context 353 | * @param key The need to obtain the key 354 | * @param defaultObject if obtain nothing, input default value 355 | * @return if nothing return the result was null 356 | */ 357 | public static Object get(Context context, String key, Object defaultObject) { 358 | if (defaultObject instanceof String) { 359 | return getString(context, key, (String) defaultObject); 360 | } else if (defaultObject instanceof Integer) { 361 | return getInt(context, key, (Integer) defaultObject); 362 | } else if (defaultObject instanceof Boolean) { 363 | return getBoolean(context, key, (Boolean) defaultObject); 364 | } else if (defaultObject instanceof Float) { 365 | return getFloat(context, key, (Float) defaultObject); 366 | } else if (defaultObject instanceof Long) { 367 | return getLong(context, key, (Long) defaultObject); 368 | } 369 | return null; 370 | } 371 | 372 | } 373 | 374 | -------------------------------------------------------------------------------- /app/src/main/java/com/yuong/pluginload/proxy/ProxyActivity.java: -------------------------------------------------------------------------------- 1 | package com.yuong.pluginload.proxy; 2 | 3 | import android.app.Activity; 4 | import android.content.BroadcastReceiver; 5 | import android.content.ComponentName; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.content.res.Resources; 9 | import android.os.Bundle; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | import com.yuong.pluginload.PluginManager; 14 | import com.yuong.transfer.IActivityInterface; 15 | 16 | import java.lang.reflect.Constructor; 17 | 18 | /** 19 | * 代理的Activity 20 | */ 21 | public class ProxyActivity extends Activity { 22 | 23 | private IActivityInterface pluginActivity1; 24 | private boolean isRegister; 25 | private ProxyReceiver proxyReceiver; 26 | 27 | @Override 28 | protected void onCreate(@Nullable Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | //真正的加载插件里面的Activity 31 | String className = getIntent().getStringExtra("className"); 32 | try { 33 | Class pluginActivity1Clazz = getClassLoader().loadClass(className); 34 | Constructor constructor = pluginActivity1Clazz.getConstructor(new Class[]{}); 35 | pluginActivity1 = (IActivityInterface) constructor.newInstance(new Object[]{}); 36 | pluginActivity1.insertAppContext(this); 37 | Bundle bundle = new Bundle(); 38 | bundle.putString("value", "我是宿主传递过来的字符串"); 39 | pluginActivity1.onCreate(bundle); 40 | 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | 45 | } 46 | 47 | @Override 48 | protected void onStart() { 49 | super.onStart(); 50 | pluginActivity1.onStart(); 51 | 52 | } 53 | 54 | @Override 55 | protected void onResume() { 56 | super.onResume(); 57 | pluginActivity1.onResume(); 58 | } 59 | 60 | @Override 61 | protected void onPause() { 62 | super.onPause(); 63 | pluginActivity1.onPause(); 64 | } 65 | 66 | @Override 67 | protected void onStop() { 68 | super.onStop(); 69 | pluginActivity1.onStop(); 70 | } 71 | 72 | @Override 73 | protected void onRestart() { 74 | super.onRestart(); 75 | pluginActivity1.onRestart(); 76 | } 77 | 78 | @Override 79 | protected void onDestroy() { 80 | super.onDestroy(); 81 | pluginActivity1.onDestroy(); 82 | if (isRegister) { 83 | unregisterReceiver(proxyReceiver); 84 | } 85 | } 86 | 87 | @Override 88 | public Resources getResources() { 89 | return PluginManager.getInstance(this).getResource(); 90 | } 91 | 92 | @Override 93 | public ClassLoader getClassLoader() { 94 | return PluginManager.getInstance(this).getClassLoader(); 95 | } 96 | 97 | @Override 98 | public void startActivity(Intent intent) { 99 | String className = intent.getStringExtra("className"); 100 | //自己跳自己 101 | Intent newIntent = new Intent(this, this.getClass()); 102 | newIntent.putExtra("className", className); 103 | super.startActivity(newIntent); 104 | } 105 | 106 | @Override 107 | public ComponentName startService(Intent service) { 108 | String className = service.getStringExtra("className"); 109 | ProxyService.pluginServiceClassName = className; 110 | //自己跳自己 111 | Intent newService = new Intent(this, ProxyService.class); 112 | newService.putExtra("className", className); 113 | return super.startService(newService); 114 | } 115 | 116 | @Override 117 | public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 118 | proxyReceiver = new ProxyReceiver(receiver.getClass().getName()); 119 | isRegister = true; 120 | return super.registerReceiver(proxyReceiver, filter); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/yuong/pluginload/proxy/ProxyReceiver.java: -------------------------------------------------------------------------------- 1 | package com.yuong.pluginload.proxy; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.yuong.pluginload.PluginManager; 8 | import com.yuong.transfer.IReceiverInterface; 9 | 10 | public class ProxyReceiver extends BroadcastReceiver { 11 | private String pluginReceiverClassName; 12 | 13 | public ProxyReceiver(String pluginReceiverClassName) { 14 | this.pluginReceiverClassName = pluginReceiverClassName; 15 | } 16 | @Override 17 | public void onReceive(Context context, Intent intent) { 18 | if ("yuongzw".equals(intent.getAction())) { 19 | try { 20 | Class pluginReceiverClass = PluginManager.getInstance(context).getClassLoader().loadClass(pluginReceiverClassName); 21 | IReceiverInterface pluginReceiver = (IReceiverInterface) pluginReceiverClass.newInstance(); 22 | pluginReceiver.onReceive(context, intent); 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/yuong/pluginload/proxy/ProxyService.java: -------------------------------------------------------------------------------- 1 | package com.yuong.pluginload.proxy; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.yuong.pluginload.PluginManager; 10 | import com.yuong.transfer.IServiceInterface; 11 | 12 | import java.lang.reflect.Constructor; 13 | 14 | public class ProxyService extends Service { 15 | 16 | public static String pluginServiceClassName = ""; 17 | private IServiceInterface pluginService; 18 | 19 | @Nullable 20 | @Override 21 | public IBinder onBind(Intent intent) { 22 | return null; 23 | } 24 | 25 | @Override 26 | public void onCreate() { 27 | super.onCreate(); 28 | initPlugin(); 29 | } 30 | 31 | @Override 32 | public int onStartCommand(Intent intent, int flags, int startId) { 33 | 34 | if (pluginService != null) { 35 | pluginService.onStartCommand(intent, flags, startId); 36 | } 37 | return super.onStartCommand(intent, flags, startId); 38 | } 39 | 40 | private void initPlugin() { 41 | try { 42 | Class pluginServiceClazz = PluginManager.getInstance(this).getClassLoader().loadClass(pluginServiceClassName); 43 | Constructor constructor = pluginServiceClazz.getConstructor(new Class[]{}); 44 | //获取某个service对象 45 | Object newInstance = constructor.newInstance(new Object[]{}); 46 | pluginService = (IServiceInterface) newInstance; 47 | pluginService.insertAppService(this); 48 | pluginService.onCreate(); 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | @Override 55 | public boolean onUnbind(Intent intent) { 56 | if (pluginService != null) { 57 | pluginService.onUnbind(intent); 58 | } 59 | return super.onUnbind(intent); 60 | } 61 | 62 | @Override 63 | public void onDestroy() { 64 | if (pluginService != null) { 65 | pluginService.onDestroy(); 66 | } 67 | super.onDestroy(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |