android 为什么使用android 加载机制制

Android资源加载机制 - 简书
Android资源加载机制
获取资源的方式
先通过Context.getResources();获取Resources对象,有了Resources对象就可以访问各种资源了。
资源加载机制
通过Context获取Resources,实际是通过ContextImpl的getResources()方法;ContextImpl内部有一个成员mResources,它就是getResources()方法返回的结果;
Context.getResources();
ContextImpl
private Resources mResources
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
ResourcesManager.getTopLevelResources()思想:在ResourcesManager中,所有的资源对象都被存储在ArrayMap中,首先根据当前的请求参数去查找资源,如果找到了就返回,否则就创建一个资源对象放到ArrayMap中。
public Resources getTopLevelResources(String resDir, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
final float scale = compatInfo.applicationS
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
synchronized (this) {
// Resources is app scale dependent.
if (false) {
Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
WeakReference&Resources& wr = mActiveResources.get(key);
r = wr != null ? wr.get() :
//if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (false) {
Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale);
//if (r != null) {
Slog.w(TAG, "Throwing away out-of-date resources!!!! "
+ r + " " + resDir);
AssetManager assets = new AssetManager();
if (assets.addAssetPath(resDir) == 0) {
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration());
if (!isDefaultDisplay) {
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
config = getConfiguration();
r = new Resources(assets, dm, config, compatInfo, token);
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
+ r.getCompatibilityInfo().applicationScale);
synchronized (this) {
WeakReference&Resources& wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() :
if (existing != null && existing.getAssets().isUpToDate()) {
// Someone else already created the resources while we were
// go ahead and use theirs.
r.getAssets().close();
// XXX need to remove entries when weak references go away
mActiveResources.put(key, new WeakReference&Resources&(r));
为什么会有多个资源对象?因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。在同一应用中不同的ContextImpl获取到的是同一套资源根据上述代码中(ResourcesManager.getTopLevelResources())资源的请求机制,再加上ResourcesManager采用单例模式,这样就保证了不同的ContextImpl访问的是同一套资源;尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例;所以它们看似不同的mResources其实都指向的是同一块内存
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
return sResourcesM
Resources对象的创建过程
构造方法:简单起见,我们应该采用第一个public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)它接受3个参数,第一个是AssetManager,后面两个是和设备相关的配置参数
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
CompatibilityInfo compatInfo, IBinder token) {
mMetrics.setToDefaults();
if (compatInfo != null) {
mCompatibilityInfo = compatI
mToken = new WeakReference&IBinder&(token);
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
所以创建Resources的关键就是创建AssetManager系统创建AssetManager的方法;
ResourcesManager.getTopLevelResources()
AssetManager assets = new AssetManager();
if (assets.addAssetPath(resDir) == 0) {
assets.addAssetPath(resDir)这句话的意思是把资源目录里的资源都加载到AssetManager对象中;但是addAssetPath方法是{@hide},这意味着即使它是public的,但是外界仍然无法访问它,因为android sdk导出的时候会自动忽略隐藏的api;
public final int addAssetPath(String path) {
int res = addAssetPathNative(path);
所以我们要通过反射才能调用 addAssetPath方法;
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, mDexPath);
mAssetManager = assetM
} catch (Exception e) {
e.printStackTrace();
Resources currentRes = this.getResources();
mResources = new Resources(mAssetManager, currentRes.getDisplayMetrics(),
currentRes.getConfiguration());Android中的动态加载机制
在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。本文对网上动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病。
Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求。但是有些特殊问题,常常引发我们进一步的沉思。我们从沉思中产生顿悟,从而产生新的技术形式。
如何开发一个可以自定义控件的Android应用?就像eclipse一样,可以动态加载插件;如何让Android应用执行服务器上的不可预知的代码?如何对Android应用,而只在执行时自解密,从而防止被?……
熟悉技术的朋友,可能意识到,我们需要使用类加载器灵活的加载执行的类。这在Java里已经算是一项比较成熟的技术了,但是在Android中,我们大多数人都还非常陌生。
类加载机制
Dalvik如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流,因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的
然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,它们有相同的地方,也有不同之处。我们必须区别对待
例如,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,大家就没必要走弯路了。参看我们知道,Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。而这个本地方法除了抛出一个“UnsupportedOperationException”之外,什么都没做,甚至连返回值都为空
static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args,JValue* pResult){
Object* loader = (Object*) args[0];
StringObject* nameObj = (StringObject*) args[1];
const u1* data = (const u1*) args[2];
int offset = args[3];
int len = args[4];
Object* pd = (Object*) args[5];
char* name = NULL;
name = dvmCreateCstrFromString(nameObj);
LOGE("ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n",loader, name, data, offset, len, pd);
dvmThrowException("Ljava/lang/UnsupportedOperationE","can't load this type of class file");
free(name);
RETURN_VOID();
Dalvik虚拟机类加载机制
那如果在Dalvik虚拟机里,ClassLoader不好使,我们如何实现动态加载类呢?Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader。其中需要特别说明的是PathClassLoader中一段被注释掉的代码:
/* --this doesn't work in current version of Dalvik--
if (data != null) {
System.out.println("--- Found class " + name
+ " in zip[" + i + "] '" + mZips[i].getName() + "'");
int dotIndex = name.lastIndexOf('.');
if (dotIndex != -1) {
String packageName = name.substring(0, dotIndex);
synchronized (this) {
Package packageObj = getPackage(packageName);
if (packageObj == null) {
definePackage(packageName, null, null,
null, null, null, null, null);
return defineClass(name, data, 0, data.length);
这从另一方面佐证了defineClass函数在Dalvik虚拟机里确实是被阉割了。而在这两个继承自ClassLoader的类加载器,本质上是重载了ClassLoader的findClass方法。在执行loadClass时,我们可以参看ClassLoader部分源码:
protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class clazz = findLoadedClass(className);
if (clazz == null) {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
// Don't want to see this.
if (clazz == null) {
clazz = findClass(className);
因此DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass方法)。也就是说,它们在加载一个类之前,回去检查自己以及自己以上的类加载器是否已经加载了这个类。如果已经加载过了,就会直接将之返回,而不会重复加载。
DexClassLoader和PathClassLoader其实都是通过DexFile这个类来实现类加载的。这里需要顺便提一下的是,Dalvik虚拟机识别的是dex文件,而不是class文件。因此,我们供类加载的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。
也许有人想到,既然DexFile可以直接加载类,那么我们为什么还要使用ClassLoader的子类呢?DexFile在加载类时,具体是调用成员方法loadClass或者loadClassBinaryName。其中loadClassBinaryName需要将包含包名的类名中的”.”转换为”/”我们看一下loadClass代码就清楚了:
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader);
在这段代码前有一段注释,截取关键一部分就是说:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 这就是我们需要使用ClassLoader子类的原因。至于它是如何验证是否是在ClassLoader中调用此方法的,我没有研究,大家如果有兴趣可以继续深入下去。
有一个细节,可能大家不容易注意到。PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path, outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。
另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”
使用到的工具都比较常规:javac、dx、eclipse等其中dx工具最好是指明--no-strict,因为class文件的路径可能不匹配
加载好类后,通常我们可以通过Java反射机制来使用这个类但是这样效率相对不高,而且老用反射代码也比较复杂凌乱。更好的做法是定义一个interface,并将这个interface写进容器端。待加载的类,继承自这个interface,并且有一个参数为空的构造函数,以使我们能够通过Class的newInstance方法产生对象然后将对象强制转换为interface对象,于是就可以直接调用成员方法了,下面是具体的实现步骤了:
编写好动态代码类:
package com.dynamic.
import android.app.A
* 动态加载类的接口
public interface IDynamic {
/**初始化方法*/
public void init(Activity activity);
/**自定义方法*/
public void showBanner();
public void showDialog();
public void showFullScreen();
public void showAppWall();
/**销毁方法*/
public void destory();
实现类代码如下:
package com.dynamic.
import android.app.A
import android.widget.T
import com.dynamic.interfaces.ID
* 动态类的实现
public class Dynamic implements IDynamic{
private Activity mA
public void init(Activity activity) {
mActivity =
public void showBanner() {
Toast.makeText(mActivity, "我是ShowBannber方法", 1500).show();
public void showDialog() {
Toast.makeText(mActivity, "我是ShowDialog方法", 1500).show();
public void showFullScreen() {
Toast.makeText(mActivity, "我是ShowFullScreen方法", 1500).show();
public void showAppWall() {
Toast.makeText(mActivity, "我是ShowAppWall方法", 1500).show();
public void destory() {
这样动态类就开发好了
将上面开发好的动态类打包成.jar,这里要注意的是只打包实现类Dynamic.java,不打包接口类IDynamic.java,
然后将打包好的jar文件拷贝到android的安装目录中的platfZ喎"/kf/ware/vc/" target="_blank" class="keylink">vcm0tdG9vbHPEv8K8z8I8L3N0cm9uZz6jrMq508NkeMP8we46KM7StcRqYXLOxLz+ysdkeW5hbWljLmphcik8L3A+CjxwPjxzdHJvbmc+ZHggLS1kZXggLS1vdXRwdXQ9ZHluYW1pY190ZW1wLmphciBkeW5hbWljLmphcjwvc3Ryb25nPjwvcD4KPHA+1eLR+b7NyfqzycHLZHluYW1pY190ZW1wLmphcqOs1eK49mphcrrNZHluYW1pYy5qYXLT0Mqyw7TH+LHwxNijvzwvcD4KPHA+xuTKtdXizPXD/MHu1vfSqtf2tcS5pNf3ysc6ytfPyL2rZHluYW1pYy5qYXKx4NLrs8lkeW5hbWljLmRleM7EvP4oQW5kcm9pZNDpxOK7+sjPyra1xNfWvdrC687EvP4po6zIu7rz1Nm9q2R5bmFtaWMuZGV4zsS8/tG5y/WzyWR5bmFtaWNfdGVtcC5qYXKjrLWxyLvE49Kyv8nS1NG5y/WzyS56aXAmIzI2Njg0O8q9tcSjrLvy1d/Wsb3TseDS67PJLmFwa87EvP62vL/J0tS1xKOs1eK49rrzw+a74cu1tb2hozwvcD4KPHA+tb3V4sDvu7myu8vjzerKwqOs0vLOqsTjz+vP69PDyrLDtMC0way907avzKzA4LrNxL+x6sDgxNijv8THvs3Kx7avzKzA4LXEvdO/2sHLo6zL+dLU1eLKsbryu7nSqrTyuPYuamFysPyjrNXiyrG68ta70OjSqrTyvdO/2sDgSUR5bmFtaWMuamF2YcHLPC9wPgo8cD48aW1nIHNyYz0="/uploadfile/Collfiles/47.jpg" alt="\">
然后将这个.jar文件引用到目标类中,下面来看一下目标类的实现:
package com.jiangwei.
import java.io.F
import java.util.L
import android.app.A
import android.content.I
import android.content.pm.ActivityI
import android.content.pm.PackageM
import android.content.pm.ResolveI
import android.os.B
import android.os.E
import android.view.V
import android.widget.B
import android.widget.T
import com.dynamic.interfaces.ID
import dalvik.system.DexClassL
import dalvik.system.PathClassL
public class AndroidDynamicLoadClassActivity extends Activity {
//动态类加载接口
private ID
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button showBannerBtn = (Button) findViewById(R.id.show_banner_btn);
Button showDialogBtn = (Button) findViewById(R.id.show_dialog_btn);
Button showFullScreenBtn = (Button) findViewById(R.id.show_fullscreen_btn);
Button showAppWallBtn = (Button) findViewById(R.id.show_appwall_btn);
/**使用DexClassLoader方式加载类*/
//dex压缩文件的路径(可以是apk,jar,zip格式)
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";
//dex解压释放后的目录
//String dexOutputDir = getApplicationInfo().dataD
String dexOutputDirs = Environment.getExternalStorageDirectory().toString();
//定义DexClassLoader
//第一个参数:是dex压缩文件的路径
//第二个参数:是dex解压缩后存放的目录
//第三个参数:是C/C++依赖的本地库文件目录,可以为null
//第四个参数:是上一级的类加载器
DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());
/**使用PathClassLoader方法加载类*/
//创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的
Intent intent = new Intent("com.dynamic.impl", null);
//获得包管理器
PackageManager pm = getPackageManager();
List resolveinfoes =
pm.queryIntentActivities(intent, 0);
//获得指定的activity的信息
ActivityInfo actInfo = resolveinfoes.get(0).activityI
//获得apk的目录或者jar的目录
String apkPath = actInfo.applicationInfo.sourceD
//native代码的目录
String libPath = actInfo.applicationInfo.nativeLibraryD
//创建类加载器,把dex加载到虚拟机中
//第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取
//第二个参数:是C/C++依赖的本地库文件目录,可以为null
//第三个参数:是上一级的类加载器
PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());
//com.dynamic.impl.Dynamic是动态类名
//使用DexClassLoader加载类
//Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
//使用PathClassLoader加载类
Class libProviderClazz = pcl.loadClass("com.dynamic.impl.Dynamic");
lib = (IDynamic)libProviderClazz.newInstance();
if(lib != null){
lib.init(AndroidDynamicLoadClassActivity.this);
} catch (Exception exception) {
exception.printStackTrace();
/**下面分别调用动态类中的方法*/
showBannerBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if(lib != null){
lib.showBanner();
Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
showDialogBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if(lib != null){
lib.showDialog();
Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
showFullScreenBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if(lib != null){
lib.showFullScreen();
Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
showAppWallBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if(lib != null){
lib.showAppWall();
Toast.makeText(getApplicationContext(), "类加载失败", 1500).show();
}这里面定义了一个IDynamic接口变量,同时使用了DexClassLoader和PathClassLoader来加载类,这里面先来说一说DexClassLoader方式加载:
//定义DexClassLoader
//第一个参数:是dex压缩文件的路径
//第二个参数:是dex解压缩后存放的目录
//第三个参数:是C/C++依赖的本地库文件目录,可以为null
//第四个参数:是上一级的类加载器
DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());上面已经说了,DexClassLoader是继承ClassLoader类的,这里面的参数说明:
第一个参数是:dex压缩文件的路径:这个就是我们将上面编译后的dynamic_temp.jar存放的目录,当然也可以是.zip和.apk格式的
第二个参数是:dex解压后存放的目录:这个就是将.jar,.zip,.apk文件解压出的dex文件存放的目录,这个就和PathClassLoader方法有区别了,同时你也可以看到PathClassLoader方法中没有这个参数,这个也真是这两个类的区别:
PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在手机的data/dalvik目录中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。
然而我们可以通过DexClassLoader方法指定解压后的dex文件的存放目录,但是我们一般不这么做,因为这样做无疑的暴露了dex文件,所以我们一般不会将.jar/.zip/.apk压缩文件存放到用户可以察觉到的位置,同时解压dex的目录也是不能让用户看到的。
第三个参数和第四个参数用到的不是很多,所以这里就不做太多的解释了。
这里还要注意一点就是PathClassLoader方法的时候,第一个参数是dex存放的路径,这里传递的是:
//获得apk的目录或者jar的目录
String apkPath = actInfo.applicationInfo.sourceD
指定的apk安装路径,这个值只能这样获取,不然会加载类失败的
运行目标类:
要做的工作是:
如果用的是DexClassLoader方式加载类:这时候需要将.jar或者.zip或者.apk文件放到指定的目录中,我这里为了方便就放到sd卡的根目录中
如果用的是PathClassLoader方法加载类:这时候需要先将Dynamic.apk安装到手机中,不然找不到这个activity,同时要注意的是:
//创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的
Intent intent = new Intent("com.dynamic.impl", null);
这里的com.dynamic.impl是一个action需要在指定的apk中定义,这个名称是动态apk和目标apk之间约定好的
点击showBanner显示一个Toast,成功的运行了动态类中的代码!
其实更好的办法就是将动态的.jar.zip.apk文件从网络上获取,安全可靠,同时本地的目标项目不需要改动代码就可以执行不同的逻辑了
关于代码加密的一些设想
最初设想将dex文件加密,然后通过JNI将解密代码写在Native层。解密之后直接传上二进制流,再通过defineClass将类加载到内存中。
现在也可以这样做,但是由于不能直接使用defineClass,而必须传文件路径给dalvik虚拟机内核,因此解密后的文件需要写到磁盘上,增加了被破解的风险。
Dalvik虚拟机内核仅支持从dex文件加载类的方式是不灵活的,由于没有非常深入的研究内核,我不能确定是Dalvik虚拟机本身不支持还是Android在移植时将其阉割了。不过相信Dalvik或者是Android开源项目都正在向能够支持raw数据定义类方向努力。
我们可以在文档中看到Google说:Jar or APK file with "classes.dex". (May expand this to include "raw DEX" in the future.);在Android的Dalvik源码中我们也能看到RawDexFile的身影(不过没有具体实现)
在RawDexFile出来之前,我们都只能使用这种存在一定风险的加密方式。需要注意释放的dex文件路径及权限管理,另外,在加载完毕类之后,除非出于其他目的否则应该马上删除临时的解密文件您所在的位置: &
Android内存机制分析下篇:分析APP内存使用情况
Android内存机制分析下篇:分析APP内存使用情况
本文主要是分析了如何获取我们应用的内存使用情况信息,关于这方面的信息,其实还有其他一些方法。另外还介绍APP应用的默认内存已经Android的GC回收,不过上面只是很浅薄地分析了一下,让大家有个印象。这些东西真要深入分析得花不少精力。因为我们的目的只是解决OOM问题,所以目前没打算深入分析,后面有时间进行Android系统分析的时候,我们再深入分析。
上面一篇文章说了Android应用运行在dalvik里面分配的堆和栈内存区别,以及程序中什么代码会在哪里运行。今天主要是讲解一下Android里面如何分析我们程序内存使用情况。以便后续可以分析我们程序瓶颈,优化方案。
1、APP默认分配内存大小
在Android里,程序内存被分为2部分:native和dalvik,dalvik就是我们普通的java使用内存,也就是我们上一篇文章分析堆栈的时候使用的内存。我们创建的对象是在这里面分配的,对于内存的限制是
native+dalvik
不能超过最大限制。android程序内存一般限制在16M,也有的是24M(早期的Android系统G1,就是只有16M)。具体看定制系统的设置,在Linux初始化代码里面Init.c,可以查到到默认的内存大小。有兴趣的朋友,可以分析一下虚拟机启动相关代码。这块比较深入,目前我也没时间去分析,后面有空会去钻研一下。
gDvm.heapSizeStart&=&2&*&1024&*&1024;&&&&gDvm.heapSizeMax&=&16&*&1024&*&1024;&&&&
2、Android的GC如何回收内存
Android的一个应用程序的内存泄露对别的应用程序影响不大。为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的。Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程(如果是system_process等系统进程出问题的话,则会引起系统重启)。
做应用开发的时候,你需要了解系统的GC(垃圾回收)机制是如何运行的,Android里面使用有向图作为遍历回收内存的机制。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象&(连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
因此对于我们已经不需要使用的对象,我们可以把它设置为null,这样当GC运行的时候,就好遍历到你这个对象已经没有引用,会自动把该对象占用的内存回收。我们没法像C++那样马上释放不需要的内存,但是我们可以主动告诉系统,哪些内存可以回收了。
3、查看应用内存使用情况
下面我们看看如何在开发过程中查看我们程序运行时内存使用情况。我们可以通过ADB的一个命令查看:
&&adb&shell&dumpsys&meminfo&$package_name&or&$pid&
498)this.width=498;' onmousewheel = 'javascript:return big(this)' width="480" height="494" alt="" src="/wyfs01/M00/11/15/wKioJlHtudCTRlV5AADYTsAVkz0429.jpg" />
上面是我使用包名查看Gallery例子的内存使用情况图,里面信息很多,不过我们主要关注的是native和Davilk的使用情况。(Android2.X和Android4.X查看的信息排序是不一样的,内容差不多,不过排布有差异,我上面是4.0的截图)
Android底层内核是基于Linux的,而Linux里面相对Window来说,有一点很特别的是,会尽量使用系统内存加载一些缓存数据或者进程间共享数据。Linux本着不用白不用的原则,会尽量使用系统内存,加快我们应用的运行速度。当然,如果我们期待某个需要大内存的应用,系统也能马上释放出一定的内存使用,这是系统内部调度实现。因此严格来说,我们要准备计算Linux下某个进程内存大小比较困难。&因为有paging
out to disk(换页),所以如果你把所有映射到进程的内存相加,它可能大于你的内存的实际物理大小。
dalvik:是指dalvik所使用的内存。
native:是被native堆使用的内存。应该指使用C\C++在堆上分配的内存。
other:是指除dalvik和native使用的内存。但是具体是指什么呢?至少包括在C\C++分配的非堆内存,比如分配在栈上的内存。puzlle!
Pss:它是把共享内存根据一定比例分摊到共享它的各个进程来计算所得到进程使用内存。网上又说是比例分配共享库占用的内存,也就是上面所说的进程共享问题。
PrivateDirty:它是指非共享的,又不能换页出去(can not be
paged to disk
)的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使你的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
SharedDirty:参照PrivateDirty我认为它应该是指共享的,又不能换页出去(can
not be paged to disk
)的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使所有共享它的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
上面针对meminfo里面的信息给出解析,这些很多我是参考了网上一些文章,所以如果有理解不到位的,欢迎各位指出。
4、程序中获取内存信息
通过ActivityManager获取相关信息,下面是一个例子代码:
private&void&displayBriefMemory()&&{&&&&&&&&&final&ActivityManager&activityManager&=&(ActivityManager)&getSystemService(ACTIVITY_SERVICE);&&&&&&&&&ActivityManager.MemoryInfo&info&=&new&ActivityManager.MemoryInfo();&&&&&&&&activityManager.getMemoryInfo(info);&&&&&&&&&&&Log.i(tag,&系统剩余内存:&+(info.availMem&&&&10)+&k&);&&&&&&&&Log.i(tag,&系统是否处于低内存运行:&+info.lowMemory);&&&&&Log.i(tag,&当系统剩余内存低于&+info.threshold+&时就看成低内存运行&);&}&
另外通过Debug的getMemoryInfo(Debug.MemoryInfo
memoryInfo)可以得到更加详细的信息。跟我们在ADB Shell看到的信息一样比较详细。
今天主要是分析了如何获取我们应用的内存使用情况信息,关于这方面的信息,其实还有其他一些方法。另外还介绍APP应用的默认内存已经Android的GC回收,不过上面只是很浅薄地分析了一下,让大家有个印象。这些东西真要深入分析得花不少精力。因为我们的目的只是解决OOM问题,所以目前没打算深入分析,后面有时间进行Android系统分析的时候,我们再深入分析。下一次我们用以前写的Gallery例子讲解如何避免OOM问题,以及内存优化方法。
PS:如发现哪里写错了,烦请各位留言指出,谢谢!
相关文章:
【编辑推荐】【责任编辑: TEL:(010)】
关于&&&&的更多文章
Android 4.4即将发布,这对大家来说都是很期待的,当然,无论是
随着秋的到来,各路才子佳人渐渐开始回到学校上课。不
百度推出轻应用引起业界火热议论,收购91和推出轻应用
App搜索对应用的影响不容忽视,目前应用商店仍采用最
该书为C#经典名著!是Wrox红皮书中最畅销的品种之一。从第1版开始就名满天下;其第3版被评选为2005年最权威的十大IT图书之一;并
Windows Phone专家
Android开发专家
51CTO旗下网站

我要回帖

更多关于 android 动态加载机制 的文章

 

随机推荐