由于Android应用的沙箱机制,每个应用所分配的内存大小是有限度的,因此内存会显得非常珍贵,如果我们的内存占用超过了一定的水平就会出现OutOfMemory错误
内存概述
RAM(random access memory)随机存取存储器.(通俗的说就是内存)
- Java的内存分配策略:
Java内存分配时会涉及到以下区域:
栈(Stack):一些基本类型的变量和对象的引用都是在栈内存中分配,当超过变量的作用域后,java会自动释放该变量分配的内存(对象本身不存放在栈中,而是存放在堆中)
堆(Heap): 通常用来存放new出来的对象和数组,由java垃圾回收器回收.
静态存储区(static field): 编译时就分配好,在程序整个运行期间都存在.它主要存放静态数据和常量
还一个CPU存储区:
寄存器(Registers): 速度最快的存储场所,因为寄存器位于处理器内部,我们在程序中无法控制
- 堆栈的特点:
栈:
定义一个变量时,Java在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放为该变量所分配的内存空间.
栈的存取速度比堆要快,仅次于寄存器.但是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性
栈中的数据可以共享,它是由编译器完成的,有利于节省空间
例如:需要定义两个变量int a = 3;int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没有,就将3存放进来再将a指向3.
接着处理int b = 3,创建完b的引用变量后在栈中已经有3这个值,便将b直接指向3.这样,就出现了a与b同时均指向3的情况.
这时,如果再让a=4,那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并让a指向4.
如果已经有了,则直接将a指向这个地址.因此a值的改变不会影响到b的值。
堆:
当堆中通过new产生数组和对象超出其作用域后,它们不会被释放,只有在没有引用变量指向它们的时候才变成垃圾,不能再被使用,并且只有等被垃圾回收器回收才回释放内存.这也是Java比较占内存的原因.
堆是一个运行时数据区,可以动态地分配内存大小,因此存取速度较慢.
如上例子,栈中a的修改并不会影响到b,而在堆中一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量
- APP内存占用信息查询
float max = Runtime.getRuntime().maxMemory() * 1.0f / (1024 * 1024);
float total = Runtime.getRuntime().totalMemory() * 1.0f / (1024 * 1024);
float free = Runtime.getRuntime().freeMemory() * 1.0f / (1024 * 1024);
查看系统设置单个进程的内存上限
C:\Users\Administrator>adb shell
sagit:/ $ getprop|grep heapgrowthlimit
[dalvik.vm.heapgrowthlimit]: [256m]
- java中四种引用类型:
强引用(StrongReference):强引用是使用最普遍的引用(如:Object object=new Object(),object就是一个强引用了),
如果一个对象具有强引用内存不足时,宁抛异常OOM导致程序终止也不回收,也就是JVM停止时才终止
软引用(SoftReference):如果内存空间不足时,才会被回收(当内存达到一个阀值,GC就会去回收它)
弱引用(WeakReference):不管当前内存空间是否足够,在GC 时都会回收
虚引用(PhantomReference):顾名思义,就是形同虚设,任何时候都可能被GC回收(已经不用)
image
软引用实例:
// 例子1:
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
public void add(String path) {
Bitmap bitmap = BitmapFactory.decodeFile(path); // 这里的bitmap属于强引用
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap); // 软引用的Bitmap对象
imageCache.put(path, softBitmap);
}
public Bitmap get(String path) {
SoftReference<Bitmap> softBitmap = imageCache.get(path);
if (softBitmap == null) {
return null;
}
return softBitmap.get(); // 取出软引用的Bitmap,如果内存不足被回收,获取为NUll
}
public static Bitmap readBitmap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
// 例子2:
static class MyHandler extends Handler {
private SoftReference<Activity> reference;
public MyHandler(Activity activity) {
// 持有 Activity 的软引用
reference = new SoftReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
// 处理消息
}
}
}
}
- 垃圾回收机制:
垃圾回收是指清理在内存中不再需要的数据对象,以便大块内存可以重新分配给新的对
象。一般来说,一旦某个对象在 App 中没有一个活动的引用,就可以作为垃圾被回收了。
垃圾回收器会先从根部的对象开始(它知道这些对象是活动的并且正被进程所使用),并
且沿着每个引用去查找它们的关联。如果一个对象不在这个有效引用的列表中,那么它肯
定不会再被使用,就可以被回收了。此时,分配给这个对象的内存空间也可以回收了
内存优化
-
内存泄漏
内存泄漏是内存优化中最重要的部分
-
IntentService的使用
IntentService是一种特殊的Service,继承自Service;用于在后台执行耗时的异步任务,当任务完成后会自动停止
为什么使用IntentService?
我们通常Service用法如下,也是标准用法:
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread() {
@Override
public void run() {
// 处理耗时逻辑
stopSelf(); // 如需实现处理完自动停止功能,可这样做
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
}
如上写法并没有什么错误,但是需要写如上额外代码,同时 当业务逻辑复杂后会有Service停止失败导致内存泄漏的风险,Android官方推荐的最佳解决方案就是使用IntentService
public class MyIntentService extends IntentService {
public MyIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 处理耗时逻辑,处理完自动停止
}
}
内部通过HandlerThread和Handler实现异步操作,创建IntentService时,只需实现onHandleIntent和构造方法.onHandleIntent为异步方法,可执行耗时操作.
-
Bitmap优化
Bitmap是内存消耗大户,是导致OMM最常见的原因之一
图片显示:
我们可以根据场景需求去加载图片的大小,例如列表中的小图我们可以只加载缩略图(thumbnails)
等比例压缩图片:
直接使用图片(bitmap)会占用较多资源,特别是图片较大的时候,可能导致崩溃,这时,我们可以使用BitmapFactory.Options设置inSampleSize.inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则获取图片的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4.
BitmapFactory.Options options = new BitmapFactory.Options();
// 该值设为true后将不返回实际的bitmap,也不给其分配内存空间.但允许我们查询图片的信息,计算出原始图片的长和宽
options.inJustDecodeBounds = true;
//缩放的倍数,图片宽高都为原来的二分之一,即图片为原来的四分之一,SDK中建议该值为2,值越大会导致图片不清晰
options.inSampleSize = 2;
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);
图片像素:
Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存
Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高.同时占用的内存也最大 所以对图片效果不是特别高的情况下可以使用RGB_565(565没有透明度属性)
public static Bitmap readBitmap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
图片回收:
使用Bitmap过后及时的调用Bitmap.recycle()方法来释放内存,不要等Android系统来进行释放
if (bitmap != null && !bitmap.isRecycled()) {
// 回收并且置为null
bitmap.recycle();
bitmap = null;
}
System.gc();
对图片采用软引用
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);
捕获异常:
最坏的情况下不能导致程序崩溃,捕获OOM异常
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
if (bitmap == null) {
return defaultBitmap;
}
相关链接直达:
Android APP性能优化之 ---- 优化监测工具(四)