├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sq │ │ └── plugindemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── plugin.apk │ │ ├── plugin1.apk │ │ ├── plugin2.apk │ │ ├── plugin3.apk │ │ ├── plugin4.apk │ │ ├── plugin5.apk │ │ ├── plugin6.apk │ │ ├── plugin7.apk │ │ └── plugin8.apk │ ├── java │ │ └── com │ │ │ └── sq │ │ │ └── plugindemo │ │ │ ├── MainActivity.java │ │ │ └── plugin │ │ │ ├── Plugin.java │ │ │ ├── PluginManager.java │ │ │ ├── ProxyPluginActivity.java │ │ │ ├── loader │ │ │ └── ApkClassLoader.java │ │ │ └── resources │ │ │ ├── MixResources.java │ │ │ └── ResourcesWrapper.java │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── icon_logo_launch.png │ │ └── logo.png │ │ ├── layout │ │ └── activity_main.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── sq │ └── plugindemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── .gitignore ├── build.gradle ├── libs │ └── full.jar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sq │ │ └── plugin │ │ ├── BaseActivity.java │ │ ├── BaseWebViewClient.java │ │ ├── CustomDialog.java │ │ ├── PluginActivity.java │ │ └── SecondActivity.java │ └── res │ ├── drawable │ └── logo.png │ ├── layout │ ├── activity_plugin.xml │ ├── activity_plugin_second.xml │ └── dialog_custom.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle └── standard ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── sq │ └── standard │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── sq │ │ └── standard │ │ └── IActivityInterface.java └── res │ └── values │ └── strings.xml └── test └── java └── com └── sq └── standard └── 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 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.sq.plugindemo" 7 | minSdkVersion 19 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation project(path:':standard') 23 | } 24 | -------------------------------------------------------------------------------- /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/sq/plugindemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.runner.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.getTargetContext(); 24 | 25 | assertEquals("com.sq.plugindemo", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/assets/plugin.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin1.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin2.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin3.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin3.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin4.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin4.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin5.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin5.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin6.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin6.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin7.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin7.apk -------------------------------------------------------------------------------- /app/src/main/assets/plugin8.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/assets/plugin8.apk -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.view.Window; 8 | import android.view.WindowManager; 9 | import android.widget.Toast; 10 | 11 | import com.sq.plugindemo.plugin.Plugin; 12 | import com.sq.plugindemo.plugin.PluginManager; 13 | import com.sq.plugindemo.plugin.ProxyPluginActivity; 14 | 15 | public class MainActivity extends Activity implements View.OnClickListener { 16 | 17 | public static Plugin mPlugin; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | requestWindowFeature(Window.FEATURE_NO_TITLE); 23 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 24 | setContentView(R.layout.activity_main); 25 | findViewById(R.id.load_plugin).setOnClickListener(this); 26 | findViewById(R.id.open_activity).setOnClickListener(this); 27 | } 28 | 29 | private void startPluActivity() { 30 | if (mPlugin == null) { 31 | Toast.makeText(getApplicationContext(), "请先加载插件", Toast.LENGTH_SHORT).show(); 32 | return; 33 | } 34 | 35 | Intent intent = new Intent(MainActivity.this, ProxyPluginActivity.class); 36 | intent.putExtra("activity", "com.sq.plugin.PluginActivity"); 37 | startActivity(intent); 38 | } 39 | 40 | @Override 41 | public void onClick(View v) { 42 | switch (v.getId()) { 43 | case R.id.open_activity: 44 | startPluActivity(); 45 | break; 46 | case R.id.load_plugin: 47 | mPlugin = PluginManager.getInstance(MainActivity.this).loadPlugin("plugin8.apk"); 48 | Toast.makeText(getApplicationContext(), "加载插件成功", Toast.LENGTH_SHORT).show(); 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | 55 | @Override 56 | protected void onResume() { 57 | super.onResume(); 58 | } 59 | 60 | @Override 61 | protected void onStart() { 62 | super.onStart(); 63 | } 64 | 65 | @Override 66 | protected void onStop() { 67 | super.onStop(); 68 | } 69 | 70 | @Override 71 | protected void onDestroy() { 72 | super.onDestroy(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/plugin/Plugin.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo.plugin; 2 | 3 | import android.content.res.Resources; 4 | 5 | import com.sq.plugindemo.plugin.loader.ApkClassLoader; 6 | 7 | /** 8 | * @author zhuxiaoxin 9 | * 插件bean 10 | * 代表一个插件 11 | */ 12 | public class Plugin { 13 | 14 | public ApkClassLoader mClassLoader; 15 | 16 | public Resources mResource; 17 | 18 | public String mPath; 19 | 20 | public String getPluginPath() { 21 | return mPath; 22 | } 23 | 24 | public void setPluginPath(String path) { 25 | mPath = path; 26 | } 27 | 28 | public ClassLoader getClassLoader() { 29 | return mClassLoader; 30 | } 31 | 32 | public void setClassLoader(ApkClassLoader classLoader) { 33 | mClassLoader = classLoader; 34 | } 35 | 36 | public Resources getResource() { 37 | return mResource; 38 | } 39 | 40 | public void setResources(Resources resources) { 41 | mResource = resources; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/plugin/PluginManager.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo.plugin; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.res.AssetManager; 7 | import android.content.res.Resources; 8 | 9 | import com.sq.plugindemo.plugin.loader.ApkClassLoader; 10 | import com.sq.plugindemo.plugin.resources.MixResources; 11 | 12 | import java.io.File; 13 | import java.io.FileNotFoundException; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.lang.reflect.Method; 18 | 19 | /** 20 | * 占位式插件 21 | */ 22 | public class PluginManager { 23 | 24 | private static PluginManager sInstance; 25 | 26 | private Context mContext; 27 | 28 | private String mPluginPath; 29 | 30 | private PluginManager(Context context) { 31 | mContext = context; 32 | } 33 | 34 | public static PluginManager getInstance(Context context) { 35 | if (sInstance == null) { 36 | sInstance = new PluginManager(context); 37 | } 38 | return sInstance; 39 | } 40 | 41 | public Plugin loadPlugin(String name) { 42 | mPluginPath = copyAssetPlugin(name); 43 | Plugin plugin = new Plugin(); 44 | plugin.setPluginPath(mPluginPath); 45 | File file = mContext.getDir("plugin-opti", Context.MODE_PRIVATE); 46 | String[] interfaces = new String[]{"com.sq.standard"}; 47 | //获取ClassLoader 48 | ApkClassLoader pluginClassLoader = new ApkClassLoader(mPluginPath, file.getAbsolutePath(), null, mContext.getClassLoader(), interfaces); 49 | plugin.setClassLoader(pluginClassLoader); 50 | try { 51 | //该方法获取Resources会依赖育addAssetPath方法,而该方法已废弃 52 | // AssetManager assetManager = AssetManager.class.newInstance(); 53 | // Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); 54 | // addAssetPathMethod.invoke(assetManager, mPluginPath); 55 | // Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration()); 56 | Resources resources = new MixResources(mContext.getResources(), buildPluginResources()); 57 | plugin.setResources(resources); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | return plugin; 62 | } 63 | 64 | private Resources buildPluginResources(){ 65 | try { 66 | PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES|PackageManager.GET_META_DATA 67 | |PackageManager.GET_SERVICES 68 | |PackageManager.GET_PROVIDERS 69 | |PackageManager.GET_SIGNATURES); 70 | packageInfo.applicationInfo.publicSourceDir = mPluginPath; 71 | packageInfo.applicationInfo.sourceDir = mPluginPath; 72 | return mContext.getPackageManager().getResourcesForApplication(packageInfo.applicationInfo); 73 | } catch (PackageManager.NameNotFoundException e) { 74 | e.printStackTrace(); 75 | } 76 | return null; 77 | } 78 | 79 | private String copyAssetPlugin(String assetName) { 80 | InputStream inputStream = null; 81 | try { 82 | inputStream = mContext.getAssets().open(assetName); 83 | } catch (IOException e) { 84 | e.printStackTrace(); 85 | } 86 | File pluginDirFile = mContext.getDir("plugin", Context.MODE_PRIVATE); 87 | if (!pluginDirFile.exists()) { 88 | pluginDirFile.mkdirs(); 89 | } 90 | File resultFile = new File(pluginDirFile, assetName); 91 | writeInputStream(resultFile.getAbsolutePath(), inputStream); 92 | return resultFile.getAbsolutePath(); 93 | } 94 | 95 | private void writeInputStream(String storagePath, InputStream inputStream) { 96 | File file = new File(storagePath); 97 | try { 98 | if (!file.exists()) { 99 | // 1.建立通道对象 100 | FileOutputStream fos = new FileOutputStream(file); 101 | // 2.定义存储空间 102 | byte[] buffer = new byte[inputStream.available()]; 103 | // 3.开始读文件 104 | int lenght = 0; 105 | while ((lenght = inputStream.read(buffer)) != -1) {// 循环从输入流读取buffer字节 106 | // 将Buffer中的数据写到outputStream对象中 107 | fos.write(buffer, 0, lenght); 108 | } 109 | fos.flush();// 刷新缓冲区 110 | // 4.关闭流 111 | fos.close(); 112 | inputStream.close(); 113 | } 114 | } catch (FileNotFoundException e) { 115 | e.printStackTrace(); 116 | } catch (IOException e) { 117 | e.printStackTrace(); 118 | } 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/plugin/ProxyPluginActivity.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo.plugin; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.content.res.Resources; 6 | import android.os.Bundle; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | 10 | import com.sq.plugindemo.MainActivity; 11 | import com.sq.plugindemo.plugin.loader.ApkClassLoader; 12 | import com.sq.standard.IActivityInterface; 13 | 14 | public class ProxyPluginActivity extends Activity { 15 | 16 | static final String TAG = "37手游安卓团队"; 17 | 18 | @Override 19 | public ApkClassLoader getClassLoader() { 20 | return MainActivity.mPlugin.mClassLoader; 21 | } 22 | 23 | @Override 24 | public Resources getResources() { 25 | return MainActivity.mPlugin.mResource; 26 | } 27 | 28 | private IActivityInterface pluginActivity; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | Intent intent = getIntent(); 34 | 35 | if (intent != null && !TextUtils.isEmpty(intent.getStringExtra("activity"))) { 36 | try { 37 | pluginActivity = getClassLoader().getInterface(IActivityInterface.class, intent.getStringExtra("activity")); 38 | pluginActivity.insertAppContext(this); 39 | pluginActivity.onCreate(new Bundle()); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | } else { 44 | Log.e(TAG, "intent 中没带插件activity信息"); 45 | } 46 | } 47 | 48 | @Override 49 | protected void onStart() { 50 | super.onStart(); 51 | if (pluginActivity != null) { 52 | pluginActivity.onStart(); 53 | } 54 | } 55 | 56 | @Override 57 | protected void onResume() { 58 | super.onResume(); 59 | if (pluginActivity != null) { 60 | pluginActivity.onResume(); 61 | } 62 | } 63 | 64 | @Override 65 | public void startActivity(Intent intent) { 66 | if (!TextUtils.isEmpty(intent.getStringExtra("activity"))) { 67 | intent.setClass(this, ProxyPluginActivity.class); 68 | } 69 | super.startActivity(intent); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/plugin/loader/ApkClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo.plugin.loader; 2 | 3 | import android.os.Build; 4 | 5 | import dalvik.system.DexClassLoader; 6 | 7 | public class ApkClassLoader extends DexClassLoader { 8 | 9 | private ClassLoader mGrandParent; 10 | private final String[] mInterfacePackageNames; 11 | 12 | public ApkClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent, String[] interfacePackageNames) { 13 | super(dexPath, optimizedDirectory, librarySearchPath, parent); 14 | ClassLoader grand = parent; 15 | mGrandParent = grand.getParent(); 16 | this.mInterfacePackageNames = interfacePackageNames; 17 | } 18 | 19 | @Override 20 | protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { 21 | String packageName; 22 | int dot = className.lastIndexOf('.'); 23 | if (dot != -1) { 24 | packageName = className.substring(0, dot); 25 | } else { 26 | packageName = ""; 27 | } 28 | 29 | boolean isInterface = false; 30 | for (String interfacePackageName : mInterfacePackageNames) { 31 | if (packageName.equals(interfacePackageName)) { 32 | isInterface = true; 33 | break; 34 | } 35 | } 36 | 37 | if (isInterface) { 38 | return super.loadClass(className, resolve); 39 | } else { 40 | Class clazz = findLoadedClass(className); 41 | 42 | if (clazz == null) { 43 | ClassNotFoundException suppressed = null; 44 | try { 45 | clazz = findClass(className); 46 | } catch (ClassNotFoundException e) { 47 | suppressed = e; 48 | } 49 | 50 | if (clazz == null) { 51 | try { 52 | clazz = mGrandParent.loadClass(className); 53 | } catch (ClassNotFoundException e) { 54 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 55 | e.addSuppressed(suppressed); 56 | } 57 | throw e; 58 | } 59 | } 60 | } 61 | 62 | return clazz; 63 | } 64 | } 65 | 66 | /** 67 | * 从apk中读取接口的实现 68 | * 69 | * @param clazz 接口类 70 | * @param className 实现类的类名 71 | * @param 接口类型 72 | * @return 所需接口 73 | * @throws Exception 74 | */ 75 | public T getInterface(Class clazz, String className) throws Exception { 76 | try { 77 | Class interfaceImplementClass = loadClass(className); 78 | Object interfaceImplement = interfaceImplementClass.newInstance(); 79 | return clazz.cast(interfaceImplement); 80 | } catch (ClassNotFoundException | InstantiationException 81 | | ClassCastException | IllegalAccessException e) { 82 | throw new Exception(e); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/plugin/resources/MixResources.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo.plugin.resources; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.res.AssetFileDescriptor; 5 | import android.content.res.ColorStateList; 6 | import android.content.res.Resources; 7 | import android.content.res.XmlResourceParser; 8 | import android.graphics.Movie; 9 | import android.graphics.Typeface; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Build; 12 | import android.util.TypedValue; 13 | import java.io.InputStream; 14 | 15 | /** 16 | * Resources资源先从插件获取,如果获取不到则从宿主获取 17 | */ 18 | public class MixResources extends ResourcesWrapper { 19 | 20 | private Resources mHostResources; 21 | 22 | public MixResources(Resources hostResources, Resources pluginResources) { 23 | super(pluginResources); 24 | mHostResources = hostResources; 25 | } 26 | 27 | @Override 28 | public CharSequence getText(int id) throws NotFoundException { 29 | try { 30 | return super.getText(id); 31 | } catch (NotFoundException e) { 32 | return mHostResources.getText(id); 33 | } 34 | } 35 | 36 | @Override 37 | public String getString(int id) throws NotFoundException { 38 | try { 39 | return super.getString(id); 40 | } catch (NotFoundException e) { 41 | return mHostResources.getString(id); 42 | } 43 | } 44 | 45 | @Override 46 | public String getString(int id, Object... formatArgs) throws NotFoundException { 47 | try { 48 | return super.getString(id,formatArgs); 49 | } catch (NotFoundException e) { 50 | return mHostResources.getString(id,formatArgs); 51 | } 52 | } 53 | 54 | @Override 55 | public float getDimension(int id) throws NotFoundException { 56 | try { 57 | return super.getDimension(id); 58 | } catch (NotFoundException e) { 59 | return mHostResources.getDimension(id); 60 | } 61 | } 62 | 63 | @Override 64 | public int getDimensionPixelOffset(int id) throws NotFoundException { 65 | try { 66 | return super.getDimensionPixelOffset(id); 67 | } catch (NotFoundException e) { 68 | return mHostResources.getDimensionPixelOffset(id); 69 | } 70 | } 71 | 72 | @Override 73 | public int getDimensionPixelSize(int id) throws NotFoundException { 74 | try { 75 | return super.getDimensionPixelSize(id); 76 | } catch (NotFoundException e) { 77 | return mHostResources.getDimensionPixelSize(id); 78 | } 79 | } 80 | 81 | @Override 82 | public Drawable getDrawable(int id) throws NotFoundException { 83 | try { 84 | return super.getDrawable(id); 85 | } catch (NotFoundException e) { 86 | return mHostResources.getDrawable(id); 87 | } 88 | } 89 | 90 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 91 | @Override 92 | public Drawable getDrawable(int id, Theme theme) throws NotFoundException { 93 | try { 94 | return super.getDrawable(id, theme); 95 | } catch (NotFoundException e) { 96 | return mHostResources.getDrawable(id,theme); 97 | } 98 | } 99 | 100 | @Override 101 | public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { 102 | try { 103 | return super.getDrawableForDensity(id, density); 104 | } catch (NotFoundException e) { 105 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { 106 | return mHostResources.getDrawableForDensity(id, density); 107 | } else { 108 | return null; 109 | } 110 | } 111 | } 112 | 113 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 114 | @Override 115 | public Drawable getDrawableForDensity(int id, int density, Theme theme) { 116 | try { 117 | return super.getDrawableForDensity(id, density, theme); 118 | } catch (Exception e) { 119 | return mHostResources.getDrawableForDensity(id,density,theme); 120 | } 121 | } 122 | 123 | @Override 124 | public int getColor(int id) throws NotFoundException { 125 | try { 126 | return super.getColor(id); 127 | } catch (NotFoundException e) { 128 | return mHostResources.getColor(id); 129 | } 130 | } 131 | @TargetApi(Build.VERSION_CODES.M) 132 | @Override 133 | public int getColor(int id, Theme theme) throws NotFoundException { 134 | try { 135 | return super.getColor(id,theme); 136 | } catch (NotFoundException e) { 137 | return mHostResources.getColor(id,theme); 138 | } 139 | } 140 | 141 | @Override 142 | public ColorStateList getColorStateList(int id) throws NotFoundException { 143 | try { 144 | return super.getColorStateList(id); 145 | } catch (NotFoundException e) { 146 | return mHostResources.getColorStateList(id); 147 | } 148 | } 149 | @TargetApi(Build.VERSION_CODES.M) 150 | @Override 151 | public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException { 152 | try { 153 | return super.getColorStateList(id,theme); 154 | } catch (NotFoundException e) { 155 | return mHostResources.getColorStateList(id,theme); 156 | } 157 | } 158 | 159 | @Override 160 | public boolean getBoolean(int id) throws NotFoundException { 161 | try { 162 | return super.getBoolean(id); 163 | } catch (NotFoundException e) { 164 | return mHostResources.getBoolean(id); 165 | } 166 | } 167 | 168 | @Override 169 | public XmlResourceParser getLayout(int id) throws NotFoundException { 170 | try { 171 | return super.getLayout(id); 172 | } catch (NotFoundException e) { 173 | return mHostResources.getLayout(id); 174 | } 175 | } 176 | 177 | @Override 178 | public String getResourceName(int resid) throws NotFoundException { 179 | try { 180 | return super.getResourceName(resid); 181 | } catch (NotFoundException e) { 182 | return mHostResources.getResourceName(resid); 183 | } 184 | } 185 | 186 | @Override 187 | public int getInteger(int id) throws NotFoundException { 188 | try { 189 | return super.getInteger(id); 190 | } catch (NotFoundException e) { 191 | return mHostResources.getInteger(id); 192 | } 193 | } 194 | 195 | @Override 196 | public CharSequence getText(int id, CharSequence def) { 197 | try { 198 | return super.getText(id,def); 199 | } catch (NotFoundException e) { 200 | return mHostResources.getText(id,def); 201 | } 202 | } 203 | 204 | @Override 205 | public InputStream openRawResource(int id) throws NotFoundException { 206 | try { 207 | return super.openRawResource(id); 208 | } catch (NotFoundException e) { 209 | return mHostResources.openRawResource(id); 210 | } 211 | 212 | } 213 | 214 | @Override 215 | public XmlResourceParser getXml(int id) throws NotFoundException { 216 | try { 217 | return super.getXml(id); 218 | } catch (NotFoundException e) { 219 | return mHostResources.getXml(id); 220 | } 221 | } 222 | 223 | @TargetApi(Build.VERSION_CODES.O) 224 | @Override 225 | public Typeface getFont(int id) throws NotFoundException { 226 | try { 227 | return super.getFont(id); 228 | } catch (NotFoundException e) { 229 | return mHostResources.getFont(id); 230 | } 231 | } 232 | 233 | @Override 234 | public Movie getMovie(int id) throws NotFoundException { 235 | try { 236 | return super.getMovie(id); 237 | } catch (NotFoundException e) { 238 | return mHostResources.getMovie(id); 239 | } 240 | } 241 | 242 | @Override 243 | public XmlResourceParser getAnimation(int id) throws NotFoundException { 244 | try { 245 | return super.getAnimation(id); 246 | } catch (NotFoundException e) { 247 | return mHostResources.getAnimation(id); 248 | } 249 | } 250 | 251 | @Override 252 | public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { 253 | try { 254 | return super.openRawResource(id,value); 255 | } catch (NotFoundException e) { 256 | return mHostResources.openRawResource(id,value); 257 | } 258 | } 259 | 260 | @Override 261 | public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { 262 | try { 263 | return super.openRawResourceFd(id); 264 | } catch (NotFoundException e) { 265 | return mHostResources.openRawResourceFd(id); 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /app/src/main/java/com/sq/plugindemo/plugin/resources/ResourcesWrapper.java: -------------------------------------------------------------------------------- 1 | package com.sq.plugindemo.plugin.resources; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.res.AssetFileDescriptor; 5 | import android.content.res.ColorStateList; 6 | import android.content.res.Configuration; 7 | import android.content.res.Resources; 8 | import android.content.res.TypedArray; 9 | import android.content.res.XmlResourceParser; 10 | import android.graphics.Movie; 11 | import android.graphics.Typeface; 12 | import android.graphics.drawable.Drawable; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.util.AttributeSet; 16 | import android.util.DisplayMetrics; 17 | import android.util.TypedValue; 18 | 19 | import org.xmlpull.v1.XmlPullParserException; 20 | 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | 24 | public class ResourcesWrapper extends Resources { 25 | 26 | private Resources mBase; 27 | 28 | public ResourcesWrapper(Resources base){ 29 | super(base.getAssets(),base.getDisplayMetrics(),base.getConfiguration()); 30 | mBase = base; 31 | } 32 | 33 | @Override 34 | public CharSequence getText(int id) throws NotFoundException { 35 | return mBase.getText(id); 36 | } 37 | 38 | @TargetApi(Build.VERSION_CODES.O) 39 | @Override 40 | public Typeface getFont(int id) throws NotFoundException { 41 | return mBase.getFont(id); 42 | } 43 | 44 | @Override 45 | public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { 46 | return mBase.getQuantityText(id, quantity); 47 | } 48 | 49 | @Override 50 | public String getString(int id) throws NotFoundException { 51 | return mBase.getString(id); 52 | } 53 | 54 | @Override 55 | public String getString(int id, Object... formatArgs) throws NotFoundException { 56 | return mBase.getString(id, formatArgs); 57 | } 58 | 59 | @Override 60 | public String getQuantityString(int id, int quantity, Object... formatArgs) throws NotFoundException { 61 | return mBase.getQuantityString(id, quantity, formatArgs); 62 | } 63 | 64 | @Override 65 | public String getQuantityString(int id, int quantity) throws NotFoundException { 66 | return mBase.getQuantityString(id, quantity); 67 | } 68 | 69 | @Override 70 | public CharSequence getText(int id, CharSequence def) { 71 | return mBase.getText(id, def); 72 | } 73 | 74 | @Override 75 | public CharSequence[] getTextArray(int id) throws NotFoundException { 76 | return mBase.getTextArray(id); 77 | } 78 | 79 | @Override 80 | public String[] getStringArray(int id) throws NotFoundException { 81 | return mBase.getStringArray(id); 82 | } 83 | 84 | @Override 85 | public int[] getIntArray(int id) throws NotFoundException { 86 | return mBase.getIntArray(id); 87 | } 88 | 89 | @Override 90 | public TypedArray obtainTypedArray(int id) throws NotFoundException { 91 | return mBase.obtainTypedArray(id); 92 | } 93 | 94 | @Override 95 | public float getDimension(int id) throws NotFoundException { 96 | return mBase.getDimension(id); 97 | } 98 | 99 | @Override 100 | public int getDimensionPixelOffset(int id) throws NotFoundException { 101 | return mBase.getDimensionPixelOffset(id); 102 | } 103 | 104 | @Override 105 | public int getDimensionPixelSize(int id) throws NotFoundException { 106 | return mBase.getDimensionPixelSize(id); 107 | } 108 | 109 | @Override 110 | public float getFraction(int id, int base, int pbase) { 111 | return mBase.getFraction(id, base, pbase); 112 | } 113 | 114 | @Override 115 | public Drawable getDrawable(int id) throws NotFoundException { 116 | return mBase.getDrawable(id); 117 | } 118 | 119 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 120 | @Override 121 | public Drawable getDrawable(int id, Theme theme) throws NotFoundException { 122 | return mBase.getDrawable(id, theme); 123 | } 124 | 125 | @Override 126 | public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { 127 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { 128 | return mBase.getDrawableForDensity(id, density); 129 | } else { 130 | return null; 131 | } 132 | } 133 | 134 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 135 | @Override 136 | public Drawable getDrawableForDensity(int id, int density, Theme theme) { 137 | return mBase.getDrawableForDensity(id, density, theme); 138 | } 139 | 140 | @Override 141 | public Movie getMovie(int id) throws NotFoundException { 142 | return mBase.getMovie(id); 143 | } 144 | 145 | @Override 146 | public int getColor(int id) throws NotFoundException { 147 | return mBase.getColor(id); 148 | } 149 | 150 | @TargetApi(Build.VERSION_CODES.M) 151 | @Override 152 | public int getColor(int id, Theme theme) throws NotFoundException { 153 | return mBase.getColor(id, theme); 154 | } 155 | 156 | @Override 157 | public ColorStateList getColorStateList(int id) throws NotFoundException { 158 | return mBase.getColorStateList(id); 159 | } 160 | 161 | @TargetApi(Build.VERSION_CODES.M) 162 | @Override 163 | public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException { 164 | return mBase.getColorStateList(id, theme); 165 | } 166 | 167 | @Override 168 | public boolean getBoolean(int id) throws NotFoundException { 169 | return mBase.getBoolean(id); 170 | } 171 | 172 | @Override 173 | public int getInteger(int id) throws NotFoundException { 174 | return mBase.getInteger(id); 175 | } 176 | 177 | @Override 178 | public XmlResourceParser getLayout(int id) throws NotFoundException { 179 | return mBase.getLayout(id); 180 | } 181 | 182 | @Override 183 | public XmlResourceParser getAnimation(int id) throws NotFoundException { 184 | return mBase.getAnimation(id); 185 | } 186 | 187 | @Override 188 | public XmlResourceParser getXml(int id) throws NotFoundException { 189 | return mBase.getXml(id); 190 | } 191 | 192 | @Override 193 | public InputStream openRawResource(int id) throws NotFoundException { 194 | return mBase.openRawResource(id); 195 | } 196 | 197 | @Override 198 | public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { 199 | return mBase.openRawResource(id, value); 200 | } 201 | 202 | @Override 203 | public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { 204 | return mBase.openRawResourceFd(id); 205 | } 206 | 207 | @Override 208 | public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { 209 | mBase.getValue(id, outValue, resolveRefs); 210 | } 211 | 212 | @Override 213 | public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs) throws NotFoundException { 214 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { 215 | mBase.getValueForDensity(id, density, outValue, resolveRefs); 216 | } 217 | } 218 | 219 | @Override 220 | public void getValue(String name, TypedValue outValue, boolean resolveRefs) throws NotFoundException { 221 | mBase.getValue(name, outValue, resolveRefs); 222 | } 223 | 224 | @Override 225 | public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { 226 | return mBase.obtainAttributes(set, attrs); 227 | } 228 | 229 | @Override 230 | public DisplayMetrics getDisplayMetrics() { 231 | return mBase.getDisplayMetrics(); 232 | } 233 | 234 | @Override 235 | public Configuration getConfiguration() { 236 | return mBase.getConfiguration(); 237 | } 238 | 239 | @Override 240 | public int getIdentifier(String name, String defType, String defPackage) { 241 | return mBase.getIdentifier(name, defType, defPackage); 242 | } 243 | 244 | @Override 245 | public String getResourceName(int resid) throws NotFoundException { 246 | return mBase.getResourceName(resid); 247 | } 248 | 249 | @Override 250 | public String getResourcePackageName(int resid) throws NotFoundException { 251 | return mBase.getResourcePackageName(resid); 252 | } 253 | 254 | @Override 255 | public String getResourceTypeName(int resid) throws NotFoundException { 256 | return mBase.getResourceTypeName(resid); 257 | } 258 | 259 | @Override 260 | public String getResourceEntryName(int resid) throws NotFoundException { 261 | return mBase.getResourceEntryName(resid); 262 | } 263 | 264 | @Override 265 | public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) throws XmlPullParserException, IOException { 266 | mBase.parseBundleExtras(parser, outBundle); 267 | } 268 | 269 | @Override 270 | public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle) throws XmlPullParserException { 271 | mBase.parseBundleExtra(tagName, attrs, outBundle); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /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/drawable/icon_logo_launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/res/drawable/icon_logo_launch.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/37MobileTeam/PluginDemo/ac4010c76d230d60c95417c74a1a3aad5d500bb4/app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |