loadAll() {
72 | if (mPluginDBCallback != null) {
73 | PaLog.d("plugin mPluginDBCallback not null >>>>>>");
74 | return mPluginDBCallback.onPluginLoadAll();
75 | }
76 | return null;
77 |
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/PluginMain/src/com/example/pluginmain/PluginSampleFragmentActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.pluginmain;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v4.app.FragmentActivity;
6 | import android.support.v4.app.FragmentTransaction;
7 | import android.view.MenuItem;
8 | import android.widget.FrameLayout;
9 | import android.widget.FrameLayout.LayoutParams;
10 |
11 | import com.plugin.core.PluginLoader;
12 | import com.plugin.core.annotation.FragmentContainer;
13 | import com.plugin.util.PaLog;
14 |
15 | /**
16 | * 一个非常普通的FragmentActivty, 用来展示一个来自插件中的fragment。
17 | *
18 | * 这里需要通过注解来通知插件框架,此activity要展示的fragment来自那个插件
19 | *
20 | * @author cailiming
21 | *
22 | */
23 | @FragmentContainer(fragmentId = PluginSampleFragmentActivity.FRAGMENT_ID_IN_PLUGIN)
24 | public class PluginSampleFragmentActivity extends FragmentActivity {
25 |
26 | public static final String FRAGMENT_ID_IN_PLUGIN = "PluginDispatcher.fragmentId";
27 |
28 | private static final String LOG_TAG = PluginSampleFragmentActivity.class.getSimpleName();
29 |
30 | @Override
31 | public void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 |
34 | FrameLayout root = new FrameLayout(this);
35 | setContentView(root, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
36 | root.setId(android.R.id.primary);
37 |
38 | loadPluginFragment();
39 | }
40 |
41 | private void loadPluginFragment() {
42 | try {
43 | String classId = getIntent().getStringExtra(FRAGMENT_ID_IN_PLUGIN);
44 | PaLog.d(LOG_TAG, "loadPluginFragment, classId is " + classId);
45 | @SuppressWarnings("rawtypes")
46 | Class clazz = PluginLoader.loadPluginFragmentClassById(classId);
47 | Fragment fragment = (Fragment) clazz.newInstance();
48 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
49 | ft.replace(android.R.id.primary, fragment).commit();
50 | } catch (InstantiationException e) {
51 | e.printStackTrace();
52 | } catch (IllegalAccessException e) {
53 | e.printStackTrace();
54 | } catch (Exception e) {
55 | e.printStackTrace();
56 | }
57 | }
58 |
59 | @Override
60 | public boolean onOptionsItemSelected(MenuItem item) {
61 | if (item.getItemId() == android.R.id.home) {
62 | finish();
63 | return true;
64 | }
65 | return super.onOptionsItemSelected(item);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/content/PluginActivityInfo.java:
--------------------------------------------------------------------------------
1 | package com.plugin.content;
2 |
3 | import android.content.pm.ActivityInfo;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | *
9 | * ActivityInfo
10 | * @author Lyk
11 | *
12 | *
13 | */
14 | public class PluginActivityInfo implements Serializable {
15 |
16 | private String name;//string
17 | private String windowSoftInputMode;//strin
18 | private String hardwareAccelerated;//int string
19 | private String launchMode = String.valueOf(ActivityInfo.LAUNCH_MULTIPLE);//string
20 | private String screenOrientation;//string
21 | private String theme;//int
22 | private String immersive;//int string
23 | private String uiOptions;
24 | private boolean main;
25 |
26 |
27 |
28 | public String getUiOptions() {
29 | return uiOptions;
30 | }
31 |
32 | public void setUiOptions(String uiOptions) {
33 | this.uiOptions = uiOptions;
34 | }
35 |
36 | public String getImmersive() {
37 | return immersive;
38 | }
39 |
40 | public void setImmersive(String immersive) {
41 | this.immersive = immersive;
42 | }
43 |
44 | public String getName() {
45 | return name;
46 | }
47 |
48 | public void setName(String name) {
49 | this.name = name;
50 | }
51 |
52 | public String getWindowSoftInputMode() {
53 | return windowSoftInputMode;
54 | }
55 |
56 | public void setWindowSoftInputMode(String windowSoftInputMode) {
57 | this.windowSoftInputMode = windowSoftInputMode;
58 | }
59 |
60 | public String getHardwareAccelerated() {
61 | return hardwareAccelerated;
62 | }
63 |
64 | public void setHardwareAccelerated(String hardwareAccelerated) {
65 | this.hardwareAccelerated = hardwareAccelerated;
66 | }
67 |
68 | public String getLaunchMode() {
69 | return launchMode;
70 | }
71 |
72 | public void setLaunchMode(String launchMode) {
73 | this.launchMode = launchMode;
74 | }
75 |
76 | public String getScreenOrientation() {
77 | return screenOrientation;
78 | }
79 |
80 | public void setScreenOrientation(String screenOrientation) {
81 | this.screenOrientation = screenOrientation;
82 | }
83 |
84 | public String getTheme() {
85 | return theme;
86 | }
87 |
88 | public void setTheme(String theme) {
89 | this.theme = theme;
90 | }
91 |
92 | public boolean getMain() {
93 | return main;
94 | }
95 |
96 | public void setMain(boolean main) {
97 | this.main = main;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/PluginMain/res/layout/detail_activity.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
22 |
23 |
30 |
31 |
38 |
39 |
46 |
47 |
54 |
55 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/util/ResourceUtil.java:
--------------------------------------------------------------------------------
1 | package com.plugin.util;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Created by LiuYongkui by Date 2015.11.13
7 | */
8 | public class ResourceUtil {
9 |
10 | public static String getString(String value, Context pluginContext) {
11 | String idHex = null;
12 | if (value != null && value.startsWith("@") && value.length() == 9) {
13 | idHex = value.replace("@", "");
14 |
15 | } else if (value != null && value.startsWith("@android:") && value.length() == 17) {
16 | idHex = value.replace("@android:", "");
17 | }
18 |
19 | if (idHex != null) {
20 | try {
21 | int id = Integer.parseInt(idHex, 16);
22 | //此时context可能还没有初始化
23 | if (pluginContext != null) {
24 | String des = pluginContext.getString(id);
25 | return des;
26 | }
27 | } catch (Exception e) {
28 | e.printStackTrace();
29 | }
30 | }
31 |
32 | return value;
33 | }
34 |
35 | public static Boolean getBoolean(String value, Context pluginContext) {
36 | String idHex = null;
37 | if (value != null && value.startsWith("@") && value.length() == 9) {
38 | idHex = value.replace("@", "");
39 |
40 | } else if (value != null && value.startsWith("@android:") && value.length() == 17) {
41 | idHex = value.replace("@android:", "");
42 | }
43 |
44 | if (idHex != null) {
45 | try {
46 | int id = Integer.parseInt(idHex, 16);
47 | //此时context可能还没有初始化
48 | if (pluginContext != null) {
49 | return pluginContext.getResources().getBoolean(id);
50 | }
51 | } catch (Exception e) {
52 | e.printStackTrace();
53 | }
54 | } else if (value != null) {
55 | return Boolean.parseBoolean(value);
56 | }
57 |
58 | return null;
59 | }
60 |
61 | public static int getResourceId(String value) {
62 | String idHex = null;
63 | if (value != null && value.startsWith("@") && value.length() == 9) {
64 | idHex = value.replace("@", "");
65 |
66 | } else if (value != null && value.startsWith("@android:") && value.length() == 17) {
67 | idHex = value.replace("@android:", "");
68 | }
69 | if (idHex != null) {
70 | try {
71 | int id = Integer.parseInt(idHex, 16);
72 | return id;
73 | } catch (Exception e) {
74 | e.printStackTrace();
75 | }
76 | }
77 | return 0;
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginThemeHelper.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 |
6 | import com.plugin.content.PluginDescriptor;
7 | import com.plugin.util.RefInvoker;
8 |
9 | import java.lang.reflect.Field;
10 | import java.util.HashMap;
11 |
12 | public class PluginThemeHelper {
13 |
14 | public static int getPluginThemeIdByName(String pluginId, String themeName) {
15 | PluginDescriptor pd = PluginLoader.getPluginDescriptorByPluginId(pluginId);
16 | if (pd != null) {
17 | //插件可能尚未初始化,确保使用前已经初始化
18 | PluginLoader.ensurePluginInited(pd);
19 | }
20 | return pd.getPluginContext().getResources().getIdentifier(themeName, "style", pd.getPackageName());
21 | }
22 |
23 | public static HashMap getAllPluginThemes(String pluginId) {
24 | HashMap themes = new HashMap();
25 | PluginDescriptor pd = PluginLoader.getPluginDescriptorByPluginId(pluginId);
26 | if (pd != null) {
27 | //插件可能尚未初始化,确保使用前已经初始化
28 | PluginLoader.ensurePluginInited(pd);
29 |
30 | try {
31 | Class pluginRstyle = pd.getPluginClassLoader().loadClass(pluginId + ".R$style");
32 | if (pluginRstyle != null) {
33 | Field[] fields = pluginRstyle.getDeclaredFields();
34 | if (fields != null) {
35 | for (Field field :
36 | fields) {
37 | field.setAccessible(true);
38 | int themeResId = field.getInt(null);
39 | themes.put(field.getName(), themeResId);
40 | }
41 | }
42 | }
43 |
44 | } catch (IllegalAccessException e) {
45 | e.printStackTrace();
46 | } catch (ClassNotFoundException e) {
47 | e.printStackTrace();
48 | }
49 | }
50 |
51 |
52 | return themes;
53 | }
54 |
55 | /**
56 | * Used by host for skin
57 | * 宿主程序使用插件主题
58 | */
59 | public static void applyPluginTheme(Activity activity, String pluginId, int themeResId) {
60 |
61 | if (!(activity.getBaseContext() instanceof PluginContextTheme)) {
62 | PluginDescriptor pd = PluginLoader.getPluginDescriptorByPluginId(pluginId);
63 | if (pd != null) {
64 |
65 | //插件可能尚未初始化,确保使用前已经初始化
66 | PluginLoader.ensurePluginInited(pd);
67 |
68 | //注入插件上下文和主题
69 | Context defaultContext = pd.getPluginContext();
70 | Context pluginContext = PluginLoader.getNewPluginComponentContext(defaultContext, activity.getBaseContext());
71 | PluginInjector.resetActivityContext(pluginContext, activity, themeResId);
72 | }
73 | }
74 |
75 | }
76 |
77 | /**
78 | * Used by plugin for Theme
79 | * 插件使用插件主题
80 | */
81 | public static void setTheme(Context pluginContext, int resId) {
82 | if (pluginContext instanceof PluginContextTheme) {
83 | ((PluginContextTheme)pluginContext).mTheme = null;
84 | pluginContext.setTheme(resId);
85 | }
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/PluginMain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | aaptOptions {
8 | //additionalParameters "-P", "E:/workspace/Android-Plugin-Framework/PluginTest/res/values/public.xml"
9 | }
10 |
11 | packagingOptions {
12 | exclude 'META-INF/LICENSE.txt'
13 | exclude 'META-INF/NOTICE.txt'
14 | }
15 |
16 | defaultConfig {
17 | applicationId "com.example.pluginmain"
18 | minSdkVersion 8
19 | targetSdkVersion 22
20 | versionCode 1
21 | versionName "1.0"
22 | }
23 |
24 | lintOptions {
25 | checkReleaseBuilds false
26 | abortOnError false
27 | }
28 |
29 | sourceSets {
30 | main {
31 | manifest.srcFile 'AndroidManifest.xml'
32 | java.srcDirs = ['src']
33 | resources.srcDirs = ['src']
34 | aidl.srcDirs = ['src']
35 | renderscript.srcDirs = ['src']
36 | res.srcDirs = ['res']
37 | assets.srcDirs = ['assets']
38 | }
39 |
40 | // Move the tests to tests/java, tests/res, etc...
41 | instrumentTest.setRoot('tests')
42 |
43 | // Move the build types to build-types/
44 | // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
45 | // This moves them out of them default location under src//... which would
46 | // conflict with src/ being used by the main source set.
47 | // Adding new build types or product flavors should be accompanied
48 | // by a similar customization.
49 | debug.setRoot('build-types/debug')
50 | release.setRoot('build-types/release')
51 | }
52 |
53 | buildTypes {
54 | release {
55 | minifyEnabled false
56 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
57 | }
58 |
59 | }
60 | }
61 |
62 | dependencies {
63 | //compile 'com.android.support:appcompat-v7:23.0.0'
64 | compile fileTree(dir: 'libs', include: ['*.jar'])
65 | compile project(':PluginCore')
66 | compile project(':PluginShareLib')
67 | }
68 |
69 |
70 | //http://unclechen.github.io/2015/10/25/Gradle%E5%AE%9E%E8%B7%B5%E4%B9%8B%E6%89%93%E5%8C%85jar+Log%E5%BC%80%E5%85%B3%E8%87%AA%E5%8A%A8%E5%85%B3%E9%97%AD/
71 | //自定义混淆配置
72 | //def androidSDKDir = plugins.getPlugin('com.android.library').sdkHandler.getSdkFolder()
73 | //def androidJarDir = androidSDKDir.toString() + '/platforms/' + "${android.compileSdkVersion}" + '/android.jar'
74 |
75 | //task proguardMyLib(type: proguard.gradle.ProGuardTask, dependsOn: ['jarMyLib']) {
76 | // injars('build/libs/my-lib.jar')
77 | // outjars('build/libs/my-pro-lib.jar')
78 | // libraryjars(androidJarDir)
79 | // configuration 'proguard-rules.pro'
80 | //}
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/content/PluginProviderInfo.java:
--------------------------------------------------------------------------------
1 | package com.plugin.content;
2 |
3 | import java.io.Serializable;
4 | /**
5 | *
6 | * @author cailiming
7 | *
8 | *
9 | * Copy from Android SDK
10 | *
11 | */
12 | public class PluginProviderInfo implements Serializable {
13 |
14 | public static final String prefix = "plugin_content_provider_prefix.";
15 |
16 | private String name;
17 |
18 | private String packageName;
19 |
20 | private String processName;
21 |
22 | private boolean enabled = true;
23 |
24 | private boolean exported = false;
25 |
26 | private String authority = null;
27 |
28 | private String readPermission = null;
29 |
30 | private String writePermission = null;
31 |
32 | private boolean grantUriPermissions = false;
33 |
34 | private boolean multiprocess = false;
35 |
36 | private int initOrder = 0;
37 |
38 | public String getName() {
39 | return name;
40 | }
41 |
42 | public void setName(String name) {
43 | this.name = name;
44 | }
45 |
46 | public String getPackageName() {
47 | return packageName;
48 | }
49 |
50 | public void setPackageName(String packageName) {
51 | this.packageName = packageName;
52 | }
53 |
54 | public String getProcessName() {
55 | return processName;
56 | }
57 |
58 | public void setProcessName(String processName) {
59 | this.processName = processName;
60 | }
61 |
62 | public boolean isEnabled() {
63 | return enabled;
64 | }
65 |
66 | public void setEnabled(boolean enabled) {
67 | this.enabled = enabled;
68 | }
69 |
70 | public boolean isExported() {
71 | return exported;
72 | }
73 |
74 | public void setExported(boolean exported) {
75 | this.exported = exported;
76 | }
77 |
78 | public String getAuthority() {
79 | return authority;
80 | }
81 |
82 | public void setAuthority(String authority) {
83 | this.authority = authority;
84 | }
85 |
86 | public String getReadPermission() {
87 | return readPermission;
88 | }
89 |
90 | public void setReadPermission(String readPermission) {
91 | this.readPermission = readPermission;
92 | }
93 |
94 | public String getWritePermission() {
95 | return writePermission;
96 | }
97 |
98 | public void setWritePermission(String writePermission) {
99 | this.writePermission = writePermission;
100 | }
101 |
102 | public boolean isGrantUriPermissions() {
103 | return grantUriPermissions;
104 | }
105 |
106 | public void setGrantUriPermissions(boolean grantUriPermissions) {
107 | this.grantUriPermissions = grantUriPermissions;
108 | }
109 |
110 | public boolean isMultiprocess() {
111 | return multiprocess;
112 | }
113 |
114 | public void setMultiprocess(boolean multiprocess) {
115 | this.multiprocess = multiprocess;
116 | }
117 |
118 | public int getInitOrder() {
119 | return initOrder;
120 | }
121 |
122 | public void setInitOrder(int initOrder) {
123 | this.initOrder = initOrder;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/PluginMain/src/com/example/pluginmain/PluginDebugHelper.java:
--------------------------------------------------------------------------------
1 | package com.example.pluginmain;
2 |
3 | import java.util.Iterator;
4 |
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.pm.ApplicationInfo;
9 | import android.content.pm.PackageManager;
10 | import android.content.pm.PackageManager.NameNotFoundException;
11 | import android.text.TextUtils;
12 |
13 | import com.plugin.core.PluginLoader;
14 | import com.plugin.util.PaLog;
15 |
16 | /**
17 | * 监听插件apk的安装广播, 并安装插件到宿主程序。
18 | *
19 | * 并不是说插件apk需要安装到系统里面才可以使用,这只是为了方便调试,将已安装的apk当作插件apk的下载源.
20 | *
21 | * 否则的话我们在调试的时候需要更新插件apk就比较麻烦
22 | *
23 | * @author lyk
24 | *
25 | */
26 | public class PluginDebugHelper extends BroadcastReceiver {
27 |
28 | @Override
29 | public void onReceive(Context context, Intent intent) {
30 |
31 | String pluginApkPath = getSource(context, intent);
32 |
33 | String defaultTarget = getDefaultTarget(context, intent);
34 |
35 | if (!TextUtils.isEmpty(pluginApkPath) && !TextUtils.isEmpty(defaultTarget)) {
36 | int code = PluginLoader.installPlugin(pluginApkPath);
37 | if (code == 0) {
38 | FragmentHelper.startFragmentWithBuildInActivity(context, defaultTarget);
39 | }
40 | }
41 |
42 | }
43 |
44 | private String getSource(Context context, Intent intent) {
45 |
46 | PaLog.v("PluginInstaller", intent.toUri(0));
47 |
48 | if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
49 |
50 | PaLog.e("PluginInstaller", "onReceive " + intent.getData().getSchemeSpecificPart());
51 |
52 | try {
53 | ApplicationInfo pinfo = context.getPackageManager().getApplicationInfo(
54 | intent.getData().getSchemeSpecificPart(), PackageManager.GET_META_DATA);
55 |
56 | return pinfo.sourceDir;
57 |
58 | } catch (NameNotFoundException e) {
59 | e.printStackTrace();
60 | }
61 | } else if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
62 | || intent.getAction().equals(Intent.ACTION_PACKAGE_RESTARTED)) {
63 |
64 | }
65 |
66 | return null;
67 | }
68 |
69 | private String getDefaultTarget(Context context, Intent intent) {
70 |
71 | PaLog.v("PluginInstaller", intent.toUri(0));
72 |
73 | if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
74 |
75 | PaLog.e("PluginInstaller", "onReceive " + intent.getData().getSchemeSpecificPart());
76 |
77 | try {
78 | ApplicationInfo pinfo = context.getPackageManager().getApplicationInfo(
79 | intent.getData().getSchemeSpecificPart(), PackageManager.GET_META_DATA);
80 | if (pinfo != null && pinfo.metaData != null) {
81 | Iterator iterator = pinfo.metaData.keySet().iterator();
82 | while (iterator.hasNext()) {
83 | String key = iterator.next();
84 | String value = pinfo.metaData.getString(key);
85 | if (key.equals("launcher")) {
86 | return value;
87 | }
88 | }
89 | }
90 | } catch (NameNotFoundException e) {
91 | e.printStackTrace();
92 | }
93 | } else if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
94 | || intent.getAction().equals(Intent.ACTION_PACKAGE_RESTARTED)) {
95 |
96 | }
97 |
98 | return null;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/util/PaLog.java:
--------------------------------------------------------------------------------
1 | package com.plugin.util;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Log
7 | */
8 | public class PaLog {
9 |
10 | private static final boolean isDebug = true;
11 |
12 | private static final int stackLevel = 4;
13 |
14 | private static final String TAG = "Log_trace";
15 |
16 | public static void v(Object... msg) {
17 | printLog(Log.VERBOSE, msg);
18 | }
19 |
20 | public static void d(Object... msg) {
21 | printLog(Log.DEBUG, msg);
22 | }
23 |
24 | public static void e(Object... msg) {
25 | printLog(Log.ERROR, msg);
26 | }
27 |
28 | private static void printLog(int level, Object... msg) {
29 | if (isDebug) {
30 | StringBuilder str = new StringBuilder();
31 |
32 | if (msg != null) {
33 | for (Object obj : msg) {
34 | str.append("★").append(obj);
35 | }
36 | if (str.length() > 0) {
37 | str.deleteCharAt(0);
38 | }
39 | } else {
40 | str.append("null");
41 | }
42 | try {
43 | StackTraceElement[] sts = Thread.currentThread().getStackTrace();
44 | StackTraceElement st = null;
45 | String tag = null;
46 | if (sts != null && sts.length > stackLevel) {
47 | st = sts[stackLevel];
48 | if (st != null) {
49 | String fileName = st.getFileName();
50 | tag = (fileName == null) ? "Unkown" : fileName.replace(".java", "");
51 | str.insert(0, "【" + tag + "." + st.getMethodName() + "() line " +
52 | st.getLineNumber() + "】\n>>>[")
53 | .append("]");
54 | }
55 | }
56 |
57 | tag = (tag==null) ? "Plugin":("Plugin_" + tag);
58 | // use logcat log
59 | while (str.length() > 0) {
60 | if (level == Log.DEBUG) {
61 | Log.d(tag, str.substring(0, Math.min(2000, str.length())).toString());
62 | } else if (level == Log.ERROR) {
63 | Log.e(tag, str.substring(0, Math.min(2000, str.length())).toString());
64 | } else {
65 | Log.v(tag, str.substring(0, Math.min(2000, str.length())).toString());
66 | }
67 | str.delete(0, 2000);
68 | }
69 | } catch (Exception exception) {
70 | exception.printStackTrace();
71 | }
72 | }
73 | }
74 |
75 | public static void printStackTrace() {
76 | if (isDebug) {
77 | try {
78 | StackTraceElement[] sts = Thread.currentThread().getStackTrace();
79 | for (StackTraceElement stackTraceElement : sts) {
80 | Log.e(TAG, stackTraceElement.toString());
81 | }
82 | } catch (Exception exception) {
83 | exception.printStackTrace();
84 | }
85 | }
86 | }
87 |
88 | public static void printException(String msg, Throwable e) {
89 | if (isDebug) {
90 | Log.e(TAG, msg, e);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/util/JsonUtil.java:
--------------------------------------------------------------------------------
1 | package com.plugin.util;
2 |
3 |
4 |
5 | import com.alibaba.fastjson.JSON;
6 | import com.alibaba.fastjson.TypeReference;
7 | import com.alibaba.fastjson.parser.DefaultJSONParser;
8 | import com.alibaba.fastjson.parser.Feature;
9 | import com.alibaba.fastjson.parser.JSONLexer;
10 | import com.alibaba.fastjson.parser.ParserConfig;
11 |
12 | import java.lang.reflect.Type;
13 | import java.util.HashMap;
14 | import java.util.List;
15 |
16 | /**
17 | * json工具类
18 | * http://wangym.iteye.com/blog/738933
19 | * http://www.cnblogs.com/windlaughing/p/3241776.html
20 | *
21 | * fastjson有bug 属性名首字母小写但第二个字母大写的情况会无法解析javabean属性丢失 https://github.com/alibaba/fastjson/pull/106
22 | *
23 | * 性能考虑,使用jackon
24 | *
25 | *
26 | * jackson不是针对android平台的json lib, 在android 5.0以上会引起IncompatibleClassChangeError错误 https://github.com/FasterXML/jackson-databind/issues/782
27 | * 上述fastjson的bug目前官方已解决,故换回fastjson,针对android平台的json解析
28 | *
29 | * fastjson相对于jackson比较轻量级,依赖jar比较少
30 | *
31 | * @author jackzhou
32 | *
33 | */
34 | public class JsonUtil {
35 |
36 | /**
37 | * 解析实体
38 | * @param jsonStr
39 | * json字符串
40 | * @param entityClass
41 | * 实体类型
42 | * @param
43 | * 实体对象
44 | * @return
45 | */
46 | public static T parseObject(String jsonStr, Class entityClass) {
47 | T ret = null;
48 |
49 | try {
50 | ret = JSON.parseObject(jsonStr, entityClass);
51 | } catch (Exception e) {
52 | PaLog.e("will", "parseObject-something Exception with:" + e.toString());
53 | e.printStackTrace();
54 | }
55 |
56 | return ret;
57 | }
58 |
59 | public static T parseObject(String jsonStr, Type type) {
60 | T obj = null;
61 | try {
62 | obj = JSON.parseObject(jsonStr, type, Feature.AutoCloseSource);
63 | } catch (Exception e) {
64 | e.printStackTrace();
65 | }
66 | return obj;
67 | }
68 |
69 |
70 | /** 解析成map
71 | * @param jsonStr
72 | * @param tf
73 | * @param
74 | * @return
75 | */
76 | public static T parseObject(String jsonStr, TypeReference tf) {
77 | T obj = null;
78 | try {
79 | obj = JSON.parseObject(jsonStr, tf, Feature.AutoCloseSource);
80 | } catch (Exception e) {
81 | e.printStackTrace();
82 | }
83 | return obj;
84 | }
85 |
86 |
87 |
88 |
89 |
90 | /**
91 | * 解析List
92 | * @param jsonStr
93 | * @param entityClass
94 | * @param
95 | * @return
96 | */
97 | public static List parseList(String jsonStr, Class entityClass) {
98 | List ret = null;
99 |
100 | try {
101 | ret = JSON.parseArray(jsonStr, entityClass);
102 | } catch (Exception e) {
103 | e.printStackTrace();
104 | }
105 |
106 | return ret;
107 | }
108 |
109 | public static String toJSONString(Object obj) {
110 | String ret = null;
111 |
112 | try {
113 | ret = JSON.toJSONString(obj);
114 | } catch (Exception e) {
115 | e.printStackTrace();
116 | }
117 |
118 | return ret;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/util/RefInvoker.java:
--------------------------------------------------------------------------------
1 | package com.plugin.util;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 |
8 | /**
9 | * 反射执行器
10 | * @author Liuyongkui.
11 | */
12 | @SuppressWarnings("unchecked")
13 | public class RefInvoker {
14 |
15 | @SuppressWarnings("rawtypes")
16 | public static Object invokeStaticMethod(String className, String methodName, Class[] paramTypes,
17 | Object[] paramValues) {
18 |
19 | return invokeMethod(null, className, methodName, paramTypes, paramValues);
20 |
21 | }
22 |
23 | @SuppressWarnings("rawtypes")
24 | public static Object invokeMethod(Object target, String className, String methodName, Class[] paramTypes,
25 | Object[] paramValues) {
26 |
27 | try {
28 | Class clazz = Class.forName(className);
29 | Method method = clazz.getDeclaredMethod(methodName, paramTypes);
30 | method.setAccessible(true);
31 | return method.invoke(target, paramValues);
32 | } catch (SecurityException e) {
33 | e.printStackTrace();
34 | } catch (IllegalArgumentException e) {
35 | e.printStackTrace();
36 | } catch (IllegalAccessException e) {
37 | e.printStackTrace();
38 | } catch (NoSuchMethodException e) {
39 | e.printStackTrace();
40 | } catch (InvocationTargetException e) {
41 | e.printStackTrace();
42 | } catch (ClassNotFoundException e) {
43 | e.printStackTrace();
44 | }
45 | return null;
46 |
47 | }
48 |
49 | @SuppressWarnings("rawtypes")
50 | public static Object getFieldObject(Object target, Class clazz, String fieldName) {
51 | try {
52 | Field field = clazz.getDeclaredField(fieldName);
53 | field.setAccessible(true);
54 | return field.get(target);
55 | } catch (SecurityException e) {
56 | e.printStackTrace();
57 | } catch (NoSuchFieldException e) {
58 | // try supper for Miui, Miui has a class named MiuiPhoneWindow
59 | try {
60 | Field field = clazz.getSuperclass().getDeclaredField(fieldName);
61 | field.setAccessible(true);
62 | return field.get(target);
63 | } catch (Exception superE) {
64 | e.printStackTrace();
65 | superE.printStackTrace();
66 | }
67 | } catch (IllegalArgumentException e) {
68 | e.printStackTrace();
69 | } catch (IllegalAccessException e) {
70 | e.printStackTrace();
71 | }
72 | return null;
73 |
74 | }
75 |
76 | @SuppressWarnings("rawtypes")
77 | public static Object getFieldObject(Object target, String className, String fieldName) {
78 | Class clazz = null;
79 | try {
80 | clazz = Class.forName(className);
81 | return getFieldObject(target, clazz, fieldName);
82 | } catch (ClassNotFoundException e) {
83 | e.printStackTrace();
84 | }
85 | return null;
86 |
87 | }
88 |
89 | public static Object getStaticFieldObject(String className, String fieldName) {
90 |
91 | return getFieldObject(null, className, fieldName);
92 | }
93 |
94 | @SuppressWarnings("rawtypes")
95 | public static void setFieldObject(Object target, String className, String fieldName, Object fieldValue) {
96 | Class clazz = null;
97 | try {
98 | clazz = Class.forName(className);
99 | Field field = clazz.getDeclaredField(fieldName);
100 | field.setAccessible(true);
101 | field.set(target, fieldValue);
102 | } catch (SecurityException e) {
103 | e.printStackTrace();
104 | } catch (NoSuchFieldException e) {
105 | // try supper for Miui, Miui has a class named MiuiPhoneWindow
106 | try {
107 | Field field = clazz.getSuperclass().getDeclaredField(fieldName);
108 | field.setAccessible(true);
109 | field.set(target, fieldValue);
110 | } catch (Exception superE) {
111 | e.printStackTrace();
112 | superE.printStackTrace();
113 | }
114 | } catch (IllegalArgumentException e) {
115 | e.printStackTrace();
116 | } catch (IllegalAccessException e) {
117 | e.printStackTrace();
118 | } catch (ClassNotFoundException e) {
119 | e.printStackTrace();
120 | }
121 | }
122 |
123 | public static void setStaticOjbect(String className, String fieldName, Object fieldValue) {
124 | setFieldObject(null, className, fieldName, fieldValue);
125 | }
126 |
127 | }
--------------------------------------------------------------------------------
/PluginCore/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/PluginMain/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/PluginShareLib/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/PluginMain/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
14 |
21 |
22 |
28 |
29 |
30 |
38 |
39 |
44 |
45 |
52 |
53 |
62 |
63 |
72 |
73 |
82 |
83 |
92 |
93 |
100 |
101 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/PluginShareLib/build.gradle:
--------------------------------------------------------------------------------
1 | import java.util.jar.JarEntry
2 | import java.util.jar.JarOutputStream
3 | import java.util.zip.Deflater
4 |
5 | apply plugin: 'com.android.library'
6 |
7 | android {
8 | compileSdkVersion 22
9 | buildToolsVersion "22.0.1"
10 |
11 | defaultConfig {
12 | minSdkVersion 8
13 | targetSdkVersion 22
14 | versionCode 1
15 | versionName "1.0"
16 | }
17 |
18 | sourceSets {
19 | main {
20 | manifest.srcFile 'AndroidManifest.xml'
21 | java.srcDirs = ['src']
22 | resources.srcDirs = ['src']
23 | aidl.srcDirs = ['src']
24 | renderscript.srcDirs = ['src']
25 | res.srcDirs = ['res']
26 | assets.srcDirs = ['assets']
27 | }
28 |
29 | // Move the tests to tests/java, tests/res, etc...
30 | instrumentTest.setRoot('tests')
31 |
32 | // Move the build types to build-types/
33 | // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
34 | // This moves them out of them default location under src//... which would
35 | // conflict with src/ being used by the main source set.
36 | // Adding new build types or product flavors should be accompanied
37 | // by a similar customization.
38 | debug.setRoot('build-types/debug')
39 | release.setRoot('build-types/release')
40 | }
41 |
42 | buildTypes {
43 | release {
44 | minifyEnabled false
45 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
46 | }
47 | }
48 | }
49 |
50 | dependencies {
51 | //compile 'com.android.support:support-v4:23.0.0'
52 | //compile 'com.android.support:appcompat-v7:23.0.0'
53 | compile fileTree(dir: 'libs', include: ['*.jar'])
54 | }
55 |
56 | def manifestFile = android.sourceSets.main.manifest.srcFile
57 |
58 | def packageName = new XmlParser().parse(manifestFile).attribute('package')
59 |
60 | //定义一个生成Jar的t方法
61 | def jarTask(String outputJarPath, String packageNamePath) {
62 |
63 | final String buildDirPath = buildDir.absolutePath;
64 | final String intermediate = 'intermediates' + File.separator + 'classes' + File.separator + 'release' + File.separator;
65 | final String filter1 = 'R.class';
66 | final String filter2 = 'R$';
67 |
68 | println 'jarTask... ' + buildDirPath
69 |
70 | try {
71 |
72 | File outputJar = new File(outputJarPath);
73 | if (outputJar.exists()) {
74 | outputJar.delete();
75 | }
76 |
77 | JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputJar));
78 | jos.setLevel(Deflater.BEST_COMPRESSION);
79 | BufferedInputStream bis = null;
80 | byte[] cache = new byte[1024];
81 |
82 | File[] file = new File(buildDirPath, intermediate + packageNamePath).listFiles(
83 | new FileFilter() {
84 | @Override
85 | public boolean accept(File pathname) {
86 | if (filter1.equals(pathname.getName()) || pathname.getName().startsWith(filter2)) {
87 | return true;
88 | }
89 | return false;
90 | }
91 | });
92 |
93 | for(int i=0; i< file.length; i++) {
94 | bis = new BufferedInputStream(new FileInputStream(file[i]), 1024);
95 | println file[i].getAbsolutePath().replace(buildDirPath + File.separator + intermediate, "")
96 | jos.putNextEntry(new JarEntry(file[i].getAbsolutePath().replace(buildDirPath + File.separator + intermediate, "")));
97 | int count;
98 | while((count = bis.read(cache, 0, 1024)) != -1) {
99 | jos.write(cache, 0, count);
100 | }
101 | jos.closeEntry();
102 | bis.close();
103 | }
104 |
105 | jos.flush();
106 | jos.close();
107 |
108 | } catch(Exception ex) {
109 | ex.printStackTrace();
110 | }
111 | }
112 |
113 | build.doLast {
114 |
115 | //测试自定义task, 观察编译log里面是否有输出
116 | helloTask.execute()
117 |
118 | //生成R的jar包
119 | jarTask(buildDir.absolutePath + File.separator + 'outputs' + File.separator + 'rClasses.jar', packageName.replace('.', File.separator))
120 |
121 | }
122 |
123 | // 自定义task的用法
124 | task helloTask(type: HelloGradleTask) {
125 | helloStr = 'hello BBB from greeting'
126 | }
127 | // 自定义task的用法
128 | class HelloGradleTask extends DefaultTask {
129 |
130 | def String helloStr = 'hello AAA from HelloGradleTask'
131 |
132 | @TaskAction
133 | def hello() {
134 | println helloStr
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/proxy/PluginProxyService.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core.proxy;
2 |
3 | import java.util.HashMap;
4 | import java.util.Iterator;
5 |
6 | import android.app.Service;
7 | import android.content.Context;
8 | import android.content.ContextWrapper;
9 | import android.content.Intent;
10 | import android.os.Build;
11 | import android.os.IBinder;
12 |
13 | import com.plugin.core.PluginIntentResolver;
14 | import com.plugin.core.PluginLoader;
15 | import com.plugin.util.PaLog;
16 | import com.plugin.util.RefInvoker;
17 |
18 | /**
19 | * 由于service的特殊性,采用欺骗的方式加载插件Service时只能同时存在一个实例
20 | *
21 | * 所以这里仍然通过service代理的方式来支持多Service
22 | *
23 | * @author Liuyongkui
24 | *
25 | */
26 | public class PluginProxyService extends Service {
27 |
28 | private final HashMap serviceMap = new HashMap();
29 |
30 | @Override
31 | public int onStartCommand(Intent intent, int flags, int startId) {
32 | if (intent != null && intent.getAction() != null) {
33 |
34 | String action = intent.getAction();
35 | PaLog.d("onStartCommand action", action);
36 |
37 | if (action != null) {
38 |
39 | final boolean isDestoryCommond = action.contains(PluginIntentResolver.SERVICE_STOP_ACTION_IN_PLUGIN);
40 | String[] targetClassName;
41 | if (isDestoryCommond) {
42 | targetClassName = action.split(PluginIntentResolver.SERVICE_STOP_ACTION_IN_PLUGIN);
43 | } else {
44 | targetClassName = action.split(PluginIntentResolver.SERVICE_START_ACTION_IN_PLUGIN);
45 | }
46 |
47 | String clazzName = targetClassName[0];
48 |
49 | PaLog.d("tagertServiceClass ", clazzName);
50 |
51 | if (clazzName != null) {
52 |
53 | Service service = serviceMap.get(clazzName);
54 | Class clazz = null;
55 | if (service == null) {
56 | if (isDestoryCommond) {
57 | return super.onStartCommand(intent, flags, startId);
58 | } else {
59 | clazz = PluginLoader.loadPluginClassByName(clazzName);
60 | intent.setExtrasClassLoader(clazz.getClassLoader());
61 | try {
62 | service = (Service) clazz.newInstance();
63 | } catch (Exception e) {
64 | e.printStackTrace();
65 | }
66 | attach(service);
67 | service.onCreate();
68 | serviceMap.put(clazzName, service);
69 | }
70 | } else {
71 | clazz = service.getClass();
72 | intent.setExtrasClassLoader(clazz.getClassLoader());
73 | }
74 |
75 | if (isDestoryCommond) {
76 | service.onDestroy();
77 | serviceMap.remove(clazzName);
78 | } else {
79 | //由于之前intent被修改过 这里再吧Intent还原到原始的intent
80 | if (targetClassName.length > 1) {
81 | intent.setAction(targetClassName[1]);
82 | } else {
83 | intent.setAction(null);
84 | }
85 | service.onStartCommand(intent, flags, startId);
86 | }
87 | }
88 | }
89 | }
90 | return super.onStartCommand(intent, flags, startId);
91 | }
92 |
93 | /**
94 | * 暂不支持bind
95 | */
96 | @Override
97 | public IBinder onBind(Intent intent) {
98 | return null;
99 | }
100 |
101 | @Override
102 | public void onLowMemory() {
103 | Iterator itr = serviceMap.values().iterator();
104 | while (itr.hasNext()) {
105 | try {
106 | itr.next().onLowMemory();
107 | } catch (Exception e) {
108 | e.printStackTrace();
109 | }
110 | }
111 | }
112 |
113 | @Override
114 | public void onTrimMemory(int level) {
115 | if (Build.VERSION.SDK_INT >= 14) {
116 | Iterator itr = serviceMap.values().iterator();
117 | while (itr.hasNext()) {
118 | try {
119 | itr.next().onTrimMemory(level);
120 | } catch (Exception e) {
121 | e.printStackTrace();
122 | }
123 | }
124 | }
125 | }
126 |
127 | @Override
128 | public void onDestroy() {
129 | Iterator itr = serviceMap.values().iterator();
130 | while (itr.hasNext()) {
131 | try {
132 | itr.next().onDestroy();
133 | } catch (Exception e) {
134 | e.printStackTrace();
135 | }
136 | }
137 | serviceMap.clear();
138 | }
139 |
140 | private void attach(Service service) {
141 | RefInvoker.invokeMethod(service, ContextWrapper.class.getName(), "attachBaseContext",
142 | new Class[] { Context.class },
143 | new Object[] { PluginLoader.getDefaultPluginContext(service.getClass()) });
144 |
145 | set(service, "mClassName");
146 | set(service, "mToken");
147 | set(service, "mApplication");
148 | set(service, "mActivityManager");
149 | set(service, "mStartCompatibility");
150 | }
151 |
152 | private void set(Service service, String name) {
153 | PaLog.d("attach " + name);
154 | Object obj = RefInvoker.getFieldObject(this, Service.class.getName(), name);
155 | if (obj != null) {
156 | RefInvoker.setFieldObject(service, Service.class.getName(), name, obj);
157 | }
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/PluginCore/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
49 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
59 |
60 |
61 |
62 |
63 |
72 |
76 |
77 |
79 |
81 |
82 |
83 |
84 |
88 |
89 |
91 |
93 |
94 |
95 |
96 |
100 |
101 |
103 |
105 |
106 |
107 |
108 |
112 |
113 |
115 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginContextTheme.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.res.AssetManager;
6 | import android.content.res.Resources;
7 | import android.database.DatabaseErrorHandler;
8 | import android.database.sqlite.SQLiteDatabase;
9 | import android.view.LayoutInflater;
10 |
11 | import com.plugin.content.PluginDescriptor;
12 | import com.plugin.util.PaLog;
13 | import com.plugin.util.RefInvoker;
14 |
15 | public class PluginContextTheme extends PluginBaseContextWrapper {
16 | private int mThemeResource;
17 | Resources.Theme mTheme;
18 | private LayoutInflater mInflater;
19 |
20 | Resources mResources;
21 | private final ClassLoader mClassLoader;
22 |
23 | private final PluginDescriptor mPluginDescriptor;
24 |
25 | public PluginContextTheme(PluginDescriptor pluginDescriptor, Context base, Resources resources, ClassLoader classLoader) {
26 | super(base);
27 | mPluginDescriptor = pluginDescriptor;
28 | mResources = resources;
29 | mClassLoader = classLoader;
30 | }
31 |
32 | @Override
33 | protected void attachBaseContext(Context newBase) {
34 | super.attachBaseContext(newBase);
35 | }
36 |
37 | @Override
38 | public ClassLoader getClassLoader() {
39 | return mClassLoader;
40 | }
41 |
42 | @Override
43 | public AssetManager getAssets() {
44 | return mResources.getAssets();
45 | }
46 |
47 | @Override
48 | public Resources getResources() {
49 | return mResources;
50 | }
51 |
52 | /**
53 | * 传0表示使用系统默认主题,最终的现实样式和客户端程序的minSdk应该有关系。 即系统针对不同的minSdk设置了不同的默认主题样式
54 | * 传非0的话表示传过来什么主题就显示什么主题
55 | */
56 | @Override
57 | public void setTheme(int resid) {
58 | mThemeResource = resid;
59 | initializeTheme();
60 | }
61 |
62 | @Override
63 | public Resources.Theme getTheme() {
64 | if (mTheme != null) {
65 | return mTheme;
66 | }
67 |
68 | Object result = RefInvoker.invokeStaticMethod(Resources.class.getName(), "selectDefaultTheme", new Class[] {
69 | int.class, int.class }, new Object[] { mThemeResource,
70 | getBaseContext().getApplicationInfo().targetSdkVersion });
71 | if (result != null) {
72 | mThemeResource = (Integer) result;
73 | }
74 |
75 | initializeTheme();
76 |
77 | return mTheme;
78 | }
79 |
80 | @Override
81 | public Object getSystemService(String name) {
82 | if (LAYOUT_INFLATER_SERVICE.equals(name)) {
83 | if (mInflater == null) {
84 | mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
85 | }
86 | return mInflater;
87 | }
88 | return getBaseContext().getSystemService(name);
89 | }
90 |
91 | private void initializeTheme() {
92 | final boolean first = mTheme == null;
93 | if (first) {
94 | mTheme = getResources().newTheme();
95 | Resources.Theme theme = getBaseContext().getTheme();
96 | if (theme != null) {
97 | mTheme.setTo(theme);
98 | }
99 | }
100 | mTheme.applyStyle(mThemeResource, true);
101 | }
102 |
103 | @Override
104 | public Context getApplicationContext() {
105 | return mPluginDescriptor.getPluginApplication();
106 | }
107 |
108 | @Override
109 | public String getPackageName() {
110 | //如果是独立插件 返回插件本身的packageName
111 | //但是只返回插件本身的packageName可能会引起其他问题
112 | //例如1、会导致toast无法弹出,原因是toast弹出时会检查packageName是否时当前用户的
113 | // 2、导致宿主的Application获取的sharepreference和插件Activity获取的shareperference不在同一个xml里面
114 | //if (mPluginDescriptor.isStandalone()) {
115 | // return mPluginDescriptor.getPackageName();
116 | //} else {
117 | return super.getPackageName();
118 | //}
119 | }
120 |
121 | @Override
122 | public String getPackageCodePath() {
123 | //if (mPluginDescriptor.isStandalone()) {
124 | // return mPluginDescriptor.getInstalledPath();
125 | //} else {
126 | return super.getPackageCodePath();
127 | //}
128 | }
129 |
130 | public PluginDescriptor getPluginDescriptor() {
131 | return mPluginDescriptor;
132 | }
133 |
134 | /**
135 | * 隔离插件间的SharedPreferences
136 | * @param name
137 | * @param mode
138 | * @return
139 | */
140 | @Override
141 | public SharedPreferences getSharedPreferences(String name, int mode) {
142 | String realName = mPluginDescriptor.getPackageName() + "_" + name;
143 | PaLog.d(realName);
144 | return super.getSharedPreferences(realName, mode);
145 | }
146 |
147 | /**
148 | * 隔离插件间的Database
149 | * @param name
150 | * @param mode
151 | * @param factory
152 | * @return
153 | */
154 | @Override
155 | public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {
156 | String realName = mPluginDescriptor.getPackageName() + "_" + name;
157 | PaLog.d(realName);
158 | return super.openOrCreateDatabase(realName, mode, factory);
159 | }
160 |
161 | /**
162 | * 隔离插件间的Database
163 | * @param name
164 | * @param mode
165 | * @param factory
166 | * @return
167 | */
168 | @Override
169 | public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
170 | String realName = mPluginDescriptor.getPackageName() + "_" + name;
171 | PaLog.d(realName);
172 | return super.openOrCreateDatabase(realName, mode, factory, errorHandler);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/PluginMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
84 |
85 |
87 |
88 |
90 |
91 |
93 |
94 |
95 |
96 |
98 |
99 |
100 |
102 |
103 |
104 |
106 |
107 |
108 |
110 |
111 |
113 |
115 |
117 |
118 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginCreator.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core;
2 |
3 | import java.io.File;
4 | import java.lang.reflect.Method;
5 |
6 | import com.plugin.content.PluginDescriptor;
7 | import com.plugin.util.PaLog;
8 | import com.plugin.util.RefInvoker;
9 |
10 | import android.app.Application;
11 | import android.content.Context;
12 | import android.content.res.AssetManager;
13 | import android.content.res.Resources;
14 | import android.os.Build;
15 | import dalvik.system.DexClassLoader;
16 |
17 | public class PluginCreator {
18 |
19 | private PluginCreator() {
20 | }
21 |
22 | /**
23 | * 根据插件apk文件,创建插件dex的classloader
24 | *
25 | * @param absolutePluginApkPath
26 | * 插件apk文件路径
27 | * @return
28 | */
29 | public static DexClassLoader createPluginClassLoader(String absolutePluginApkPath, boolean isStandalone) {
30 | if (!isStandalone) {
31 | return new DexClassLoader(absolutePluginApkPath, new File(absolutePluginApkPath).getParent(),
32 | new File(absolutePluginApkPath).getParent() + File.separator + "lib",
33 | PluginLoader.class.getClassLoader());
34 | } else {
35 | return new DexClassLoader(absolutePluginApkPath, new File(absolutePluginApkPath).getParent(),
36 | new File(absolutePluginApkPath).getParent() + File.separator + "lib",
37 | PluginLoader.class.getClassLoader().getParent());
38 | }
39 |
40 | }
41 |
42 |
43 | /**
44 | * 根据插件apk文件,创建插件资源文件,同时绑定宿主程序的资源,这样就可以在插件中使用宿主程序的资源。
45 | *
46 | * @param application
47 | * 宿主程序的Application
48 | * @param absolutePluginApkPath
49 | * 插件apk文件路径
50 | * @return
51 | */
52 | public static Resources createPluginResource(Application application, String absolutePluginApkPath,
53 | boolean isStandalone) {
54 | try {
55 | // 如果是第三方编译的独立插件的话,插件的资源id默认是0x7f开头。
56 | // 但是宿主程序的资源id必须使用默认值0x7f(否则在5.x系统上主题会由问题)
57 | // 插件运行时可能会通过getActivityInfo等
58 | // 会拿到到PluginStubActivity的ActivityInfo以及ApplicationInfo
59 | // 这两个info里面有部分资源id是在宿主程序的Manifest中配置的,比如logo和icon
60 | // 所有如果在独立插件中尝试通过Context获取上述这些资源会导致异常
61 | String[] assetPaths = buildAssetPath(isStandalone, application.getApplicationInfo().sourceDir,
62 | absolutePluginApkPath);
63 | AssetManager assetMgr = AssetManager.class.newInstance();
64 | RefInvoker.invokeMethod(assetMgr, AssetManager.class.getName(), "addAssetPaths",
65 | new Class[] { String[].class }, new Object[] { assetPaths });
66 | // Method addAssetPaths =
67 | // AssetManager.class.getDeclaredMethod("addAssetPaths",
68 | // String[].class);
69 | // addAssetPaths.invoke(assetMgr, new Object[] { assetPaths });
70 |
71 | Resources mainRes = application.getResources();
72 | Resources pluginRes = new PluginResourceWrapper(assetMgr, mainRes.getDisplayMetrics(),
73 | mainRes.getConfiguration());
74 |
75 | return pluginRes;
76 | } catch (Exception e) {
77 | e.printStackTrace();
78 | }
79 | return null;
80 | }
81 |
82 | private static String[] buildAssetPath(boolean isStandalone, String app, String plugin) {
83 | String[] assetPaths = new String[isStandalone ? 1 : 2];
84 |
85 | if (!isStandalone) {
86 | // 不可更改顺序否则不能兼容4.x
87 | assetPaths[0] = app;
88 | assetPaths[1] = plugin;
89 | if ("vivo".equalsIgnoreCase(Build.BRAND) || "oppo".equalsIgnoreCase(Build.BRAND)
90 | || "Coolpad".equalsIgnoreCase(Build.BRAND)) {
91 | // 但是!!!如是OPPO或者vivo4.x系统的话 ,要吧这个顺序反过来,否则在混合模式下会找不到资源
92 | assetPaths[0] = plugin;
93 | assetPaths[1] = app;
94 | }
95 | PaLog.d("create Plugin Resource from: ", assetPaths[0], assetPaths[1]);
96 | } else {
97 | assetPaths[0] = plugin;
98 | PaLog.d("create Plugin Resource from: ", assetPaths[0]);
99 | }
100 |
101 | return assetPaths;
102 |
103 | }
104 |
105 | /**
106 | * 未使用
107 | */
108 | /* package */static Resources createPluginResourceFor5(Application application, String absolutePluginApkPath) {
109 | try {
110 | AssetManager assetMgr = AssetManager.class.newInstance();
111 | Method addAssetPaths = AssetManager.class.getDeclaredMethod("addAssetPaths", String[].class);
112 |
113 | String[] assetPaths = new String[2];
114 |
115 | // 不可更改顺序否则不能兼容4.x
116 | assetPaths[0] = absolutePluginApkPath;
117 | assetPaths[1] = application.getApplicationInfo().sourceDir;
118 |
119 | addAssetPaths.invoke(assetMgr, new Object[] { assetPaths });
120 |
121 | Resources mainRes = application.getResources();
122 | Resources pluginRes = new PluginResourceWrapper(assetMgr, mainRes.getDisplayMetrics(),
123 | mainRes.getConfiguration());
124 |
125 | PaLog.d("create Plugin Resource from: ", assetPaths[0], assetPaths[1]);
126 |
127 | return pluginRes;
128 | } catch (Exception e) {
129 | e.printStackTrace();
130 | }
131 | return null;
132 | }
133 |
134 | /**
135 | * 创建插件apk的Context。
136 | * 如果插件是运行在普通的Activity中,那么插件中需要使用context的地方,都需要使用此方法返回的Context
137 | *
138 | * @param application
139 | * @param pluginRes
140 | * @param pluginClassLoader
141 | * @return
142 | */
143 | static Context createPluginApplicationContext(PluginDescriptor pluginDescriptor, Application application, Resources pluginRes,
144 | DexClassLoader pluginClassLoader) {
145 | return new PluginContextTheme(pluginDescriptor, application, pluginRes, pluginClassLoader);
146 | }
147 |
148 | /**
149 | * 创建插件的Context
150 | * @return
151 | */
152 | static Context createPluginContext(PluginDescriptor pluginDescriptor, Context base, Resources pluginRes,
153 | DexClassLoader pluginClassLoader) {
154 | return new PluginContextTheme(pluginDescriptor, base, pluginRes, pluginClassLoader);
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/database/PluginSQLiteHelper.java:
--------------------------------------------------------------------------------
1 | package com.plugin.database;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.database.sqlite.SQLiteOpenHelper;
8 |
9 | import com.plugin.util.PaCursorUtils;
10 | import com.plugin.util.PaLog;
11 |
12 | /**
13 | * Plugin sqlite helper
14 | * Created by LIUYONGKUI on 2015-12-02.
15 | */
16 | public class PluginSQLiteHelper extends SQLiteOpenHelper {
17 |
18 | /** table name */
19 | private static final String TABLE_NAME = "pahf_plugintable";
20 | /** suffix */
21 | private static final String SUFFIX = "=?";
22 |
23 | /**
24 | * constructor
25 | *
26 | * @param aContext context
27 | */
28 | public PluginSQLiteHelper(Context aContext) {
29 | super(aContext, PluginDatabaseManager.DB_NAME, null, PluginDatabaseManager.DB_VERSION);
30 | }
31 |
32 | @Override
33 | public void onCreate(SQLiteDatabase db) {
34 | createTable(db);
35 | }
36 |
37 | @Override
38 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
39 | PaLog.d("PluginDB", "oldversion: " + oldVersion + " new version: " + newVersion);
40 |
41 | }
42 |
43 | /**
44 | * 增
45 | *
46 | * @param db db
47 | * @param values values
48 | * @return result
49 | */
50 | public static long insert(SQLiteDatabase db, ContentValues values) {
51 | if (db == null) {
52 | PaLog.e("PluginDB is null");
53 | return -1;
54 | }
55 | try {
56 | long ret = db.replace(TABLE_NAME, null, values);
57 | return ret;
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | return -1;
61 | }
62 | }
63 |
64 | /**
65 | * 删
66 | *
67 | * @param db db
68 | * @param selectionArgs args
69 | * @return result
70 | */
71 | public static int delete(SQLiteDatabase db, String[] selectionArgs) {
72 | try {
73 | return db.delete(TABLE_NAME, PluginDatabaseManager.Columns.PLUGIN_ID + SUFFIX, selectionArgs);
74 | } catch (Exception e) {
75 | e.printStackTrace();
76 | }
77 | return 0;
78 | }
79 |
80 | /**
81 | * 删全部
82 | *
83 | * @param db db
84 | * @return result
85 | */
86 | public static int deleteAll(SQLiteDatabase db) {
87 | try {
88 | return db.delete(TABLE_NAME, null, null);
89 | } catch (Exception e) {
90 | e.printStackTrace();
91 | }
92 | return 0;
93 | }
94 |
95 | /**
96 | * 改
97 | *
98 | * @param db db
99 | * @param values values
100 | * @param selectionArgs args
101 | * @return result
102 | */
103 | public static long update(SQLiteDatabase db, ContentValues values, String[] selectionArgs) {
104 | try {
105 | return db.replace(TABLE_NAME, null,values);
106 | } catch (Exception e) {
107 | e.printStackTrace();
108 | }
109 | return -1;
110 | }
111 |
112 | /**
113 | * 查
114 | *
115 | * @param db db
116 | * @param columns colums
117 | * @param selectionArgs args
118 | * @param groupBy groupby
119 | * @param having having
120 | * @param orderBy orderby
121 | * @return cursor
122 | */
123 | public static Cursor query(SQLiteDatabase db, String[] columns, String[] selectionArgs, String groupBy,
124 | String having, String orderBy) {
125 | PaCursor cursor = null;
126 | try {
127 | cursor = PaCursorUtils.getCursor(db.query(TABLE_NAME, columns, null,
128 | selectionArgs, groupBy, having, orderBy));
129 | } catch (Exception e) {
130 | e.printStackTrace();
131 | }
132 | return cursor;
133 | }
134 |
135 | /**
136 | * create table
137 | *
138 | * @param db db
139 | */
140 | private void createTable(SQLiteDatabase db) {
141 | try {
142 | String cmd = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ( "
143 | + PluginDatabaseManager.Columns.PLUGIN_ID + " TEXT PRIMARY KEY NOT NULL,"
144 | + PluginDatabaseManager.Columns.URL + " TEXT NOT NULL,"
145 | + PluginDatabaseManager.Columns.PACKAGE_NAME + " TEXT NOT NULL,"
146 | + PluginDatabaseManager.Columns.PLUGIN_NAME + " TEXT DEFAULT '',"
147 | + PluginDatabaseManager.Columns.INSTALL_PATH + " TEXT NOT NULL,"
148 | + PluginDatabaseManager.Columns.VERSION + " TEXT DEFAULT '',"
149 | + PluginDatabaseManager.Columns.TYPE + " INTEGER DEFAULT 0,"
150 | + PluginDatabaseManager.Columns.STATUS + " INTEGER DEFAULT 1, "
151 | + PluginDatabaseManager.Columns.APK_MAINACTIVITY + " TEXT DEFAULT '',"
152 | + PluginDatabaseManager.Columns.DESCRIPTION + " TEXT DEFAULT '',"
153 | + PluginDatabaseManager.Columns.APP_ICON + " BLOB,"
154 | + PluginDatabaseManager.Columns.IS_STANDALONE + " INTEGER DEFAULT 0,"
155 | + PluginDatabaseManager.Columns.IS_ENABLED + " INTEGER DEFAULT 0,"
156 | + PluginDatabaseManager.Columns.APPLICATION_NAME + " TEXT DEFAULT '',"
157 | + PluginDatabaseManager.Columns.APPLICATION_LOGO + " INTEGER DEFAULT 0,"
158 | + PluginDatabaseManager.Columns.APPLICATION_ICON + " INTEGER DEFAULT 0,"
159 | + PluginDatabaseManager.Columns.APPLICATION_THEME + " INTEGER DEFAULT 0,"
160 | + PluginDatabaseManager.Columns.ACTIVITY_INFOS + " TEXT DEFAULT '',"
161 | + PluginDatabaseManager.Columns.ACTIVITYS + " TEXT DEFAULT '',"
162 | + PluginDatabaseManager.Columns.SERVICES + " TEXT DEFAULT '',"
163 | + PluginDatabaseManager.Columns.PROVIDER_INFOS + " TEXT DEFAULT '',"
164 | + PluginDatabaseManager.Columns.FRAGMENTS + " TEXT DEFAULT '',"
165 | + PluginDatabaseManager.Columns.METADATA + " TEXT DEFAULT '',"
166 | + PluginDatabaseManager.Columns.RECEIVERS + " TEXT DEFAULT ''"
167 | + " ) ";
168 | db.execSQL(cmd);
169 | } catch (Exception e) {
170 | e.printStackTrace();
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/content/PluginPatternMatcher.java:
--------------------------------------------------------------------------------
1 | package com.plugin.content;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * A simple pattern matcher, which is safe to use on untrusted data: it does
7 | * not provide full reg-exp support, only simple globbing that can not be
8 | * used maliciously.
9 | *
10 | *
11 | *
12 | * Copy from Android SDK
13 | *
14 | */
15 | public class PluginPatternMatcher implements Serializable {
16 | /**
17 | *
18 | */
19 | private static final long serialVersionUID = -881941408600225278L;
20 |
21 | /**
22 | * Pattern type: the given pattern must exactly match the string it is
23 | * tested against.
24 | */
25 | public static final int PATTERN_LITERAL = 0;
26 |
27 | /**
28 | * Pattern type: the given pattern must match the
29 | * beginning of the string it is tested against.
30 | */
31 | public static final int PATTERN_PREFIX = 1;
32 |
33 | /**
34 | * Pattern type: the given pattern is interpreted with a
35 | * simple glob syntax for matching against the string it is tested against.
36 | * In this syntax, you can use the '*' character to match against zero or
37 | * more occurrences of the character immediately before. If the
38 | * character before it is '.' it will match any character. The character
39 | * '\' can be used as an escape. This essentially provides only the '*'
40 | * wildcard part of a normal regexp.
41 | */
42 | public static final int PATTERN_SIMPLE_GLOB = 2;
43 |
44 | private final String mPattern;
45 | private final int mType;
46 |
47 | public PluginPatternMatcher(String pattern, int type) {
48 | mPattern = pattern;
49 | mType = type;
50 | }
51 |
52 | public final String getPath() {
53 | return mPattern;
54 | }
55 |
56 | public final int getType() {
57 | return mType;
58 | }
59 |
60 | public boolean match(String str) {
61 | return matchPattern(mPattern, str, mType);
62 | }
63 |
64 | public String toString() {
65 | String type = "? ";
66 | switch (mType) {
67 | case PATTERN_LITERAL:
68 | type = "LITERAL: ";
69 | break;
70 | case PATTERN_PREFIX:
71 | type = "PREFIX: ";
72 | break;
73 | case PATTERN_SIMPLE_GLOB:
74 | type = "GLOB: ";
75 | break;
76 | }
77 | return "PatternMatcher{" + type + mPattern + "}";
78 | }
79 |
80 | static boolean matchPattern(String pattern, String match, int type) {
81 | if (match == null) return false;
82 | if (type == PATTERN_LITERAL) {
83 | return pattern.equals(match);
84 | } if (type == PATTERN_PREFIX) {
85 | return match.startsWith(pattern);
86 | } else if (type != PATTERN_SIMPLE_GLOB) {
87 | return false;
88 | }
89 |
90 | final int NP = pattern.length();
91 | if (NP <= 0) {
92 | return match.length() <= 0;
93 | }
94 | final int NM = match.length();
95 | int ip = 0, im = 0;
96 | char nextChar = pattern.charAt(0);
97 | while ((ip= (NP-1)) {
110 | // at the end with a pattern match, so
111 | // all is good without checking!
112 | return true;
113 | }
114 | ip++;
115 | nextChar = pattern.charAt(ip);
116 | // Consume everything until the next character in the
117 | // pattern is found.
118 | if (nextChar == '\\') {
119 | ip++;
120 | nextChar = ip < NP ? pattern.charAt(ip) : 0;
121 | }
122 | do {
123 | if (match.charAt(im) == nextChar) {
124 | break;
125 | }
126 | im++;
127 | } while (im < NM);
128 | if (im == NM) {
129 | // Whoops, the next character in the pattern didn't
130 | // exist in the match.
131 | return false;
132 | }
133 | ip++;
134 | nextChar = ip < NP ? pattern.charAt(ip) : 0;
135 | im++;
136 | } else {
137 | // Consume only characters matching the one before '*'.
138 | do {
139 | if (match.charAt(im) != c) {
140 | break;
141 | }
142 | im++;
143 | } while (im < NM);
144 | ip++;
145 | nextChar = ip < NP ? pattern.charAt(ip) : 0;
146 | }
147 | } else {
148 | if (c != '.' && match.charAt(im) != c) return false;
149 | im++;
150 | }
151 | }
152 |
153 | if (ip >= NP && im >= NM) {
154 | // Reached the end of both strings, all is good!
155 | return true;
156 | }
157 |
158 | // One last check: we may have finished the match string, but still
159 | // have a '.*' at the end of the pattern, which should still count
160 | // as a match.
161 | if (ip == NP-2 && pattern.charAt(ip) == '.'
162 | && pattern.charAt(ip+1) == '*') {
163 | return true;
164 | }
165 |
166 | return false;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginBaseContextWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2006 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.plugin.core;
18 |
19 | import android.annotation.TargetApi;
20 | import android.content.BroadcastReceiver;
21 | import android.content.ComponentName;
22 | import android.content.Context;
23 | import android.content.ContextWrapper;
24 | import android.content.Intent;
25 | import android.content.ServiceConnection;
26 | import android.os.Build;
27 | import android.os.Bundle;
28 | import android.os.Handler;
29 | import android.os.UserHandle;
30 |
31 | import com.plugin.util.PaLog;
32 |
33 | public class PluginBaseContextWrapper extends ContextWrapper {
34 |
35 | public PluginBaseContextWrapper(Context base) {
36 | super(base);
37 | }
38 |
39 | /**
40 | * startActivity有很多重载的方法,如有必要,可以相应的重写
41 | */
42 | @Override
43 | public void startActivity(Intent intent) {
44 | PaLog.d(intent);
45 | PluginIntentResolver.resolveActivity(intent);
46 | super.startActivity(intent);
47 | }
48 |
49 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
50 | @Override
51 | public void startActivity(Intent intent, Bundle options) {
52 | PaLog.d(intent);
53 | PluginIntentResolver.resolveActivity(intent);
54 | super.startActivity(intent, options);
55 | }
56 |
57 | @Override
58 | public void sendBroadcast(Intent intent) {
59 | PaLog.d(intent);
60 | intent = PluginIntentResolver.resolveReceiver(intent);
61 | super.sendBroadcast(intent);
62 | }
63 |
64 | @Override
65 | public void sendBroadcast(Intent intent, String receiverPermission) {
66 | PaLog.d(intent);
67 | intent = PluginIntentResolver.resolveReceiver(intent);
68 | super.sendBroadcast(intent, receiverPermission);
69 | }
70 |
71 | @Override
72 | public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
73 | PaLog.d(intent);
74 | intent = PluginIntentResolver.resolveReceiver(intent);
75 | super.sendOrderedBroadcast(intent, receiverPermission);
76 | }
77 |
78 | @Override
79 | public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
80 | Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
81 | PaLog.d(intent);
82 | intent = PluginIntentResolver.resolveReceiver(intent);
83 | super.sendOrderedBroadcast(intent, receiverPermission, resultReceiver,
84 | scheduler, initialCode, initialData, initialExtras);
85 |
86 | }
87 |
88 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
89 | @Override
90 | public void sendBroadcastAsUser(Intent intent, UserHandle user) {
91 | PaLog.d(intent);
92 | intent = PluginIntentResolver.resolveReceiver(intent);
93 | super.sendBroadcastAsUser(intent, user);
94 | }
95 |
96 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
97 | @Override
98 | public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) {
99 | PaLog.d(intent);
100 | intent = PluginIntentResolver.resolveReceiver(intent);
101 | super.sendBroadcastAsUser(intent, user, receiverPermission);
102 | }
103 |
104 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
105 | @Override
106 | public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
107 | BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
108 | Bundle initialExtras) {
109 | PaLog.d(intent);
110 | intent = PluginIntentResolver.resolveReceiver(intent);
111 | super.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver, scheduler, initialCode,
112 | initialData, initialExtras);
113 | }
114 |
115 | @Override
116 | public void sendStickyBroadcast(Intent intent) {
117 | PaLog.d(intent);
118 | intent = PluginIntentResolver.resolveReceiver(intent);
119 | super.sendStickyBroadcast(intent);
120 | }
121 |
122 | @Override
123 | public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler,
124 | int initialCode, String initialData, Bundle initialExtras) {
125 | PaLog.d(intent);
126 | intent = PluginIntentResolver.resolveReceiver(intent);
127 | super.sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras);
128 | }
129 |
130 | @Override
131 | public void removeStickyBroadcast(Intent intent) {
132 | PaLog.d(intent);
133 | intent = PluginIntentResolver.resolveReceiver(intent);
134 | super.removeStickyBroadcast(intent);
135 | }
136 |
137 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
138 | @Override
139 | public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
140 | PaLog.d(intent);
141 | intent = PluginIntentResolver.resolveReceiver(intent);
142 | super.sendStickyBroadcastAsUser(intent, user);
143 | }
144 |
145 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
146 | @Override
147 | public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver,
148 | Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
149 | PaLog.d(intent);
150 | intent = PluginIntentResolver.resolveReceiver(intent);
151 | super.sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver, scheduler, initialCode, initialData,
152 | initialExtras);
153 | }
154 |
155 | @Override
156 | public ComponentName startService(Intent service) {
157 | PaLog.d(service);
158 | PluginIntentResolver.resolveService(service);
159 | return super.startService(service);
160 | }
161 |
162 | @Override
163 | public boolean stopService(Intent name) {
164 | PaLog.d(name);
165 | PluginIntentResolver.resolveService(name);
166 | return super.stopService(name);
167 | }
168 |
169 | @Override
170 | public boolean bindService(Intent service, ServiceConnection conn, int flags) {
171 | PaLog.d(service);
172 | PluginIntentResolver.resolveService(service);
173 | return super.bindService(service, conn, flags);
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/PluginMain/src/com/example/pluginmain/PluginDetailActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.pluginmain;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.Iterator;
6 | import java.util.Map.Entry;
7 |
8 | import android.app.Activity;
9 | import android.content.Intent;
10 | import android.net.Uri;
11 | import android.os.Build;
12 | import android.os.Bundle;
13 | import android.view.View;
14 | import android.view.View.OnClickListener;
15 | import android.view.ViewGroup;
16 | import android.widget.Button;
17 | import android.widget.LinearLayout;
18 | import android.widget.Space;
19 | import android.widget.TextView;
20 | import android.widget.Toast;
21 |
22 | import com.example.pluginsharelib.SharePOJO;
23 | import com.lyk.pluginmain.R;
24 | import com.plugin.content.PluginDescriptor;
25 | import com.plugin.content.PluginIntentFilter;
26 | import com.plugin.core.PluginLoader;
27 | import com.plugin.util.PaLog;
28 |
29 | public class PluginDetailActivity extends Activity {
30 |
31 | private ViewGroup mRoot;
32 |
33 | @Override
34 | protected void onCreate(Bundle savedInstanceState) {
35 | super.onCreate(savedInstanceState);
36 | setContentView(R.layout.detail_activity);
37 |
38 | setTitle("插件详情");
39 |
40 | mRoot = (ViewGroup) findViewById(R.id.root);
41 |
42 | String pluginId = getIntent().getStringExtra("plugin_id");
43 | if (pluginId == null) {
44 | Toast.makeText(this, "缺少plugin_id参数", Toast.LENGTH_LONG).show();
45 | return;
46 | }
47 |
48 | PluginDescriptor pluginDescriptor = PluginLoader.getPluginDescriptorByPluginId(pluginId);
49 |
50 | initViews(pluginDescriptor);
51 | }
52 |
53 | /**
54 | * INTIView
55 | * @param pluginDescriptor
56 | */
57 | private void initViews(PluginDescriptor pluginDescriptor) {
58 | if (pluginDescriptor != null) {
59 |
60 | TextView pluginIdView = (TextView) mRoot.findViewById(R.id.plugin_id);
61 | pluginIdView.setText("插件Id:" + pluginDescriptor.getPackageName());
62 |
63 | TextView pluginVerView = (TextView) mRoot.findViewById(R.id.plugin_version);
64 | pluginVerView.setText("插件Version:" + pluginDescriptor.getVersion());
65 |
66 | TextView pluginDescipt = (TextView) mRoot.findViewById(R.id.plugin_description);
67 | pluginDescipt.setText("插件Description:" + pluginDescriptor.getDescription());
68 |
69 | TextView pluginInstalled = (TextView) mRoot.findViewById(R.id.plugin_installedPath);
70 | pluginInstalled.setText("插件安装路径:" + pluginDescriptor.getInstalledPath());
71 |
72 | TextView pluginStandalone = (TextView) mRoot.findViewById(R.id.isstandalone);
73 | pluginStandalone.setText("独立插件:" + (pluginDescriptor.isStandalone()?"是":"否"));
74 |
75 |
76 | LinearLayout pluginView = (LinearLayout) mRoot.findViewById(R.id.plugin_items);
77 | Iterator> fragment = pluginDescriptor.getFragments().entrySet().iterator();
78 | while (fragment.hasNext()) {
79 | final Entry entry = fragment.next();
80 |
81 | TextView tv = new TextView(this);
82 | tv.append("插件类型:Fragment");
83 | pluginView.addView(tv);
84 |
85 | tv = new TextView(this);
86 | tv.setText("插件ClassId:" + entry.getKey());
87 | pluginView.addView(tv);
88 |
89 | tv = new TextView(this);
90 | tv.append("插件ClassName : " + entry.getValue());
91 | pluginView.addView(tv);
92 |
93 | Button btn = new Button(this);
94 | btn.setText("点击打开");
95 | btn.setOnClickListener(new OnClickListener() {
96 |
97 | @Override
98 | public void onClick(View v) {
99 | // 插件中的Fragment分两类
100 | // 第一类是在插件提供的Activity中展示,就是一个普通的Fragment
101 | // 第二类是在宿主提供的Activity中展示,分为普通Fragment和特别处理过的fragment
102 | PaLog.d("id_main", entry.getKey());
103 |
104 | FragmentHelper.startFragmentWithBuildInActivity(PluginDetailActivity.this, entry.getKey());
105 | }
106 | });
107 | pluginView.addView(btn);
108 | Space space = new Space(this);
109 | space.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 25));
110 | pluginView.addView(space);
111 | }
112 |
113 | addButton(pluginView, pluginDescriptor.getActivitys(), "Activity");
114 |
115 | addButton(pluginView, pluginDescriptor.getServices(), "Service");
116 |
117 | addButton(pluginView, pluginDescriptor.getReceivers(), "Receiver");
118 | }
119 | }
120 |
121 | private void addButton(LinearLayout pluginView, HashMap> map, final String type) {
122 | Iterator components = map.keySet().iterator();
123 | while (components.hasNext()) {
124 |
125 | final String entry = components.next();
126 | // Toast.makeText(PluginDetailActivity.this, entry, 1).show();
127 | TextView tv = new TextView(this);
128 | // 这个判断仅仅是为了方便debug,在实际开发中,类型一定是已知的
129 | tv.append("插件类型:" + type);
130 | pluginView.addView(tv);
131 |
132 | tv = new TextView(this);
133 | tv.append("插件ClassName : " + entry);
134 | pluginView.addView(tv);
135 |
136 | Button btn = new Button(this);
137 | btn.setText("点击打开");
138 | btn.setOnClickListener(new OnClickListener() {
139 |
140 | @Override
141 | public void onClick(View v) {
142 |
143 | // 这个判断仅仅是为了方便debug,在实际开发中,类型一定是已知的
144 | if (type.equals("Service")) {
145 |
146 | Intent intent = new Intent();
147 | intent.setClassName(PluginDetailActivity.this, entry);
148 | intent.putExtra("testParam", "testParam");
149 | startService(intent);
150 | // stopService(intent);
151 |
152 | } else if (type.equals("Receiver")) {// 这个判断仅仅是为了方便debug,在实际开发中,类型一定是已知的
153 |
154 | Intent intent = new Intent();
155 | intent.setClassName(PluginDetailActivity.this, entry);
156 | intent.putExtra("testParam", "testParam");
157 | sendBroadcast(intent);
158 | } else if (type.equals("Activity")) {
159 |
160 | // 测试通过ClassName匹配
161 | Intent intent = new Intent();
162 | intent.setClassName(PluginDetailActivity.this, entry);
163 | intent.putExtra("testParam", "testParam");
164 | intent.putExtra("paramVO", new SharePOJO("测试VO"));
165 | startActivity(intent);
166 |
167 | // 测试通过action进行匹配的方式
168 | if (entry.equals("com.example.plugintest.activity.PluginNotInManifestActivity")) {
169 | intent = new Intent("test.xyz1");
170 | intent.putExtra("testParam", "testParam");
171 | startActivity(intent);
172 | }
173 |
174 | // 测试通过url进行匹配的方式
175 | if (entry.equals("com.example.plugintest.activity.PluginNotInManifestActivity")) {
176 | intent = new Intent(Intent.ACTION_VIEW);
177 | intent.addCategory(Intent.CATEGORY_DEFAULT);
178 | intent.addCategory(Intent.CATEGORY_BROWSABLE);
179 | intent.setData(Uri.parse("testscheme://testhost"));
180 | intent.putExtra("testParam", "testParam");
181 | startActivity(intent);
182 | }
183 |
184 | }
185 | }
186 | });
187 | pluginView.addView(btn);
188 |
189 | if (Build.VERSION.SDK_INT >=14) {
190 | Space space = new Space(this);
191 | space.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 25));
192 | pluginView.addView(space);
193 | }
194 |
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginIntentResolver.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core;
2 |
3 | import android.app.PendingIntent;
4 | import android.content.ComponentName;
5 | import android.content.Intent;
6 | import android.content.pm.ServiceInfo;
7 | import android.os.Build;
8 |
9 | import com.plugin.content.PluginActivityInfo;
10 | import com.plugin.content.PluginDescriptor;
11 | import com.plugin.content.PluginReceiverIntent;
12 | import com.plugin.core.proxy.PluginProxyService;
13 | import com.plugin.core.stub.PluginStubReceiver;
14 | import com.plugin.util.ClassLoaderUtil;
15 | import com.plugin.util.PaLog;
16 | import com.plugin.util.RefInvoker;
17 |
18 | public class PluginIntentResolver {
19 |
20 | public static final String SERVICE_START_ACTION_IN_PLUGIN = "_SERVICE_START_ACTION_IN_PLUGIN_";
21 | public static final String SERVICE_STOP_ACTION_IN_PLUGIN = "_SERVICE_STOP_ACTION_IN_PLUGIN_";
22 | static final String ACTIVITY_ACTION_IN_PLUGIN = "_ACTIVITY_ACTION_IN_PLUGIN_";
23 | private static String RECEIVER_ACTION_IN_PLUGIN = "_RECEIVER_ACTION_IN_PLUGIN_";
24 |
25 | static String prefix = "plugin_receiver_prefix.";
26 |
27 | /* package */static void resolveService(Intent service) {
28 | String className = PluginLoader.matchPlugin(service);
29 | if (className != null) {
30 | service.setClass(PluginLoader.getApplicatoin(), PluginProxyService.class);
31 | service.setAction(className + SERVICE_START_ACTION_IN_PLUGIN + (service.getAction() == null ? "" : service.getAction()));
32 | }
33 | }
34 |
35 | /* package */static Intent resolveReceiver(final Intent intent) {
36 | // 如果在插件中发现了匹配intent的receiver项目,替换掉ClassLoader
37 | // 不需要在这里记录目标className,className将在Intent中传递
38 | String className = PluginLoader.matchPlugin(intent);
39 | if (className != null) {
40 | ClassLoaderUtil.hackClassLoaderIfNeeded();
41 | intent.setComponent(new ComponentName(PluginLoader.getApplicatoin().getPackageName(),
42 | PluginStubReceiver.class.getName()));
43 | //hackReceiverForClassLoader检测到这个标记后会进行替换
44 | intent.setAction(className + RECEIVER_ACTION_IN_PLUGIN + (intent.getAction() == null ? "" : intent.getAction()));
45 | }
46 | return intent;
47 | }
48 |
49 | /* package */static Class hackReceiverForClassLoader(Object msgObj) {
50 | Intent intent = (Intent) RefInvoker.getFieldObject(msgObj, "android.app.ActivityThread$ReceiverData", "intent");
51 | if (intent.getComponent().getClassName().equals(PluginStubReceiver.class.getName())) {
52 | String action = intent.getAction();
53 | PaLog.d("action", action);
54 | if (action != null) {
55 | String[] targetClassName = action.split(RECEIVER_ACTION_IN_PLUGIN);
56 | @SuppressWarnings("rawtypes")
57 | Class clazz = PluginLoader.loadPluginClassByName(targetClassName[0]);
58 | if (clazz != null) {
59 | intent.setExtrasClassLoader(clazz.getClassLoader());
60 | //由于之前intent被修改过 这里再吧Intent还原到原始的intent
61 | if (targetClassName.length > 1) {
62 | intent.setAction(targetClassName[1]);
63 | } else {
64 | intent.setAction(null);
65 | }
66 | }
67 | // PluginClassLoader检测到这个特殊标记后会进行替换
68 | intent.setComponent(new ComponentName(intent.getComponent().getPackageName(),
69 | prefix + targetClassName[0]));
70 |
71 | if (Build.VERSION.SDK_INT >= 21) {
72 | if (intent.getExtras() != null) {
73 | PluginReceiverIntent newIntent = new PluginReceiverIntent(intent);
74 | RefInvoker.setFieldObject(msgObj, "android.app.ActivityThread$ReceiverData", "intent", newIntent);
75 | }
76 | }
77 |
78 | return clazz;
79 | }
80 | }
81 | return null;
82 | }
83 |
84 | /* package */static boolean resolveStopService(final Intent service) {
85 | String className = PluginLoader.matchPlugin(service);
86 | if (className != null) {
87 | service.setClass(PluginLoader.getApplicatoin(), PluginProxyService.class);
88 | service.setAction(className + SERVICE_STOP_ACTION_IN_PLUGIN + (service.getAction() == null ? "" : service.getAction()));
89 | return true;
90 | }
91 | return false;
92 | }
93 |
94 | /* package */static void resolveActivity(Intent intent) {
95 | // 如果在插件中发现Intent的匹配项,记下匹配的插件Activity的ClassName
96 | String className = PluginLoader.matchPlugin(intent);
97 | if (className != null) {
98 |
99 | PluginDescriptor pd = PluginLoader.getPluginDescriptorByClassName(className);
100 |
101 | PluginActivityInfo pluginActivityInfo = pd.getActivityInfos().get(className);
102 |
103 | String stubActivityName = PluginStubBinding.bindLaunchModeStubActivity(className, Integer.parseInt(pluginActivityInfo.getLaunchMode()));
104 |
105 | intent.setComponent(
106 | new ComponentName(PluginLoader.getApplicatoin().getPackageName(), stubActivityName));
107 | //PluginInstrumentationWrapper检测到这个标记后会进行替换
108 | intent.setAction(className + ACTIVITY_ACTION_IN_PLUGIN + (intent.getAction()==null?"":intent.getAction()));
109 | }
110 | }
111 |
112 | /* package */static void resolveActivity(Intent[] intent) {
113 | // not needed
114 | }
115 |
116 | /**
117 | * used before send notification
118 | * @param intent
119 | * @return
120 | */
121 | public static Intent resolveNotificationIntent(Intent intent) {
122 | int type = PluginLoader.getTargetType(intent);
123 |
124 | PaLog.d("notification type", type);
125 |
126 | if (type == PluginDescriptor.BROADCAST) {
127 |
128 | Intent newIntent = PluginIntentResolver.resolveReceiver(intent);
129 | return newIntent;
130 |
131 | } else if (type == PluginDescriptor.ACTIVITY) {
132 |
133 | PluginIntentResolver.resolveActivity(intent);
134 | return intent;
135 |
136 | } else if (type == PluginDescriptor.SERVICE) {
137 |
138 | PluginIntentResolver.resolveService(intent);
139 | return intent;
140 |
141 | }
142 | return intent;
143 | }
144 |
145 | @SuppressWarnings("ResourceType")
146 | public static PendingIntent resolvePendingIntent(PendingIntent origin) {
147 | if (origin != null) {
148 | Intent originIntent = (Intent)RefInvoker.invokeMethod(origin,
149 | PendingIntent.class.getName(), "getIntent",
150 | (Class[])null, (Object[])null);
151 | if (originIntent != null) {
152 | //如果目标是插件中的组件,需要额外提供2个参数, 默认为0、Update_Current。
153 | String className = PluginLoader.matchPlugin(originIntent);
154 | if (className != null) {
155 |
156 | int type = PluginLoader.getTargetType(originIntent);
157 |
158 | int requestCode = originIntent.getIntExtra("pending_requestCode", 0);
159 | int flags = originIntent.getIntExtra("pending_flag", PendingIntent.FLAG_UPDATE_CURRENT);
160 |
161 | if (type == PluginDescriptor.BROADCAST) {
162 |
163 | Intent newIntent = PluginIntentResolver.resolveReceiver(originIntent);
164 | return PendingIntent.getBroadcast(PluginLoader.getApplicatoin(), requestCode, newIntent, flags);
165 |
166 | } else if (type == PluginDescriptor.ACTIVITY) {
167 |
168 | PluginIntentResolver.resolveActivity(originIntent);
169 | return PendingIntent.getActivity(PluginLoader.getApplicatoin(), requestCode, originIntent, flags);
170 |
171 | } else if (type == PluginDescriptor.SERVICE) {
172 |
173 | PluginIntentResolver.resolveService(originIntent);
174 | return PendingIntent.getService(PluginLoader.getApplicatoin(), requestCode, originIntent, flags);
175 |
176 | }
177 | }
178 | }
179 | }
180 | return origin;
181 | }
182 |
183 |
184 | /* package */static String hackServiceName(Object msgObj) {
185 | ServiceInfo info = (ServiceInfo) RefInvoker.getFieldObject(msgObj, "android.app.ActivityThread$CreateServiceData", "info");
186 | //通过映射查找
187 | String targetClassName = PluginStubBinding.getBindedPluginServiceName(info.name);
188 |
189 | PaLog.d("hackServiceName", info.name, info.packageName, info.processName, "targetClassName", targetClassName);
190 |
191 | if (targetClassName != null) {
192 | info.name = prefix + targetClassName;
193 | }
194 | return targetClassName;
195 | }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/util/PackageVerifyer.java:
--------------------------------------------------------------------------------
1 | package com.plugin.util;
2 |
3 | import android.content.pm.Signature;
4 |
5 | import java.io.BufferedInputStream;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.lang.ref.WeakReference;
9 | import java.security.cert.Certificate;
10 | import java.security.cert.CertificateEncodingException;
11 | import java.util.Enumeration;
12 | import java.util.HashSet;
13 | import java.util.jar.JarEntry;
14 | import java.util.jar.JarFile;
15 |
16 | /**
17 | * Copy from Android SDK
18 | */
19 | public class PackageVerifyer {
20 | private static String TAG = "PackageVerifyer";
21 |
22 | private static final Object mSync = new Object();
23 | private static WeakReference mReadBuffer;
24 |
25 | /**
26 | * @param sourcePath
27 | * @param simpleMode
28 | * @return
29 | */
30 | public static Signature[] collectCertificates(String sourcePath, boolean simpleMode) {
31 |
32 | //其实可以直接通过getPackageManager().getPackageArchiveInfo()获取插件的签名信息
33 | //不用这么麻烦
34 |
35 | Signature mSignatures[] = null;
36 | WeakReference readBufferRef;
37 | byte[] readBuffer = null;
38 | synchronized (mSync) {
39 | readBufferRef = mReadBuffer;
40 | if (readBufferRef != null) {
41 | mReadBuffer = null;
42 | readBuffer = readBufferRef.get();
43 | }
44 | if (readBuffer == null) {
45 | readBuffer = new byte[8192];
46 | readBufferRef = new WeakReference(readBuffer);
47 | }
48 | }
49 |
50 | try {
51 | JarFile jarFile = new JarFile(sourcePath);
52 |
53 | Certificate[] certs = null;
54 |
55 | if (simpleMode) {
56 | // if SIMPLE MODE,, then we
57 | // can trust it... we'll just use the AndroidManifest.xml
58 | // to retrieve its signatures, not validating all of the
59 | // files.
60 | JarEntry jarEntry = jarFile.getJarEntry("AndroidManifest.xml");
61 | certs = loadCertificates(jarFile, jarEntry, readBuffer);
62 | if (certs == null) {
63 | PaLog.e(TAG, "Package "
64 | + " has no certificates at entry "
65 | + jarEntry.getName() + "; ignoring!");
66 | jarFile.close();
67 |
68 | PaLog.e("INSTALL_PARSE_FAILED_NO_CERTIFICATES");
69 | return null;
70 | }
71 | if (true) {
72 | PaLog.d(TAG, "File " + sourcePath + ": entry=" + jarEntry
73 | + " certs=" + (certs != null ? certs.length : 0));
74 | if (certs != null) {
75 | final int N = certs.length;
76 | for (int i=0; i 0) {
138 | final int N = certs.length;
139 | mSignatures = new Signature[certs.length];
140 | for (int i=0; i set1 = new HashSet();
197 | for (Signature sig : s1) {
198 | set1.add(sig);
199 | }
200 | HashSet set2 = new HashSet();
201 | for (Signature sig : s2) {
202 | set2.add(sig);
203 | }
204 | // Make sure s2 contains all signatures in s1.
205 | if (set1.equals(set2)) {
206 | return true;
207 | }
208 | return false;
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/manager/PluginManagerImpl.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core.manager;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 | import android.content.SharedPreferences;
6 | import android.os.Build;
7 | import android.text.TextUtils;
8 | import android.util.Base64;
9 |
10 | import com.plugin.config.PluginConfig;
11 | import com.plugin.content.PluginDescriptor;
12 | import com.plugin.core.PluginLoader;
13 | import com.plugin.database.PluginDatabaseManager;
14 | import com.plugin.util.FileUtil;
15 | import com.plugin.util.JsonUtil;
16 | import com.plugin.util.PaLog;
17 | import com.plugin.util.RefInvoker;
18 |
19 | import java.io.ByteArrayInputStream;
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.File;
22 | import java.io.IOException;
23 | import java.io.ObjectInputStream;
24 | import java.io.ObjectOutputStream;
25 | import java.io.Serializable;
26 | import java.util.Collection;
27 | import java.util.Hashtable;
28 | import java.util.Iterator;
29 |
30 | public class PluginManagerImpl implements PluginManager {
31 |
32 |
33 | private Context mContext;
34 |
35 | private PluginDatabaseManager mDBManager;
36 |
37 | /** instance */
38 | private static PluginManagerImpl sInstance;
39 |
40 |
41 | public PluginManagerImpl() {
42 |
43 | }
44 |
45 | public PluginManagerImpl(Context aContext) {
46 | mContext = aContext;
47 | mDBManager = PluginDatabaseManager.getInstance(mContext);
48 | }
49 |
50 | /**
51 | * getInstance
52 | *
53 | * @param aContext context
54 | * @return PluginManagerImpl
55 | */
56 | public static synchronized PluginManagerImpl getInstance(Context aContext) {
57 | if (sInstance == null) {
58 | sInstance = new PluginManagerImpl(aContext);
59 | }
60 | return sInstance;
61 | }
62 |
63 | public Context getContext() {
64 | return mContext;
65 | }
66 |
67 | public void setContext(Context mContext) {
68 | this.mContext = mContext;
69 | }
70 |
71 | private final Hashtable sInstalledPlugins = new Hashtable();
72 |
73 |
74 | @Override
75 | public String genInstallPath(String pluginId, String pluginVersoin) {
76 | return PluginLoader.getApplicatoin().getDir(PluginConfig.DF_PLUGIN_INSTALLED_DIR, Context.MODE_PRIVATE).getAbsolutePath() + "/" + pluginId + "/"
77 | + pluginVersoin + "/" + pluginId + ".apk";
78 | }
79 |
80 | @SuppressWarnings("unchecked")
81 | @Override
82 | public synchronized void loadInstalledPlugins() {
83 | if (sInstalledPlugins.size() == 0) {
84 | Hashtable installedPlugins = null;
85 | if (mDBManager == null) {
86 | mDBManager = PluginDatabaseManager.getInstance((Context) RefInvoker.getFieldObject(PluginLoader.getApplicatoin(), ContextWrapper.class.getName(),
87 | "mBase"));
88 | }
89 | installedPlugins = mDBManager.queryAll();
90 | if (installedPlugins != null ) {
91 | sInstalledPlugins.putAll(installedPlugins);
92 | } else {
93 | /*Object object = getPluginsBySp();
94 | if (object != null) {
95 | installedPlugins = (Hashtable) object;
96 | Iterator itr = installedPlugins.values().iterator();
97 | *//*while (itr.hasNext()) {
98 | PluginDescriptor descriptor = itr.next();
99 | //mDBManager.insert(descriptor);
100 | }*//*
101 | sInstalledPlugins.putAll(installedPlugins);
102 | }*/
103 | }
104 | }
105 | }
106 |
107 | @Override
108 | public boolean addOrReplace(PluginDescriptor pluginDescriptor) {
109 | sInstalledPlugins.put(pluginDescriptor.getPackageName(), pluginDescriptor);
110 | long stats = mDBManager.insert(pluginDescriptor);
111 | return saveInstalledPlugins() && stats != -1;
112 | }
113 |
114 | @Override
115 | public synchronized boolean removeAll() {
116 | sInstalledPlugins.clear();
117 | return saveInstalledPlugins() && !(mDBManager.deleteAll() == 0);
118 | }
119 |
120 | @Override
121 | public synchronized boolean remove(String pluginId) {
122 | PluginDescriptor old = sInstalledPlugins.remove(pluginId);
123 | if (old != null) {
124 | int stats = mDBManager.delete(new String[]{pluginId});
125 | boolean isSuccess = saveInstalledPlugins();
126 | boolean deleteSuccess = FileUtil.deleteAll(new File(old.getInstalledPath()).getParentFile());
127 | PaLog.d("delete old", isSuccess, deleteSuccess, old.getInstalledPath(), old.getPackageName());
128 | return isSuccess && (stats == 1);
129 | }
130 | return false;
131 | }
132 |
133 | @Override
134 | public Collection getPlugins() {
135 | if (sInstalledPlugins.size() == 0) {
136 | loadInstalledPlugins();
137 | }
138 | return sInstalledPlugins.values();
139 | }
140 |
141 | @Override
142 | public synchronized void enablePlugin(String pluginId, boolean enable) {
143 | PluginDescriptor pluginDescriptor = sInstalledPlugins.get(pluginId);
144 | if (pluginDescriptor != null && !pluginDescriptor.isEnabled()) {
145 | pluginDescriptor.setEnabled(enable);
146 | saveInstalledPlugins();
147 | }
148 | }
149 |
150 | /**
151 | * for Fragment
152 | *
153 | * @param clazzId
154 | * @return
155 | */
156 | @Override
157 | public PluginDescriptor getPluginDescriptorByFragmenetId(String clazzId) {
158 | Iterator itr = sInstalledPlugins.values().iterator();
159 | while (itr.hasNext()) {
160 | PluginDescriptor descriptor = itr.next();
161 | if (descriptor.containsFragment(clazzId)) {
162 | return descriptor;
163 | }
164 | }
165 | return null;
166 | }
167 |
168 | @Override
169 | public PluginDescriptor getPluginDescriptorByPluginId(String pluginId) {
170 | PluginDescriptor pluginDescriptor = sInstalledPlugins.get(pluginId);
171 | if (pluginDescriptor != null && pluginDescriptor.isEnabled()) {
172 | return pluginDescriptor;
173 | }
174 | return null;
175 | }
176 |
177 | @Override
178 | public PluginDescriptor getPluginDescriptorByClassName(String clazzName) {
179 | Iterator itr = sInstalledPlugins.values().iterator();
180 | while (itr.hasNext()) {
181 | PluginDescriptor descriptor = itr.next();
182 | if (descriptor.containsName(clazzName)) {
183 | return descriptor;
184 | }
185 | }
186 | return null;
187 | }
188 |
189 | @Override
190 | public PluginDescriptor getPluginsDescriptorByPath(String aSrcDirPath) {
191 | Iterator itr = sInstalledPlugins.values().iterator();
192 | while (itr.hasNext()) {
193 | PluginDescriptor descriptor = itr.next();
194 | if (descriptor.containsPath(aSrcDirPath)) {
195 | return descriptor;
196 | }
197 | }
198 | return null;
199 | }
200 |
201 | private static SharedPreferences getSharedPreference() {
202 | SharedPreferences sp = PluginLoader.getApplicatoin().getSharedPreferences(PluginConfig.SP_PLUGIN_INSTALLED_KEY,
203 | Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE : Context.MODE_PRIVATE | 0x0004);
204 | return sp;
205 | }
206 |
207 | private synchronized boolean saveInstalledPlugins() {
208 |
209 | String jsonStr = JsonUtil.toJSONString(sInstalledPlugins);
210 | ObjectOutputStream objectOutputStream = null;
211 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
212 | try {
213 | objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
214 | objectOutputStream.writeObject(sInstalledPlugins);
215 | objectOutputStream.flush();
216 |
217 | byte[] data = byteArrayOutputStream.toByteArray();
218 | String list = Base64.encodeToString(data, Base64.DEFAULT);
219 |
220 | PaLog.e(">>>>>>>>>>>>>", jsonStr);
221 |
222 | getSharedPreference().edit().putString(PluginConfig.SP_PLUGIN_KEY, list).commit();
223 | return true;
224 | } catch (Exception e) {
225 | e.printStackTrace();
226 | } finally {
227 | if (objectOutputStream != null) {
228 | try {
229 | objectOutputStream.close();
230 | } catch (IOException e) {
231 | e.printStackTrace();
232 | }
233 | }
234 | if (byteArrayOutputStream != null) {
235 | try {
236 | byteArrayOutputStream.close();
237 | } catch (IOException e) {
238 | e.printStackTrace();
239 | }
240 | }
241 | }
242 | return false;
243 | }
244 |
245 | /** 从sp读取插件信息
246 | * @return
247 | */
248 | private synchronized Object getPluginsBySp() {
249 |
250 | String list = getSharedPreference().getString(PluginConfig.SP_PLUGIN_KEY, "");
251 | Serializable object = null;
252 | if (!TextUtils.isEmpty(list)) {
253 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
254 | Base64.decode(list, Base64.DEFAULT));
255 | ObjectInputStream objectInputStream = null;
256 | try {
257 | objectInputStream = new ObjectInputStream(byteArrayInputStream);
258 | object = (Serializable) objectInputStream.readObject();
259 | } catch (Exception e) {
260 | e.printStackTrace();
261 | } finally {
262 | if (objectInputStream != null) {
263 | try {
264 | objectInputStream.close();
265 | } catch (IOException e) {
266 | e.printStackTrace();
267 | }
268 | }
269 | if (byteArrayInputStream != null) {
270 | try {
271 | byteArrayInputStream.close();
272 | } catch (IOException e) {
273 | e.printStackTrace();
274 | }
275 | }
276 | }
277 | }
278 | return object;
279 |
280 | }
281 |
282 |
283 |
284 | }
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/util/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.plugin.util;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.Build;
5 | import android.widget.Toast;
6 |
7 | import com.plugin.core.PluginLoader;
8 |
9 | import java.io.BufferedInputStream;
10 | import java.io.BufferedOutputStream;
11 | import java.io.BufferedReader;
12 | import java.io.File;
13 | import java.io.FileInputStream;
14 | import java.io.FileNotFoundException;
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.io.InputStreamReader;
19 | import java.util.Enumeration;
20 | import java.util.HashSet;
21 | import java.util.Set;
22 | import java.util.jar.JarEntry;
23 | import java.util.jar.JarFile;
24 | import java.util.zip.ZipEntry;
25 | import java.util.zip.ZipFile;
26 |
27 | public class FileUtil {
28 |
29 | public static boolean copyFile(String source, String dest) {
30 | try {
31 | return copyFile(new FileInputStream(new File(source)), dest);
32 | } catch (FileNotFoundException e) {
33 | e.printStackTrace();
34 | }
35 | return false;
36 | }
37 |
38 | /**
39 | * copy File
40 | * @param inputStream
41 | * @param dest
42 | * @return
43 | */
44 | public static boolean copyFile(final InputStream inputStream, String dest) {
45 | PaLog.d("copyFile to " + dest);
46 | FileOutputStream oputStream = null;
47 | try {
48 | File destFile = new File(dest);
49 | destFile.getParentFile().mkdirs();
50 | destFile.createNewFile();
51 |
52 | oputStream = new FileOutputStream(destFile);
53 | byte[] bb = new byte[48 * 1024];
54 | int len = 0;
55 | while ((len = inputStream.read(bb)) != -1) {
56 | oputStream.write(bb, 0, len);
57 | }
58 | oputStream.flush();
59 | return true;
60 | } catch (Exception e) {
61 | e.printStackTrace();
62 | } finally {
63 | if (oputStream != null) {
64 | try {
65 | oputStream.close();
66 | } catch (IOException e) {
67 | e.printStackTrace();
68 | }
69 | }
70 | if (inputStream != null) {
71 | try {
72 | inputStream.close();
73 | } catch (IOException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 | }
78 | return false;
79 | }
80 |
81 | public static boolean copySo(File sourceDir, String so, String dest) {
82 |
83 | try {
84 |
85 | boolean isSuccess = false;
86 |
87 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
88 | String[] abis = Build.SUPPORTED_ABIS;
89 | if (abis != null) {
90 | for (String abi: abis) {
91 | PaLog.d(abi);
92 | String name = "lib" + File.separator + abi + File.separator + so;
93 | File sourceFile = new File(sourceDir, name);
94 | if (sourceFile.exists()) {
95 | isSuccess = copyFile(sourceFile.getAbsolutePath(), dest + File.separator + "lib" + File.separator + so);
96 | // api21 64位系统的目录可能有些不同
97 | //copyFile(sourceFile.getAbsolutePath(), dest + File.separator + name);
98 | break;
99 | }
100 | }
101 | }
102 | } else {
103 | PaLog.d(Build.CPU_ABI, Build.CPU_ABI2);
104 |
105 | String name = "lib" + File.separator + Build.CPU_ABI + File.separator + so;
106 | File sourceFile = new File(sourceDir, name);
107 |
108 | if (!sourceFile.exists() && Build.CPU_ABI2 != null) {
109 | name = "lib" + File.separator + Build.CPU_ABI2 + File.separator + so;
110 | sourceFile = new File(sourceDir, name);
111 |
112 | if (!sourceFile.exists()) {
113 | name = "lib" + File.separator + "armeabi" + File.separator + so;
114 | sourceFile = new File(sourceDir, name);
115 | }
116 | }
117 | if (sourceFile.exists()) {
118 | isSuccess = copyFile(sourceFile.getAbsolutePath(), dest + File.separator + "lib" + File.separator + so);
119 | }
120 | }
121 |
122 | if (!isSuccess) {
123 | Toast.makeText(PluginLoader.getApplicatoin(), "安装 " + so + " 失败: NO_MATCHING_ABIS", Toast.LENGTH_LONG).show();
124 | }
125 | } catch(Exception e) {
126 | e.printStackTrace();
127 | }
128 |
129 | return true;
130 | }
131 |
132 | public static Set unZipSo(String apkFile, File tempDir) {
133 |
134 | HashSet result = null;
135 |
136 | if (!tempDir.exists()) {
137 | tempDir.mkdirs();
138 | }
139 |
140 | PaLog.d("开始so文件", tempDir.getAbsolutePath());
141 |
142 | ZipFile zfile = null;
143 | boolean isSuccess = false;
144 | BufferedOutputStream fos = null;
145 | BufferedInputStream bis = null;
146 | try {
147 | zfile = new ZipFile(apkFile);
148 | ZipEntry ze = null;
149 | Enumeration zList = zfile.entries();
150 | while (zList.hasMoreElements()) {
151 | ze = (ZipEntry) zList.nextElement();
152 | String relativePath = ze.getName();
153 |
154 | if (!relativePath.startsWith("lib" + File.separator)) {
155 | PaLog.d("不是lib目录,跳过", relativePath);
156 | continue;
157 | }
158 |
159 | if (ze.isDirectory()) {
160 | File folder = new File(tempDir, relativePath);
161 | PaLog.d("正在创建目录", folder.getAbsolutePath());
162 | if (!folder.exists()) {
163 | folder.mkdirs();
164 | }
165 |
166 | } else {
167 |
168 | if (result == null) {
169 | result = new HashSet(4);
170 | }
171 |
172 | File targetFile = new File(tempDir, relativePath);
173 | PaLog.d("正在解压so文件", targetFile.getAbsolutePath());
174 | if (!targetFile.getParentFile().exists()) {
175 | targetFile.getParentFile().mkdirs();
176 | }
177 | targetFile.createNewFile();
178 |
179 | fos = new BufferedOutputStream(new FileOutputStream(targetFile));
180 | bis = new BufferedInputStream(zfile.getInputStream(ze));
181 | byte[] buffer = new byte[2048];
182 | int count = -1;
183 | while ((count = bis.read(buffer)) != -1) {
184 | fos.write(buffer, 0, count);
185 | fos.flush();
186 | }
187 | fos.close();
188 | fos = null;
189 | bis.close();
190 | bis = null;
191 |
192 | result.add(relativePath.substring(relativePath.lastIndexOf(File.separator) +1));
193 | }
194 | }
195 | isSuccess = true;
196 | } catch (IOException e) {
197 | e.printStackTrace();
198 | } finally {
199 | if (fos != null) {
200 | try {
201 | fos.close();
202 | } catch (IOException e) {
203 | e.printStackTrace();
204 | }
205 | }
206 | if (bis != null) {
207 | try {
208 | bis.close();
209 | } catch (IOException e) {
210 | e.printStackTrace();
211 | }
212 | }
213 | if (zfile != null) {
214 | try {
215 | zfile.close();
216 | } catch (IOException e) {
217 | e.printStackTrace();
218 | }
219 | }
220 | }
221 |
222 | PaLog.d("解压so文件结束", isSuccess);
223 | return result;
224 | }
225 |
226 | public static void readFileFromJar(String jarFilePath, String metaInfo) {
227 | PaLog.d("readFileFromJar:", jarFilePath, metaInfo);
228 | JarFile jarFile = null;
229 | try {
230 | jarFile = new JarFile(jarFilePath);
231 | JarEntry entry = jarFile.getJarEntry(metaInfo);
232 | if (entry != null) {
233 | InputStream input = jarFile.getInputStream(entry);
234 |
235 | return;
236 | }
237 |
238 | } catch (IOException e) {
239 | e.printStackTrace();
240 | } finally {
241 | if (jarFile != null) {
242 | try {
243 | jarFile.close();
244 | } catch (IOException e) {
245 | e.printStackTrace();
246 | }
247 | }
248 | }
249 | return;
250 |
251 | }
252 |
253 | /**
254 | * 递归删除文件及文件夹
255 | * @param file
256 | */
257 | public static boolean deleteAll(File file) {
258 | if (file.isDirectory()) {
259 | File[] childFiles = file.listFiles();
260 | if (childFiles != null && childFiles.length > 0) {
261 | for (int i = 0; i < childFiles.length; i++) {
262 | deleteAll(childFiles[i]);
263 | }
264 | }
265 | }
266 | return file.delete();
267 | }
268 |
269 | /**
270 | * streamToString.
271 | * @param input
272 | * @return
273 | * @throws IOException
274 | */
275 | public static String streamToString(InputStream input) throws IOException {
276 |
277 | InputStreamReader isr = new InputStreamReader(input);
278 | BufferedReader reader = new BufferedReader(isr);
279 |
280 | String line;
281 | StringBuffer sb = new StringBuffer();
282 | while ((line = reader.readLine()) != null) {
283 | sb.append(line);
284 | }
285 | reader.close();
286 | isr.close();
287 | return sb.toString();
288 | }
289 |
290 | /**
291 | * @param bmp
292 | * @param dir
293 | * @param fileName
294 | */
295 | public String saveImageToSdcard(Bitmap bmp, String dir, String fileName) {
296 | File file = new File(dir);
297 | if (!file.exists())
298 | file.mkdir();
299 |
300 | file = new File((dir + File.separator + fileName).trim());
301 | String mfileName = file.getName();
302 | String mName = mfileName.substring(0, fileName.lastIndexOf("."));
303 | String sName = mfileName.substring(fileName.lastIndexOf("."));
304 |
305 | // /sdcard/myFolder/temp_cropped.jpg
306 | String newFilePath = dir + "/" + mName + "_cropped" + sName;
307 | file = new File(newFilePath);
308 | try {
309 | file.createNewFile();
310 | FileOutputStream fos = new FileOutputStream(file);
311 | bmp.compress(Bitmap.CompressFormat.JPEG, 50, fos);
312 | fos.flush();
313 | fos.close();
314 | } catch (IOException e) {
315 | e.printStackTrace();
316 | return null;
317 | }
318 | return newFilePath;
319 | }
320 |
321 | }
322 |
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginAppTrace.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.ContextWrapper;
6 | import android.os.Handler;
7 | import android.os.IBinder;
8 | import android.os.Message;
9 |
10 | import com.plugin.content.PluginDescriptor;
11 | import com.plugin.util.ClassLoaderUtil;
12 | import com.plugin.util.PaLog;
13 | import com.plugin.util.RefInvoker;
14 |
15 | import java.util.Iterator;
16 | import java.util.Map;
17 |
18 | /**
19 | * 插件Receiver免注册的主要实现原理
20 | *
21 | * @author cailiming
22 | *
23 | */
24 | public class PluginAppTrace implements Handler.Callback {
25 |
26 | private final Handler mHandler;
27 |
28 | protected PluginAppTrace(Handler handler) {
29 | mHandler = handler;
30 | }
31 |
32 | @Override
33 | public boolean handleMessage(Message msg) {
34 |
35 | PaLog.d(">>> handling: ", CodeConst.codeToString(msg.what));
36 |
37 | String serviceName = null;
38 | Context baseContext = null;
39 |
40 | if (msg.what == CodeConst.RECEIVER) {
41 |
42 | Class clazz = PluginIntentResolver.hackReceiverForClassLoader(msg.obj);
43 |
44 | baseContext = replaceReceiverContext(clazz);
45 |
46 | } else if (msg.what == CodeConst.CREATE_SERVICE) {
47 |
48 | serviceName = PluginIntentResolver.hackServiceName(msg.obj);
49 |
50 | if (serviceName != null) {
51 | ClassLoaderUtil.hackClassLoaderIfNeeded();
52 | }
53 | } else if (msg.what == CodeConst.STOP_SERVICE) {
54 | //销毁service时回收映射关系
55 | Object activityThread = PluginInjector.getActivityThread();
56 | if (activityThread != null) {
57 | Map services = (Map)RefInvoker.getFieldObject(activityThread, "android.app.ActivityThread", "mServices");
58 | if (services != null) {
59 | Service service = services.get(msg.obj);
60 | if (service != null) {
61 | String pluginServiceClassName = service.getClass().getName();
62 | PaLog.d("STOP_SERVICE", pluginServiceClassName);
63 | PluginStubBinding.unBindStubService(pluginServiceClassName);
64 | }
65 | }
66 | }
67 | }
68 |
69 | try {
70 | mHandler.handleMessage(msg);
71 | PaLog.d(">>> done: " + CodeConst.codeToString(msg.what));
72 | } finally {
73 | if (msg.what == CodeConst.RECEIVER && baseContext != null) {
74 |
75 | RefInvoker.setFieldObject(baseContext, "android.app.ContextImpl", "mReceiverRestrictedContext", null);
76 |
77 | } else if (msg.what == CodeConst.CREATE_SERVICE) {
78 | //拿到创建好的service,重新 设置mBase和mApplicaiton
79 | //由于这步操作是再service得oncreate之后执行,所以再插件service得oncreate中不应尝试通过此service的context执行操作
80 | replaceServiceContext(serviceName);
81 | }
82 | }
83 |
84 | return true;
85 | }
86 |
87 | private static Context replaceReceiverContext(Class clazz) {
88 | if (clazz == null) {
89 | return null;
90 | }
91 | Context baseContext = PluginLoader.getApplicatoin().getBaseContext();
92 | if (baseContext.getClass().getName().equals("android.app.ContextImpl")) {
93 | ContextWrapper receiverRestrictedContext = (ContextWrapper) RefInvoker.invokeMethod(baseContext, "android.app.ContextImpl", "getReceiverRestrictedContext", (Class[]) null, (Object[]) null);
94 | RefInvoker.setFieldObject(receiverRestrictedContext, ContextWrapper.class.getName(), "mBase", PluginLoader.getDefaultPluginContext(clazz));
95 | } else {
96 | baseContext = null;
97 | }
98 | return baseContext;
99 | }
100 |
101 | private static void replaceServiceContext(String serviceName) {
102 | Object activityThread = PluginInjector.getActivityThread();
103 | if (activityThread != null) {
104 | Map services = (Map)RefInvoker.getFieldObject(activityThread, "android.app.ActivityThread", "mServices");
105 | if (services != null) {
106 | Iterator itr = services.values().iterator();
107 | while(itr.hasNext()) {
108 | Service service = itr.next();
109 | if (service != null && service.getClass().getName().equals(serviceName) ) {
110 |
111 | PluginDescriptor pd = PluginLoader.getPluginDescriptorByClassName(serviceName);
112 |
113 | RefInvoker.setFieldObject(service, ContextWrapper.class.getName(), "mBase", PluginLoader.getNewPluginComponentContext(pd.getPluginContext(), service.getBaseContext()));
114 |
115 | if (pd.getPluginApplication() != null) {
116 | RefInvoker.setFieldObject(service, Service.class.getName(), "mApplication", pd.getPluginApplication());
117 | }
118 | }
119 |
120 | }
121 | }
122 |
123 | }
124 | }
125 |
126 | private static class CodeConst {
127 | public static final int LAUNCH_ACTIVITY = 100;
128 | public static final int PAUSE_ACTIVITY = 101;
129 | public static final int PAUSE_ACTIVITY_FINISHING = 102;
130 | public static final int STOP_ACTIVITY_SHOW = 103;
131 | public static final int STOP_ACTIVITY_HIDE = 104;
132 | public static final int SHOW_WINDOW = 105;
133 | public static final int HIDE_WINDOW = 106;
134 | public static final int RESUME_ACTIVITY = 107;
135 | public static final int SEND_RESULT = 108;
136 | public static final int DESTROY_ACTIVITY = 109;
137 | public static final int BIND_APPLICATION = 110;
138 | public static final int EXIT_APPLICATION = 111;
139 | public static final int NEW_INTENT = 112;
140 | public static final int RECEIVER = 113;
141 | public static final int CREATE_SERVICE = 114;
142 | public static final int SERVICE_ARGS = 115;
143 | public static final int STOP_SERVICE = 116;
144 | public static final int REQUEST_THUMBNAIL = 117;
145 | public static final int CONFIGURATION_CHANGED = 118;
146 | public static final int CLEAN_UP_CONTEXT = 119;
147 | public static final int GC_WHEN_IDLE = 120;
148 | public static final int BIND_SERVICE = 121;
149 | public static final int UNBIND_SERVICE = 122;
150 | public static final int DUMP_SERVICE = 123;
151 | public static final int LOW_MEMORY = 124;
152 | public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
153 | public static final int RELAUNCH_ACTIVITY = 126;
154 | public static final int PROFILER_CONTROL = 127;
155 | public static final int CREATE_BACKUP_AGENT = 128;
156 | public static final int DESTROY_BACKUP_AGENT = 129;
157 | public static final int SUICIDE = 130;
158 | public static final int REMOVE_PROVIDER = 131;
159 | public static final int ENABLE_JIT = 132;
160 | public static final int DISPATCH_PACKAGE_BROADCAST = 133;
161 | public static final int SCHEDULE_CRASH = 134;
162 | public static final int DUMP_HEAP = 135;
163 | public static final int DUMP_ACTIVITY = 136;
164 | public static final int SLEEPING = 137;
165 | public static final int SET_CORE_SETTINGS = 138;
166 | public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
167 | public static final int TRIM_MEMORY = 140;
168 |
169 | public static String codeToString(int code) {
170 | switch (code) {
171 | case LAUNCH_ACTIVITY:
172 | return "LAUNCH_ACTIVITY";
173 | case PAUSE_ACTIVITY:
174 | return "PAUSE_ACTIVITY";
175 | case PAUSE_ACTIVITY_FINISHING:
176 | return "PAUSE_ACTIVITY_FINISHING";
177 | case STOP_ACTIVITY_SHOW:
178 | return "STOP_ACTIVITY_SHOW";
179 | case STOP_ACTIVITY_HIDE:
180 | return "STOP_ACTIVITY_HIDE";
181 | case SHOW_WINDOW:
182 | return "SHOW_WINDOW";
183 | case HIDE_WINDOW:
184 | return "HIDE_WINDOW";
185 | case RESUME_ACTIVITY:
186 | return "RESUME_ACTIVITY";
187 | case SEND_RESULT:
188 | return "SEND_RESULT";
189 | case DESTROY_ACTIVITY:
190 | return "DESTROY_ACTIVITY";
191 | case BIND_APPLICATION:
192 | return "BIND_APPLICATION";
193 | case EXIT_APPLICATION:
194 | return "EXIT_APPLICATION";
195 | case NEW_INTENT:
196 | return "NEW_INTENT";
197 | case RECEIVER:
198 | return "RECEIVER";
199 | case CREATE_SERVICE:
200 | return "CREATE_SERVICE";
201 | case SERVICE_ARGS:
202 | return "SERVICE_ARGS";
203 | case STOP_SERVICE:
204 | return "STOP_SERVICE";
205 | case REQUEST_THUMBNAIL:
206 | return "REQUEST_THUMBNAIL";
207 | case CONFIGURATION_CHANGED:
208 | return "CONFIGURATION_CHANGED";
209 | case CLEAN_UP_CONTEXT:
210 | return "CLEAN_UP_CONTEXT";
211 | case GC_WHEN_IDLE:
212 | return "GC_WHEN_IDLE";
213 | case BIND_SERVICE:
214 | return "BIND_SERVICE";
215 | case UNBIND_SERVICE:
216 | return "UNBIND_SERVICE";
217 | case DUMP_SERVICE:
218 | return "DUMP_SERVICE";
219 | case LOW_MEMORY:
220 | return "LOW_MEMORY";
221 | case ACTIVITY_CONFIGURATION_CHANGED:
222 | return "ACTIVITY_CONFIGURATION_CHANGED";
223 | case RELAUNCH_ACTIVITY:
224 | return "RELAUNCH_ACTIVITY";
225 | case PROFILER_CONTROL:
226 | return "PROFILER_CONTROL";
227 | case CREATE_BACKUP_AGENT:
228 | return "CREATE_BACKUP_AGENT";
229 | case DESTROY_BACKUP_AGENT:
230 | return "DESTROY_BACKUP_AGENT";
231 | case SUICIDE:
232 | return "SUICIDE";
233 | case REMOVE_PROVIDER:
234 | return "REMOVE_PROVIDER";
235 | case ENABLE_JIT:
236 | return "ENABLE_JIT";
237 | case DISPATCH_PACKAGE_BROADCAST:
238 | return "DISPATCH_PACKAGE_BROADCAST";
239 | case SCHEDULE_CRASH:
240 | return "SCHEDULE_CRASH";
241 | case DUMP_HEAP:
242 | return "DUMP_HEAP";
243 | case DUMP_ACTIVITY:
244 | return "DUMP_ACTIVITY";
245 | case SLEEPING:
246 | return "SLEEPING";
247 | case SET_CORE_SETTINGS:
248 | return "SET_CORE_SETTINGS";
249 | case UPDATE_PACKAGE_COMPATIBILITY_INFO:
250 | return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
251 | case TRIM_MEMORY:
252 | return "TRIM_MEMORY";
253 | }
254 | return "(unknown: " + code +")";
255 | }
256 | }
257 |
258 | }
--------------------------------------------------------------------------------
/PluginCore/src/com/plugin/core/PluginStubBinding.java:
--------------------------------------------------------------------------------
1 | package com.plugin.core;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.pm.PackageManager;
8 | import android.content.pm.ResolveInfo;
9 | import android.text.TextUtils;
10 | import android.util.Base64;
11 |
12 | import com.plugin.core.stub.ui.PluginStubActivity;
13 | import com.plugin.util.PaLog;
14 |
15 | import java.io.ByteArrayInputStream;
16 | import java.io.ByteArrayOutputStream;
17 | import java.io.IOException;
18 | import java.io.ObjectInputStream;
19 | import java.io.ObjectOutputStream;
20 | import java.io.Serializable;
21 | import java.util.HashMap;
22 | import java.util.Iterator;
23 | import java.util.List;
24 | import java.util.Map;
25 |
26 | /**
27 | * LaunchMode动态绑定,解决singleTask问题。
28 | */
29 | public class PluginStubBinding {
30 |
31 | public static final String STUB_ACTIVITY_PRE = PluginStubActivity.class.getPackage().getName();
32 |
33 | private static final String ACTION_LAUNCH_MODE = "com.plugin.core.LAUNCH_MODE";
34 |
35 | /**
36 | * key:stub Activity Name
37 | * value:plugin Activity Name
38 | */
39 | private static HashMap singleTaskMapping = new HashMap();
40 | private static HashMap singleTopMapping = new HashMap();
41 | private static HashMap singleInstanceMapping = new HashMap();
42 |
43 | /**
44 | * key:stub Service Name
45 | * value:plugin Service Name
46 | */
47 | private static HashMap serviceMapping = new HashMap();
48 |
49 | private static boolean isPoolInited = false;
50 |
51 | public static String bindLaunchModeStubActivity(String pluginActivityClassName, int launchMode) {
52 |
53 | initPool();
54 |
55 | String stubActivityName = null;
56 |
57 | Iterator> itr = null;
58 |
59 | if (launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
60 |
61 | itr = singleTaskMapping.entrySet().iterator();
62 |
63 | } else if (launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
64 |
65 | itr = singleTopMapping.entrySet().iterator();
66 |
67 | } else if (launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
68 |
69 | itr = singleInstanceMapping.entrySet().iterator();
70 |
71 | }
72 |
73 | if (itr != null) {
74 |
75 | String idleStubActivityName = null;
76 |
77 | while (itr.hasNext()) {
78 | Map.Entry entry = itr.next();
79 | if (entry.getValue() == null) {
80 | if (idleStubActivityName == null) {
81 | idleStubActivityName = entry.getKey();
82 | }
83 | } else if (pluginActivityClassName.equals(entry.getValue())) {
84 | return entry.getKey();
85 | }
86 | }
87 |
88 | //没有绑定到StubActivity,而且还有空余的stubActivity,进行绑定
89 | if (idleStubActivityName != null) {
90 | singleTaskMapping.put(idleStubActivityName, pluginActivityClassName);
91 | return idleStubActivityName;
92 | }
93 |
94 | }
95 |
96 | //绑定失败
97 | return PluginStubActivity.class.getName();
98 | }
99 |
100 | private static void initPool() {
101 | if (isPoolInited) {
102 | return;
103 | }
104 |
105 | Intent launchModeIntent = new Intent();
106 | launchModeIntent.setAction(ACTION_LAUNCH_MODE);
107 | launchModeIntent.setPackage(PluginLoader.getApplicatoin().getPackageName());
108 |
109 | List list = PluginLoader.getApplicatoin().getPackageManager().queryIntentActivities(launchModeIntent, PackageManager.MATCH_DEFAULT_ONLY);
110 |
111 | if (list != null && list.size() >0) {
112 | for (ResolveInfo resolveInfo:
113 | list) {
114 | if (resolveInfo.activityInfo.name.startsWith(STUB_ACTIVITY_PRE)) {
115 |
116 | if (resolveInfo.activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
117 |
118 | singleTaskMapping.put(resolveInfo.activityInfo.name, null);
119 |
120 | } else if (resolveInfo.activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
121 |
122 | singleTopMapping.put(resolveInfo.activityInfo.name, null);
123 |
124 | } else if (resolveInfo.activityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
125 |
126 | singleInstanceMapping.put(resolveInfo.activityInfo.name, null);
127 |
128 | }
129 |
130 | }
131 | }
132 | }
133 |
134 | isPoolInited = true;
135 | }
136 |
137 | public static void unBindLaunchModeStubActivity(String activityName, Intent intent) {
138 | if (activityName.startsWith(PluginStubBinding.STUB_ACTIVITY_PRE)) {
139 | if (intent != null) {
140 | ComponentName cn = intent.getComponent();
141 | if (cn != null) {
142 | String pluginActivityName = cn.getClassName();
143 | if (pluginActivityName.equals(singleTaskMapping.get(activityName))) {
144 | singleTaskMapping.put(activityName, null);
145 | } else if (pluginActivityName.equals(singleInstanceMapping.get(activityName))) {
146 | singleInstanceMapping.put(activityName, null);
147 | } else {
148 | //对于standard和singleTop的launchmode,不做处理。
149 | }
150 | }
151 | }
152 | }
153 | }
154 |
155 | public static String getBindedPluginServiceName(String stubServiceName) {
156 |
157 | Iterator> itr = serviceMapping.entrySet().iterator();
158 |
159 | while (itr.hasNext()) {
160 | Map.Entry entry = itr.next();
161 |
162 | if (entry.getKey().equals(stubServiceName)) {
163 | return entry.getValue();
164 | }
165 | }
166 |
167 | //没有找到,尝试重磁盘恢复
168 | HashMap mapping = restore();
169 | if (mapping != null) {
170 | itr = mapping.entrySet().iterator();
171 | while (itr.hasNext()) {
172 | Map.Entry entry = itr.next();
173 |
174 | if (entry.getKey().equals(stubServiceName)) {
175 | serviceMapping.put(stubServiceName, entry.getValue());
176 | save(serviceMapping);
177 | return entry.getValue();
178 | }
179 | }
180 | }
181 |
182 | return null;
183 | }
184 |
185 | public static String bindStubService(String pluginServiceClassName) {
186 |
187 | initPool();
188 |
189 | Iterator> itr = serviceMapping.entrySet().iterator();
190 |
191 | String idleStubServiceName = null;
192 |
193 | while (itr.hasNext()) {
194 | Map.Entry entry = itr.next();
195 | if (entry.getValue() == null) {
196 | if (idleStubServiceName == null) {
197 | idleStubServiceName = entry.getKey();
198 | //这里找到空闲的idleStubServiceName以后,还需继续遍历,用来检查是否pluginActivityClassName已经绑定过了
199 | }
200 | } else if (pluginServiceClassName.equals(entry.getValue())) {
201 | //已经绑定过,直接返回
202 | PaLog.d("已经绑定过", entry.getKey(), pluginServiceClassName);
203 | return entry.getKey();
204 | }
205 | }
206 |
207 | //没有绑定到StubService,而且还有空余的StubService,进行绑定
208 | if (idleStubServiceName != null) {
209 | PaLog.d("添加绑定", idleStubServiceName, pluginServiceClassName);
210 | serviceMapping.put(idleStubServiceName, pluginServiceClassName);
211 | //对serviceMapping持久化是因为如果service处于运行状态时app发生了crash,系统会自动恢复之前的service,此时插件映射信息查不到的话会再次crash
212 | save(serviceMapping);
213 | return idleStubServiceName;
214 | }
215 |
216 | //绑定失败
217 | return null;
218 | }
219 |
220 | public static void unBindStubService(String pluginServiceName) {
221 | Iterator> itr = serviceMapping.entrySet().iterator();
222 | while (itr.hasNext()) {
223 | Map.Entry entry = itr.next();
224 | if (pluginServiceName.equals(entry.getValue())) {
225 | //如果存在绑定关系,解绑
226 | PaLog.d("回收绑定", entry.getKey(), entry.getValue());
227 | serviceMapping.put(entry.getKey(), null);
228 | save(serviceMapping);
229 | entry.getKey();
230 | }
231 | }
232 | }
233 |
234 | private static boolean save(HashMap mapping) {
235 |
236 | ObjectOutputStream objectOutputStream = null;
237 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
238 | try {
239 | objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
240 | objectOutputStream.writeObject(mapping);
241 | objectOutputStream.flush();
242 |
243 | byte[] data = byteArrayOutputStream.toByteArray();
244 | String list = Base64.encodeToString(data, Base64.DEFAULT);
245 |
246 | PluginLoader.getApplicatoin()
247 | .getSharedPreferences("plugins.serviceMapping", Context.MODE_PRIVATE)
248 | .edit().putString("plugins.serviceMapping.map", list).commit();
249 |
250 | return true;
251 | } catch (Exception e) {
252 | e.printStackTrace();
253 | } finally {
254 | if (objectOutputStream != null) {
255 | try {
256 | objectOutputStream.close();
257 | } catch (IOException e) {
258 | e.printStackTrace();
259 | }
260 | }
261 | if (byteArrayOutputStream != null) {
262 | try {
263 | byteArrayOutputStream.close();
264 | } catch (IOException e) {
265 | e.printStackTrace();
266 | }
267 | }
268 | }
269 | return false;
270 | }
271 |
272 | private static HashMap restore() {
273 | String list = PluginLoader.getApplicatoin()
274 | .getSharedPreferences("plugins.serviceMapping", Context.MODE_PRIVATE)
275 | .getString("plugins.serviceMapping.map", "");
276 | Serializable object = null;
277 | if (!TextUtils.isEmpty(list)) {
278 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
279 | Base64.decode(list, Base64.DEFAULT));
280 | ObjectInputStream objectInputStream = null;
281 | try {
282 | objectInputStream = new ObjectInputStream(byteArrayInputStream);
283 | object = (Serializable) objectInputStream.readObject();
284 | } catch (Exception e) {
285 | e.printStackTrace();
286 | } finally {
287 | if (objectInputStream != null) {
288 | try {
289 | objectInputStream.close();
290 | } catch (IOException e) {
291 | e.printStackTrace();
292 | }
293 | }
294 | if (byteArrayInputStream != null) {
295 | try {
296 | byteArrayInputStream.close();
297 | } catch (IOException e) {
298 | e.printStackTrace();
299 | }
300 | }
301 | }
302 | }
303 | if (object != null) {
304 |
305 | HashMap mapping = (HashMap) object;
306 | return mapping;
307 | }
308 | return null;
309 | }
310 | }
311 |
--------------------------------------------------------------------------------