APK免安装启动
在讲诉具体实现之前说一下涉及的知识点Java虚拟机启动流程启动Java虚拟机,创建ClassLoader,将java字节码加载进入ClassLoader,随即找到入口函数,执行。当需要创建一个对象的时候,向Java虚拟机发送一个请求,Java虚拟机接收到请求以后,首先在内存中进行寻找,若存在,则解析class,找到相应的方法执行。若内存中不存在,则让ClassLoader对相应的.clas
在讲诉具体实现之前说一下涉及的知识点
Java虚拟机启动流程
启动Java虚拟机,创建ClassLoader,将java字节码加载进入ClassLoader,随即找到入口函数,执行。当需要创建一个对象的时候,向Java虚拟机发送一个请求,Java虚拟机接收到请求以后,首先在内存中进行寻找,若存在,则解析class,找到相应的方法执行。若内存中不存在,则让ClassLoader对相应的.class文件通过import 路径进行加载到内存中,然后进行解析,找到对应的方法执行。(ClassLoader实际上为虚拟机的一个部分,Java虚拟机并不会一次性将java字节码中的所有class文件进行加载,是需要什么,在内存中寻找不到的时候,再通过ClassLoader将对应的.class文件通过路径方式,加载进入内存,供他人使用)。Android dalvik虚拟机对于ClassLoader的处理与Java虚拟机类似。
在说之前,先提出一个问题:
我们知道DexClassLoader加载的Activity是没有生命周期的,而我们知道dalvik对于类的查找以及加载流程了,那么我们是不是可以将我们加载的dex让Android虚拟机帮我们管理呢,因为虚拟机要加载类同样也是通过ClassLoader进行加载的。
**
这便有产生一个想法,Android程序运行过程中,所需要的类都是采用上述方式进行加载,那么可不可以找到虚拟机中的ClassLoader,将需要动态加载的dex或者APK文件进行加载,便于去寻找。
首先找到ZygoteInit.java文件 这个是创建应用进程的入口
1
2
3
4
5
6
7
8
9
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
|
<code
class
=
"hljs javascript"
>
static
void
invokeStaticMain(ClassLoader loader,
119
String className, String[] argv)
120
throws
ZygoteInit.MethodAndArgsCaller {
121
Class<!--?--> cl;
122
123
try
{
124
cl = loader.loadClass(className);
125
}
catch
(ClassNotFoundException ex) {
126
throw
new
RuntimeException(
127
"Missing class when invoking static main "
+ className,
128
ex);
129
}
130
131
Method m;
132
try
{
//在此处调用ActivityThread.java的main方法
133
m = cl.getMethod(
"main"
,
new
Class[] { String[].
class
});
134
}
catch
(NoSuchMethodException ex) {
135
throw
new
RuntimeException(
136
"Missing static main on "
+ className, ex);
137
}
catch
(SecurityException ex) {
138
throw
new
RuntimeException(
139
"Problem getting static main on "
+ className, ex);
140
}
141
142
int
modifiers = m.getModifiers();
143
if
(! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
144
throw
new
RuntimeException(
145
"Main method is not public and static on "
+ className);
146
}
147
148
/*
149 * This throw gets caught in ZygoteInit.main(), which responds
150 * by invoking the exception's run() method. This arrangement
151 * clears up all the stack frames that were required in setting
152 * up the process.
153 */
154
throw
new
ZygoteInit.MethodAndArgsCaller(m, argv);
155
}</code>
|
在此处创建了一个新的ActivityThread对象
1
2
3
4
5
6
7
8
9
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
|
<code
class
=
"hljs java"
>
public
static
void
main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(
false
);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(
new
EventLoggingReporter());
Security.addProvider(
new
AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final
File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0(
"<pre-initialized>"
);
Looper.prepareMainLooper();
ActivityThread thread =
new
ActivityThread();
thread.attach(
false
);
if
(sMainThreadHandler ==
null
) {
sMainThreadHandler = thread.getHandler();
}
if
(
false
) {
Looper.myLooper().setMessageLogging(
new
LogPrinter(Log.DEBUG,
"ActivityThread"
));
}
Looper.loop();
throw
new
RuntimeException(
"Main thread loop unexpectedly exited"
);
}</pre-initialized></code>
|
现在我们看一下ActivityThread实例中的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<code
class
=
"hljs vhdl"
>
// These can be accessed by multiple threads; mPackages is the lock.
// XXX For now we keep around information about all packages we have
// seen, not removing entries from this map.
// NOTE: The activity and window managers need to call in to
// ActivityThread to do things like update resource configurations,
// which means this lock gets held while the activity and window managers
// holds their own lock. Thus you MUST NEVER call back into the activity manager
// or window manager or anything that depends on them while holding this lock.
final
ArrayMap<string, loadedapk=
""
>> mPackages
=
new
ArrayMap<string, loadedapk=
""
>>();
final
ArrayMap<string, loadedapk=
""
>> mResourcePackages
=
new
ArrayMap<string, loadedapk=
""
>>();
final
ArrayList mRelaunchingActivities
=
new
ArrayList();
</activityclientrecord></activityclientrecord></string,></string,></string,></string,></code>
|
我们看见其中有mPackages,mResourcePackages, mRelaunchingActivities其他两个不用理会,我们关注mPackages,我们转而进入LoadedApk.java;去看看里面有什么东西。
1
2
3
4
5
6
7
8
9
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
|
<code
class
=
"hljs java"
>
/**
75 * Local state maintained about a currently loaded .apk.
76 * @hide
77 */
78
public
final
class
LoadedApk {
79
80
private
static
final
String TAG =
"LoadedApk"
;
81
82
private
final
ActivityThread mActivityThread;
83
private
ApplicationInfo mApplicationInfo;
84
final
String mPackageName;
85
private
final
String mAppDir;
86
private
final
String mResDir;
87
private
final
String[] mSplitAppDirs;
88
private
final
String[] mSplitResDirs;
89
private
final
String[] mOverlayDirs;
90
private
final
String[] mSharedLibraries;
91
private
final
String mDataDir;
92
private
final
String mLibDir;
93
private
final
File mDataDirFile;
94
private
final
ClassLoader mBaseClassLoader;
95
private
final
boolean
mSecurityViolation;
96
private
final
boolean
mIncludeCode;
97
private
final
boolean
mRegisterPackage;
98
private
final
DisplayAdjustments mDisplayAdjustments =
new
DisplayAdjustments();
99
Resources mResources;
100
private
ClassLoader mClassLoader;
101
private
Application mApplication;
102
103
private
final
ArrayMap<context, receiverdispatcher=
""
>> mReceivers
104
=
new
ArrayMap<context, loadedapk.receiverdispatcher=
""
>>();
105
private
final
ArrayMap<context, loadedapk.receiverdispatcher=
""
>> mUnregisteredReceivers
106
=
new
ArrayMap<context, loadedapk.receiverdispatcher=
""
>>();
107
private
final
ArrayMap<context, loadedapk.servicedispatcher=
""
>> mServices
108
=
new
ArrayMap<context, loadedapk.servicedispatcher=
""
>>();
109
private
final
ArrayMap<context, loadedapk.servicedispatcher=
""
>> mUnboundServices
110
=
new
ArrayMap<context, loadedapk.servicedispatcher=
""
>>();
111
112
int
mClientCount =
0
;
113
114
Application getApplication() {
115
return
mApplication;
116
}</context,></context,></context,></context,></context,></context,></context,></context,></code>
|
在上述的变量中我们发现有一个非final类型的ClassLoader对象,在这里楼主做了一些求证,为了证明该ClassLoader对象是我所需要的,我将日志输入进去,并编译了整个源码,运行了一个测试程序,发现这个的确是我所需要的ClassLoader。
因此到此为止,我们已经找到我们所需要的东西。
接下来我们说一下Android中的ClassLoader机制
大家都知道,Android对外开发的有2个ClassLoader, 一个叫做PathClassLoader,另外一个叫做DexClassLoader。他们都是继承BaseDexClassLoader,当我们实例化一个DexClassLoader的时候,
1
|
<code
class
=
"hljs lasso"
>
public
DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) </code>
|
上述为DexClassLoader的构造函数, 传入的parent是做什么用途的呢 ?
一个ClassLoader的加载类的流程为,首先通过最顶层的classLoader中进行查找该classLoader中是否已经存在了该类,若没有,则在次一层的classLoader中进行查找,当上层的classLoader都没有找到该类的时候,则跑到最后一层中对该类进行查找,若找到则返回该类,若没有找到则返回ClassNotFound的异常。
一言以蔽之:加载一个类,首先在内存中查找,没有则通过dex文件进行路径查找该类,若找到,加载至内存,若没有则返回ClassNotFound异常。以下为classLoader的查找类函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<code
class
=
"hljs java"
>
protected
Class<!--?--> loadClass(String className,
boolean
resolve)
throws
ClassNotFoundException {
Class<!--?--> clazz = findLoadedClass(className);
if
(clazz ==
null
) {
ClassNotFoundException suppressed =
null
;
try
{
clazz = parent.loadClass(className,
false
);
}
catch
(ClassNotFoundException e) {
suppressed = e;
}
if
(clazz ==
null
) {
try
{
clazz = findClass(className);
}
catch
(ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw
e;
}
}
}
return
clazz;
}</code>
|
因此我们可以得出,当我们实例化一个DexClassLoader,并将其构造函数的parent设置为LoadedApk.java中的mClassLoader,并将其替换,这样不就可以实现让Android帮我们自动管理我们所动态加载的Activity了嘛。
因为这样并不会导致原来的类查找不到,因为我已经将原来载入了主APK的ClassLoader设置为我们替代的DexClassLoader的parent。这样当虚拟机去加载类的时候,同样还是可以加载原有的,同时又可以加载我们动态加载的dex。
到此为止,我们已经从理论上实现了动态加载Activity,并让Android本身帮助我们管理动态加载的Activity,并且带有生命周期。
以下是动态加载DEX的代码:
1
2
3
4
5
6
7
8
9
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
81
82
83
84
85
86
87
88
89
90
91
|
<code
class
=
"hljs java"
>
public
class
MainActivity
extends
AppCompatActivity {
private
static
final
String ACTIVITY_THREAD_CLASS_PATH =
"android.app.ActivityThread"
;
private
static
final
String GET_CURRENT_ACTIVITY_THREAD_METHOD_NAME =
"currentActivityThread"
;
private
static
final
String ACTIVITY_THREAD_PACKAGES =
"mPackages"
;
private
static
final
String LOAD_APK_CLASS_PATH =
"android.app.LoadedApk"
;
private
static
final
String LOAD_APK_CLASS_LOADER_FILED_NAME =
"mClassLoader"
;
private
static
final
String APK_NAME =
"app-debug.apk"
;
private
static
final
String PLUGIN_APK_PATH = Environment.getExternalStorageDirectory().getPath() + File.separator + APK_NAME;
private
static
String PLUGIN_APK_INTENER_PATH;
private
static
String DEX_OUT_PUT_PATH = Environment.getExternalStorageDirectory().getPath();
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DEX_OUT_PUT_PATH = getApplicationContext().getFilesDir().getPath();
init();
initLoadPlugin();
}
@Override
public
void
onResume(){
super
.onResume();
DynamicApplication.getApplication().resetResource();
}
private
void
init() {
View button = findViewById(R.id.buttonPanel);
button.setOnClickListener(
new
View.OnClickListener() {
@Override
public
void
onClick(View v) {
DynamicApplication.getApplication().loadPlugInResources(PLUGIN_APK_INTENER_PATH);
Intent intent =
new
Intent();
intent.setClassName(MainActivity.
this
,
"smither.gionee.com.plugin.PluginActivity"
);
ResolveInfo info = getPackageManager().resolveActivity(intent,
0
);
if
(
null
!= info) {
startActivity(intent);
}
}
});
}
private
void
replaceApkClassLoader(DexClassLoader classLoader)
throws
Exception {
Object currentActivityThread = RefInvoke.invokeStaticMethod(ACTIVITY_THREAD_CLASS_PATH,
GET_CURRENT_ACTIVITY_THREAD_METHOD_NAME,
new
Class[]{},
new
Object[]{});
String currentPackageName = getPackageName();
ArrayMap activityThreadPackages = (ArrayMap) RefInvoke.getFieldOjbect(ACTIVITY_THREAD_CLASS_PATH,
currentActivityThread, ACTIVITY_THREAD_PACKAGES);
WeakReference loadApk = (WeakReference) activityThreadPackages.get(currentPackageName);
RefInvoke.setFieldOjbect(LOAD_APK_CLASS_PATH, LOAD_APK_CLASS_LOADER_FILED_NAME, loadApk.get(), classLoader);
}
private
void
initLoadPlugin() {
if
(
new
File(PLUGIN_APK_PATH).exists()){
try
{
String copyApkPath = copyDex().getPath();
DexClassLoader classLoader =
new
DexClassLoader(copyApkPath,
DEX_OUT_PUT_PATH,
null
, getClassLoader());
replaceApkClassLoader(classLoader);
}
catch
(Exception e) {
e.printStackTrace();
}
}
else
{
Toast.makeText(
this
,
"PLUGIN_APK_PATH is not exists"
, Toast.LENGTH_SHORT).show();
}
}
private
File copyDex()
throws
IOException {
File file =
new
File(getApplicationContext().getFilesDir() + File.separator + APK_NAME);
File plugIn1 =
new
File(PLUGIN_APK_PATH);
if
(!file.exists()) {
file.createNewFile();
}
if
(plugIn1.length() != file.length()) {
FileOutputStream outputStream =
new
FileOutputStream(file);
FileInputStream fileInputStream =
new
FileInputStream(plugIn1);
byte
[] buffer =
new
byte
[fileInputStream.available()];
fileInputStream.read(buffer);
fileInputStream.close();
outputStream.write(buffer);
outputStream.close();
}
PLUGIN_APK_INTENER_PATH = file.getPath();
return
file;
}
}</code>
|
需要动态加载的APK的代码我就不贴出来了,那里面只有一个空的Activity; 名字为smither.gionee.com.plugin.PluginActivity
现在可以去运行了,哈哈,不过你突然发现,我明明可以加载Activity,但是跳转到dex中的Activity为何还会报错?
这是因为当你用DexClassLoader进行加载APK的时候,并不会将你的AndroidManifest文件进行加载,因此你需要在主程序的AndroidManifest中声明该Activity。
接下来你就可以启动该Activity了。
接下来又产生一个新的问题
你会发现View不见了,或者跳转到的Acitivty的View并非你所指定的View
这是因为采用DexClassLoader动态加载进来的APK并没有将资源文件加载进来,而当插件APK中调用setContentView(int layoutId),所访问的R文件其实是调用插件中的R文件,而插件中的R文件,与宿主本身的R文件不一样,因此当引用插件R文件中的资源ID的时候,实际上是通过该资源ID去宿主中去寻找资源文件,这样肯定是找不到的。因为不同的APK的R资源都是不一样的。所以是无法直接使用R.layout.customer_layout来访问的。因为这样所访问的资源ID其实是不存在的。
那么有没有办法呢?
首先我们可以确定通过DexClassLoader加载进来的APK中的Activity并没有产生新的Application, 而跳转到插件中的Activity实际上还是使用的同一个Application。因为一个Application的产生需要通过Zygote fork出一个新的进程,进而去加载APK资源,产生Application。通常正常情况下,一个应用是只有一个Application,当我们通过上述方式获取Application的时候,其实还是获取到宿主的Application。
因此在此时可以通过重写Application.getResource()等方法,当切换到插件工程中的Activity的时候,可以将对应的在Application中切换资源的获取。在设置View,获取layout,String等资源文件的时候,采用Application.getResource(),而这个实际上获取到的就是我们手动加载进来的插件工程中的资源文件。
接下来看一下代码的实现:
首先看一下宿主工程的Application
1
2
3
4
5
6
7
8
9
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
|
<code
class
=
"hljs java"
>
public
class
DynamicApplication
extends
Application{
private
Resources mResource;
private
Resources.Theme mTheme;
private
AssetManager mAssetManager;
private
static
DynamicApplication mApplication;
public
static
DynamicApplication getApplication(){
return
mApplication;
}
public
void
onCreate() {
super
.onCreate();
mApplication =
this
;
}
public
void
resetResource(){
mResource =
super
.getResources();
mTheme =
super
.getTheme();
}
@Override
public
Resources getResources() {
return
mResource ==
null
?
super
.getResources() : mResource;
}
@Override
public
Resources.Theme getTheme() {
return
mTheme ==
null
?
super
.getTheme() : mTheme;
}
public
void
loadPlugInResources(String dexPath) {
try
{
AssetManager assetManager = AssetManager.
class
.newInstance();
Method addAssetPathMethod = assetManager.getClass().getMethod(
"addAssetPath"
, String.
class
);
Object obj = addAssetPathMethod.invoke(assetManager, dexPath);
mAssetManager = assetManager;
Resources superResources =
super
.getResources();
mResource =
new
Resources(assetManager, superResources.getDisplayMetrics(), superResources.getConfiguration());
mTheme = mResource.newTheme();
mTheme.setTo(
super
.getTheme());
}
catch
(InstantiationException e) {
e.printStackTrace();
}
catch
(IllegalAccessException e) {
e.printStackTrace();
}
catch
(NoSuchMethodException e) {
e.printStackTrace();
}
catch
(InvocationTargetException e) {
e.printStackTrace();
}
}
}</code>
|
接下来是插件工程中Activity获取资源的方式需要稍微修改一下:
1
2
3
4
5
6
7
|
<code
class
=
"hljs java"
> View view = LayoutInflater.from(getApplication()).inflate(R.layout.activity_plugin,
null
);
view = LayoutInflater.from(getApplication()).inflate(getApplication().getResources().getLayout(R.layout.activity_plugin),
null
);
/**
* 该处的两种加载XML layout的方式都是可行的。效果是一样的 走的也是同一套流程 在inflate函数中同样也调用了getResources().getLayout()方式。
*/
setContentView(view);</code>
|
在此处我有试过将获取的日志进行输出(可以参看下面的日志信息):
1
2
3
4
5
6
7
8
9
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
|
<code
class
=
"hljs java"
>
public
class
PluginActivity
extends
AppCompatActivity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
/**
* 当该apk由另外一个APK启动的时候,此时获取的getApplication()实际上为启动该ACTIVITY的应用的Application。
* 且我在主工程的Application已经对该APK的资源进行了加载,且对getResource等方法进行了重写。因此当调用getApplication(),
* 实际上获取的是主工程的DynamicApplication,因为插件的Application并未启动,而在DynamicApplication中又对getResource进行了重写,
* 因此此刻获取到的实际上是插件工程中的资源文件。这样便可以实现调用
*/
Log.v(
"PluginActivity"
,
"onCreate"
);
/**
* 该处返回的所指定的Context所生成的LayoutInflater 可以参看LayoutInflater的构造函数
* Obtains the LayoutInflater from the given context.
* public static LayoutInflater from(Context context) {
* LayoutInflater LayoutInflater =
* (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
* if (LayoutInflater == null) {
* throw new AssertionError("LayoutInflater not found.");
* }
* return LayoutInflater;
*}
*/
LayoutInflater inflater = LayoutInflater.from(getApplication());
Log.v(
"PluginActivity"
,
"LayoutInflater inside Context:"
+ inflater.getContext() +
" Application:"
+ getApplication());
Log.v(
"PluginActivity"
,
"LayoutInflater inside Context == getApplication() :"
+ (inflater.getContext() == getApplication()));
/**
* public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
* final Resources res = getContext().getResources();
* if (DEBUG) {
* Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
* + Integer.toHexString(resource) + ")");
* }
* final XmlResourceParser parser = res.getLayout(resource);
* try {
* return inflate(parser, root, attachToRoot);
* } finally {
* parser.close();
* }
* }
* 以下为输出日志LayoutInflater inside Context:smither.gionee.com.dynamicmain.DynamicApplication@e6b7a3 Application:smither.gionee.com.dynamicmain.DynamicApplication@e6b7a3
* 从上文可以看出由于Context为相同的 因此其调用的 getResources实际上为在DynamicApplication@重写的getResources,因此能够正常加载
* 同理可以获取String等资源
* 为何一样的原因请参看https://docs.google.com/document/d/10EYlyuxDw1KPy7LJlGtgMz69gwMO-pjDHS2GRtppvZg/edit?pref=2&pli=1
*
* 下列为下面所有日志打印的输出:
* 01-15 08:47:31.027 V/PluginActivity(14208): onCreate
* 01-15 08:47:31.028 V/PluginActivity(14208): LayoutInflater inside Context:smither.gionee.com.dynamicmain.DynamicApplication@e6b7a3 Application:smither.gionee.com.dynamicmain.DynamicApplication@e6b7a3
* 01-15 08:47:31.028 V/PluginActivity(14208): LayoutInflater inside Context == getApplication() :true
* 01-15 08:47:31.030 V/PluginActivity(14208): LayoutInflater.from(getApplication()).inflate(R.layout.activity_plugin, null):false
* 01-15 08:47:31.040 V/PluginActivity(14208): LayoutInflater.from(getApplication()).inflate(getResources().getLayout(R.layout.activity_plugin), null):false
* 01-15 08:47:31.040 V/PluginActivity(14208): Current Package Name:smither.gionee.com.dynamicmain Real package name:smither.gionee.com.plugin
* 01-15 08:47:31.040 V/PluginActivity(14208): getApplication Current Package Name:smither.gionee.com.dynamicmain Real package name:smither.gionee.com.plugin
* 01-15 08:47:31.040 V/PluginActivity(14208): getResources().getString(R.string.app_name):DynamicMain real app name:Plugin
* 01-15 08:47:31.040 V/PluginActivity(14208): getApplication getResources().getString(R.string.app_name):Plugin real app name:Plugin
*/
View view = LayoutInflater.from(getApplication()).inflate(R.layout.activity_plugin,
null
);
Log.v(
"PluginActivity"
,
"LayoutInflater.from(getApplication()).inflate(R.layout.activity_plugin, null):"
+ (
null
== view ?
true
:
false
));
view = LayoutInflater.from(getApplication()).inflate(getApplication().getResources().getLayout(R.layout.activity_plugin),
null
);
/**
* 该处的两种加载XML layout的方式都是可行的。效果是一样的 走的也是同一套流程 在inflate函数中同样也调用了getResources().getLayout()方式。
*/
setContentView(view);
Log.v(
"PluginActivity"
,
"LayoutInflater.from(getApplication()).inflate(getResources().getLayout(R.layout.activity_plugin), null):"
+ (
null
== view ?
true
:
false
));
// setContentView(R.layout.activity_plugin);
Log.v(
"PluginActivity"
,
"Current Package Name:"
+ getPackageName() +
" Real package name:smither.gionee.com.plugin"
);
Log.v(
"PluginActivity"
,
"getApplication Current Package Name:"
+ getApplication().getPackageName() +
" Real package name:smither.gionee.com.plugin"
);
Log.v(
"PluginActivity"
,
"getResources().getString(R.string.app_name):"
+ getResources().getString(R.string.app_name) +
" real app name:Plugin"
);
Log.v(
"PluginActivity"
,
"getApplication getResources().getString(R.string.app_name):"
+ getApplication().getResources().getString(R.string.app_name) +
" real app name:Plugin"
);
}
}</code>
|
此时,就可以正常使用插件工程中的Activity了。
好了,此次免安装方式动态加载APK就讲到此处。
更多推荐
所有评论(0)