Android动态加载Activity原理详解

activity的启动流程

加载一个Activity肯定不会像加载一般的类那样,因为activity作为系统的组件有自己的生命周期,有系统的很多回调控制,所以自定义一个DexClassLoader类加载器来加载插件中的Activity肯定是不可以的。

首先不得不了解一下activity的启动流程,当然只是简单的看一下,太详细的话很难研究清楚。

通过startActivity启动后,最终通过AMS进行跨进程回调到ApplicationThread的scheduleLaunchActivity,这时会创建一个ActivityClientRecord对象,这个对象表示一个Acticity以及他的相关信息,比如activityInfo字段包括了启动模式等,还有loadedApk,顾名思义指的是加载过了的APK,他会被放在一个Map中,应用包名到LoadedApk的键值对,包含了一个应用的相关信息。然后通过Handler切换到主线程执performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
// 1.创建ActivityClientRecord对象时没有对他的packageInfo赋值,所以它是null
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE);
}
// ...
Activity activity = null;
try {
// 2.非常重要!!这个ClassLoader保存于LoadedApk对象中,它是用来加载我们写的activity的加载器
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
// 3.用加载器来加载activity类,这个会根据不同的intent加载匹配的activity
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
// 4.这里的异常也是非常非常重要的!!!后面就根据这个提示找到突破口。。。
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
// 从这里就会执行到我们通常看到的activity的生命周期的onCreate里面
mInstrumentation.callActivityOnCreate(activity, r.state);
// 省略的是根据不同的状态执行生命周期
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
// ...
}
return activity;
}

1.getPackageInfo方法最终返回一个LoadedApk对象,它会从一个HashMap的数据结构中取,mPackages维护了包名和LoadedApk的对应关系,即每一个应用有一个键值对对应。如果为null,就新创建一个LoadedApk对象,并将其添加到Map中,重点是这个对象的ClassLoader字段为null!

public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
int flags) {
// 为true
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
boolean securityViolation = includeCode && ai.uid != 0
&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
: true);
// ...
// includeCode为true
// classloader为null!!!
return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
synchronized (mPackages) {
WeakReference<loadedapk> ref;
if (includeCode) {
// includeCode为true
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) // ...
// packageInfo为null,创建一个LoadedApk,并且添加到mPackages里面
packageInfo = new LoadedApk(this, aInfo, compatInfo, this, baseLoader, securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo. ) != 0);
if (includeCode) {
mPackages.put(aInfo.packageName, new WeakReference<loadedapk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName, new WeakReference<loadedapk>(packageInfo));
}
}
return packageInfo;
}
}</loadedapk></loadedapk></loadedapk>

2.获取这个activity对应的类加载器,由于上面说过,mClassLoader为null,那么就会执行到ApplicationLoaders#getClassLoader(zip, libraryPath, mBaseClassLoader)方法。

public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader != null) {
return mClassLoader;
}
// ...
// 创建加载器,创建默认的加载器
// zip为Apk的路径,libraryPath也就是JNI的路径
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, libraryPath, mBaseClassLoader);
initializeJavaContextClassLoader();
StrictMode.setThreadPolicy(oldPolicy);
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
} else {
mClassLoader = mBaseClassLoader;
}
}
return mClassLoader;
}
}

ApplicationLoaders使用单例它的getClassLoader方法根据传入的zip路径事实上也就是Apk的路径来创建加载器,返回的是一个PathClassLoader。并且PathClassLoader只能加载安装过的APK。这个加载器创建的时候传入的是当前应用APK的路径,理所应当的,想加载其他的APK就构造一个传递其他APK的类加载器。

3.用该类加载器加载我们要启动的activity,并反射创建一个activity实例

public Activity newActivity(ClassLoader cl, String className,Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}

总结一下上面的思路就是,当我们启动一个activity时,通过系统默认的PathClassLoader来加载这个activity,当然默认情况下只能加载本应用里面的activity,然后就由系统调用到这个activity的生命周期中。

4.这个地方的异常在后面的示例中会出现,到时候分析到原因后就可以找出我们动态加载Activity的思路了。

动态加载Activity:修改系统类加载器

按照这个思路,做这样的一个示例,按下按钮,打开插件中的Activity。

插件项目

plugin.dl.pluginactivity

|--MainActivity.java

内容很简单,就是一个布局上面写了这是插件中的Activity!并重写了他的onStart和onDestroy方法。

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载到宿主程序中之后,这个R.layout.activity_main就是宿主程序中的R.layout.activity_main了
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this,"onStart", 0).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
Toast.makeText(this,"onDestroy", 0).show();
}
}

宿主项目

host.dl.hostactivity

|--MainActivity.java

包括两个按钮,第一个按钮跳转到插件中的MainActivity.java,第二个按钮调转到本应用中的MainActivity.java

private Button btn;
private Button btn1;
DexClassLoader loader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn1 = (Button) findViewById(R.id.btn1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Class activity = null;
String dexPath = "/PluginActivity.apk";
loader = new DexClassLoader(dexPath, MainActivity.this.getApplicationInfo().dataDir, null, getClass().getClassLoader());
try {
activity = loader.loadClass("plugin.dl.pluginactivity.MainActivity");
}catch (ClassNotFoundException e) {
Log.i("MainActivity", "ClassNotFoundException");
}
Intent intent = new Intent(MainActivity.this, activity);
MainActivity.this.startActivity(intent);
}
});
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MainActivity2.class);
MainActivity.this.startActivity(intent);
}
});

首先我们要将该activity在宿主工程的额AndroidManifest里面注册。点击按钮打开插件中的activity,发现报错

java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{host.dl.hostactivity/plugin.dl.pluginactivity.MainActivity}: java.lang.ClassNotFoundException: plugin.dl.pluginactivity.MainActivity

#已经使用自定义的加载器,当startActivity时为什么提示找不到插件中的activity?

前面第四点说过这个异常。其实这个异常就是在performLaunchActivity中抛出的,仔细看这个异常打印信息,发现它说plugin.dl.pluginactivity.MainActivity类找不到,可是我们不是刚刚定义了一个DexClassLoader,成功加载了这个类的吗??怎么这里又提示这个类找不到?

实际上,确实是这样的,还记得前面说过,系统默认的类加载器PathClassLoader吗?(因为LoadedApk对象的mClassLoader变量为null,就调用到ApplicationLoaders#getClassLoader方法,即根据当前应用的路径返回一个默认的PathClassLoader),当执行到mPackages.get(aInfo.packageName);时从Map获取的LoadedApk中未指定mClassLoader,因此会使用系统默认的类加载器。于是当执行这一句 mInstrumentation.newActivity(cl, component.getClassName(), r.intent);时,由于这个类加载器找不到我们插件工程中的类,因此报错了。

现在很清楚了,原因就是使用系统默认的这个类加载器不包含插件工程路径,无法正确加载我们想要的activity造成的。

于是考虑替换系统的类加载器。

private void replaceClassLoader(DexClassLoader loader) {
try {
Class clazz_Ath = Class.forName("android.app.ActivityThread");
Class clazz_LApk = Class.forName("android.app.LoadedApk");
Object currentActivityThread = clazz_Ath.getMethod("currentActivityThread").invoke(null);
Field field1 = clazz_Ath.getDeclaredField("mPackages");
field1.setAccessible(true);
Map mPackages = (Map) field1.get(currentActivitead);
String packageName = MainActivity.this.getPackageName();
WeakReference ref = (WeakReference) mPackages.get(packageName);
Field field2 = clazz_LApk.getDeclaredField("mClassLoader");
field2.setAccessible(true);
field2.set(ref.get(), loader);
} catch (Exception e) {
e.printStackTrace();
}
}

这段代码的思路是将ActivityThread类中的mPackages变量中保存的以当前包名为键的LoadedApk值的mClassLoader替换成我们自定义的类加载器。当下一次要加载存放在别的地方的插件中的某个Activity时,直接在mPackages变量中能取到,因此用的就是我们修改了的类加载器了。
因此,在打开插件中的activity之前调用replaceClassLoader(loader);方法替换系统的类加载器,就可以了。

效果如下

此时发现可以启动插件中的activity,因为执行到了他的onStart方法,并且关闭的时候执行了onDestroy方法,但是奇怪的是界面上的控件貌似没有变化?和启动他的界面一模一样,还不能点击。这是什么原因呢?

显然,我们只是把插件中的MainActivity类加载过来了,当执行到他的onCreate方法时,在里面调用setContentView使用的布局参数是R.layout.activity_main,当然使用的就是当前应用的资源了!

##已经替换了系统的类加载器为什么加载本应用的activity却能正常运行?

不过在修正这个问题之前,有没有发现一个很奇怪的现象,当加载过插件中的activity后,再次启动本地的activity也是能正常启动的?这是为什么呢?前面已经替换了默认的类加载器了,并且可以在打开插件中的activity后再点击第二个按钮打开本应用的activity之前查看使用的activity,确实是我们已经替换了的类加载器。那这里为什么还能正常启动本应用的activity呢?玄机就在我们创建DexClassLoader时的第四个参数,父加载器!设置父加载器为当前类的加载器,就能保证类的双亲委派模型不被破坏,在加载类时都是先由父加载器来加载,加载不成功时在由自己加载。不信可以在new这个加载器的时候父加载器的参数设置成其他值,比如系统类加载器,那么当运行activity时肯定会报错。

接下来解决前面出现的,跳转到插件activity中界面显示不对的问题。这个现象出现的原因已经解释过了,就是因为使用了本地的资源所导致的,因此需要在setContentView时,使用插件中的资源布局。因此在插件Activity中作如下修改

public class MainActivity2 extends Activity {
private static View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载到宿主程序中之后,这个R.layout.activity_main就是宿主程序中的R.layout.activity_main了
// setContentView(R.layout.activity_main);
if (view != null)
setContentView(view);
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this,"onStart", 0).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
Toast.makeText(this,"onDestroy", 0).show();
}
private static void setLayout(View v){
view = v;
}
}

然后在宿主Activity中获取插件资源并将布局填充成View,然后设置给插件中的activity,作为它的ContentView的内容。

Class<!--?--> layout = loader.loadClass("plugin.dl.pluginactivity.R$layout");
Field field = layout.getField("activity_main");
Integer obj = (Integer) field.get(null);
// 使用包含插件APK的Resources对象来获取这个布局才能正确获取插件中定义的界面效果
//View view = LayoutInflater.from(MainActivity.this).inflate(resources.getLayout(obj),null);
// 或者这样,但一定要重写getResources方法,才能这样写
View view = LayoutInflater.from(MainActivity.this).inflate(obj, null);
Method method = activity.getDeclaredMethod("setLayout", View.class);
method.setAccessible(true);
method.invoke(activity, view);

完整的代码

public class MainActivity extends Activity {
private Resources resources;
protected AssetManager assetManager;
private Button btn;
private Button btn1;
DexClassLoader loader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
btn1 = (Button) findViewById(R.id.btn1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String dexPath = "/PluginActivity.apk";
loader = new DexClassLoader(dexPath, MainActivity.this.getApplicationInfo().dataDir, null, getClass().getClassLoader());
Class<!--?--> activity = null;
Class<!--?--> layout = null;
try {
activity = loader.loadClass("plugin.dl.pluginactivity.MainActivity");
layout = loader.loadClass("plugin.dl.pluginactivity.R$layout");
}catch (ClassNotFoundException e) {
Log.i("MainActivity", "ClassNotFoundException");
}
replaceClassLoader(loader);
loadRes(dexPath);
try {
Field field = layout.getField("activity_main");
Integer obj = (Integer) field.get(null);
// 使用包含插件APK的Resources对象来获取这个布局才能正确获取插件中定义的界面效果
View view = LayoutInflater.from(MainActivity.this).inflate(resources.getLayout(obj),null);
// 或者这样,但一定要重写getResources方法,才能这样写
// View view = LayoutInflater.from(MainActivity.this).inflate(obj, null);
Method method = activity.getDeclaredMethod("setLayout", View.class);
method.setAccessible(true);
method.invoke(activity, view);
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent(MainActivity.this, activity);
MainActivity.this.startActivity(intent);
}
});
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MainActivity2.class);
MainActivity.this.startActivity(intent);
}
});
}
public void loadRes(String path){
try {
assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, path);
} catch (Exception e) {
}
resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
// 也可以根据资源获取主题
}
private void replaceClassLoader(DexClassLoader loader){
try {
Class clazz_Ath = Class.forName("android.app.ActivityThread");
Class clazz_LApk = Class.forName("android.app.LoadedApk");
Object currentActivityThread = clazz_Ath.getMethod("currentActivityThread").invoke(null);
Field field1 = clazz_Ath.getDeclaredField("mPackages");
field1.setAccessible(true);
Map mPackages = (Map)field1.get(currentActivityThread);
String packageName = MainActivity.this.getPackageName();
WeakReference ref = (WeakReference) mPackages.get(packageName);
Field field2 = clazz_LApk.getDeclaredField("mClassLoader");
field2.setAccessible(true);
field2.set(ref.get(), loader);
} catch (Exception e){
System.out.println("-------------------------------------" + "click");
e.printStackTrace();
}
}
@Override
public Resources getResources() {
return resources == null ? super.getResources() : resources;
}
@Override
public AssetManager getAssets() {
return assetManager == null ? super.getAssets() : assetManager;
}
}

动态加载Activity:使用代理

还有一种方式启动插件中的activity的方式就是将插件中的activity当做一个一般的类,不把它当成组件activity,于是在启动的时候启动一个代理ProxyActivity,它才是真正的Activity,他的生命周期由系统管理,我们在它里面调用插件Activity里的函数即可。同时,在插件Activity里面保存一个代理Activity的引用,把这个引用当做上下文环境Context理解。

这里插件Activity的生命周期函数均由代理Activity调起,ProxyActivity其实就是一个真正的我们启动的Activity,而不是启动插件中的Activity,插件中的“要启动”的Activity就当做一个很普通的类看待,当成一个包含了一些函数的普通类来理解,只是这个类里面的函数名字起的有些“奇怪”罢了。涉及到访问资源和更新UI相关的时候通过当前上下文环境,即保存的proxyActivity引用来获取。

以下面这个Demo为例

宿主项目

com.dl.host

|--MainActivity.java

|--ProxyActivity.java

MainActivity包括一个按钮,按下按钮跳转到插件Activity

public class MainActivity extends Activity{
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.startActivity(new Intent(MainActivity.this, ProxyActivity.class));
}
});
}
}

ProxyActivity就是我们要启动的插件Activity的一个傀儡,代理。是系统维护的Activity。

public class ProxyActivity extends Activity{
private DexClassLoader loader;
private Activity activity;
private Class<!--?--> clazz = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loader = new DexClassLoader("/Plugin.apk", getApplicationInfo().dataDir, null, getClass().getClassLoader());
try {
clazz = loader.loadClass("com.dl.plugin.MainActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 设置插件activity的代理
try {
Method setProxy = clazz.getDeclaredMethod("setProxy", Activity.class);
setProxy.setAccessible(true);
activity = (Activity)clazz.newInstance();
setProxy.invoke(activity, this);
Method onCreate = clazz.getDeclaredMethod("onCreate", Bundle.class);
onCreate.setAccessible(true);
onCreate.invoke(activity, savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
// 调用插件activity的onStart方法
Method onStart = null;
try {
onStart = clazz.getDeclaredMethod("onStart");
onStart.setAccessible(true);
onStart.invoke(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onStart();
// 调用插件activity的onDestroy方法
Method onDestroy = null;
try {
onDestroy = clazz.getDeclaredMethod("onDestroy");
onDestroy.setAccessible(true);
onDestroy.invoke(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}

可以看到,ProxyActivity其实就是一个真正的Activity,我们启动的就是这个Activity,而不是插件中的Activity。

插件项目

com.dl.plugin

|--MainActivity.java

保存了一个代理Activity的引用,值得注意的是,由于访问插件中的资源需要额外的操作,要加载资源,因此这里未使用插件项目里面的资源,所以我使用代码添加的TextView,但原理和前面讲的内容是一样的。

public class MainActivity extends Activity {
private Activity proxyActivity;
public void setProxy(Activity proxyActivity) {
this.proxyActivity = proxyActivity;
}
// 里面的所有操作都由代理activity来操作
@Override
protected void onCreate(Bundle savedInstanceState) {
TextView tv = new TextView(proxyActivity);
tv.setText("插件Activity");
proxyActivity.setContentView(tv,new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
@Override
protected void onStart() {
Toast.makeText(proxyActivity, "插件onStart", 0).show();
}
@Override
protected void onDestroy() {
Toast.makeText(proxyActivity, "插件onDestroy", 0).show();
}
}

这种方法相比较前面修改系统加载器的方法需要自己维护生命周期,比较麻烦,前一种方式由系统自己维护,并且启动的就是插件中实实在在的Activity。

前一种方式要在宿主的AndroidManifest里面声明插件Activity,这样当activity太多时就要声明很多,比较繁琐,不过也可以不声明逃过系统检查。后面这种方式就只需要一个代理ProxyActivity类即可。在他的onCreate里面根据传递的值选择加载插件中的哪个Activity即可。

(0)

相关推荐

  • Android 布局文件Layout XML属性

    Layout对于迅速的搭建界面和提高界面在不同分辨率的屏幕上的适应性具有很大的作用.这里简要介绍Android的Layout和研究 一下它的实现. Android有Layout:FrameLayout,LinearLayout,TableLayout,RelativeLayout,AbsoluteLayout. 放入Layout中进行排布的View的XML属性: 几种Layout中Item所共有的XML属性:  (1)layout_width  (2)layout_height 注: (1)和(

  • Android应用开发中Fragment的静态加载与动态加载实例

    1.Fragment的静态使用 Fragment是作为Activity的UI的一部分,它内嵌在Activity中,多个Fragment可以把一个Activity分成多个部分,这在大屏幕手机或者平板电脑中会比较多的用到,这样就不用使用多个Activity来切换这么麻烦了.当然Fragment也可以不显示,只在后台处理一些数据,这篇文章中就暂时不谈到这个.以下来看怎么静态地在Activity的布局文件中添加Fragment. 自定义的Fragment通常要继承Fragment这个类,也有一些特殊的是

  • Android 中动态加载.jar的实现步骤

    首先第一个是 jar 文件的制作,Java 里面直接把 .class 文件打包到 .jar 文件里面就可以了,但是 Android 的 Dalvik VM 是不认 Java 的 byte code 的,所以不能直接这么打包,而要用 dx 工具转成 Dalvik byte code 才可以.当然,dx 工具转了之后,jar 包里面就不 是 .class 文件了,而是 .dex 文件. 第二个是,Android 里面虽然也提供了 URLClassLoader 的实现,但是并不能用.要动态加载其它类,

  • Android编程动态加载布局实例详解【附demo源码】

    本文实例讲述了Android编程动态加载布局的方法.分享给大家供大家参考,具体如下: 由于前段时间项目需要,需要在一个页面上加载根据不同的按钮加载不同的布局页面,当时想到用 tabhot .不过美工提供的界面图完全用不上tabhot ,所以想到了动态加载的方法来解决这一需求.在这里我整理了一下,写了一个 DEMO 希望大家以后少走点弯路. 首先,我们先把界面的框架图画出来,示意图如下: 中间白色部门是一个线性布局文件,我喜欢在画图的时候用不同的颜色将一块布局标示出来,方便查看.布局文件代码如下:

  • android layout XML解析错误的解决方法

    提示: org.xmlpull.v1.XmlPullParserException: PI must not start with xml (position:unknown @1:5 in java.io.InputStreamReader@47ec2770) org.xml.sax.SAXParseException: PI must not start with xml (position:unknown xm@3:5 in java.io.InputStreamReader@43e

  • Android在layout xml中使用ViewStub完成动态加载问题

    一.Layout XML文件常见的两种模块加载方式 1.静态加载:被加载的模块和其它模块加载的时间一样. <include layout="@layout/otherLayout"/> 2.动态加载:需要被加载的模块初始时并没有被加载进内存,在你需要加载这个模块才会被动态的加载进去. <ViewStub android:layout="@layout/otherLayout"/> 还要把layout width和height加上 二.View

  • Android实现Listview异步加载网络图片并动态更新的方法

    本文实例讲述了Android实现Listview异步加载网络图片并动态更新的方法.分享给大家供大家参考,具体如下: 应用实例:解析后台返回的数据,把每条都显示在ListView中,包括活动图片.店名.活动详情.地址.电话和距离等. 在布局文件中ListView的定义: <ListView android:id="@id/maplistview" android:background="@drawable/bg" android:layout_width=&qu

  • android动态加载布局文件示例

    一.布局文件part.xml: 复制代码 代码如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="

  • Android动态加载Activity原理详解

    activity的启动流程 加载一个Activity肯定不会像加载一般的类那样,因为activity作为系统的组件有自己的生命周期,有系统的很多回调控制,所以自定义一个DexClassLoader类加载器来加载插件中的Activity肯定是不可以的. 首先不得不了解一下activity的启动流程,当然只是简单的看一下,太详细的话很难研究清楚. 通过startActivity启动后,最终通过AMS进行跨进程回调到ApplicationThread的scheduleLaunchActivity,这时

  • Java代码块与代码加载顺序原理详解

    这篇文章主要介绍了Java代码块与代码加载顺序原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本文首先介绍几个基本的名次,然后介绍了三种代码块的特性和使用方法. 在面试大型公司时,如果遇到大型国企或者大的互联网私企,笔试中经常遇到代码块和代码加载顺序的笔试题.这里做一个总结,也方便各位小伙伴飙车不会飘. 名词解释 代码块 由 { } 包起来的代码,称为代码块 静态代码块 由 static { } 包起来的代码,称为静态代码块. 不同类型

  • PHP+jQuery实现滚屏无刷新动态加载数据功能详解

    本文实例讲述了PHP+jQuery实现滚屏无刷新动态加载数据功能.分享给大家供大家参考,具体如下: index.php <?php require_once('connect.php'); //连接数据库 $user = array('demo1','demo2','demo3','demo3','<de></de>mo4'); //模拟了几个用户 ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Trans

  • Java动态加载类示例详解

    在讲解动态加载类之前呢,我们先弄清楚为什么要动态加载类,静态加载不行吗?我们可以看下面的实例: 我在文件夹里写了Office.java 类和 Word.java类,如下: Office.java class Office{ public static void main(String[] args){ if(args[0].equals("Word")){ Word w = new Word(); w.start(); } if(args[0].equals("Excel&q

  • SpringCache框架加载/拦截原理详解

    官网文档 背景 项目A中需要多数据源的实现,比如UserDao.getAllUserList() 需要从readonly库中读取,但是UserDao.insert() 需要插入主(写)库 就需要在dao层的方法调用上面添加注解! 了解后知道-接口通过jdk代理(mybatis的mapper接口就是通过jdk代理动态生成的-> MapperFactoryBean.class )的,没办法被aop的拦截(注解配置的拦截) //dao @Pointcut("@annotation(com.kao

  • Android 动态加载 so实现示例详解

    目录 背景 so动态加载介绍 从一个例子出发 so库检索与删除 动态加载so 结束了吗? ELF文件 扩展 总结 背景 对于一个普通的android应用来说,so库的占比通常都是巨高不下的,因为我们无可避免的在开发中遇到各种各样需要用到native的需求,所以so库的动态化可以减少极大的包体积,自从2020腾讯的bugly团队发部关于动态化so的相关文章后,已经过去两年了,相关文章,经过两年的考验,实际上so动态加载也是非常成熟的一项技术了. 但是很遗憾,许多公司都还没有这方面的涉略又或者说不知

  • Android性能优化之RecyclerView分页加载组件功能详解

    目录 引言 1 分页加载组件 1.1 功能定制 1.2 手写分页列表 1.3 生命周期管理 2 github 引言 在Android应用中,列表有着举足轻重的地位,几乎所有的应用都有列表的身影,但是对于列表的交互体验一直是一个大问题.在性能比较好的设备上,列表滑动几乎看不出任何卡顿,但是放在低端机上,卡顿会比较明显,而且列表中经常会伴随图片的加载,卡顿会更加严重,因此本章从手写分页加载组件入手,并对列表卡顿做出对应的优化 1 分页加载组件 为什么要分页加载,通常列表数据存储在服务端会超过100条

  • mui上拉加载功能实例详解

    最近在做移动端的项目,用到了mui的上拉加载,整理如下: 1.需要引入的css.js <link rel="stylesheet" href="common/mui/css/mui.min.css" rel="external nofollow" > <script src="js/jquery-3.2.0.min.js"></script> <script src="com

  • JDBC数据库连接过程及驱动加载与设计模式详解

    首先要导入JDBC的jar包: 接下来,代码: Class.forName(xxx.xx.xx)返回的是一个类 Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段. JDBC连接数据库 • 创建一个以JDBC连接数据库的程序,包含7个步骤: 1.加载JDBC驱动程序: 在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String

随机推荐