在android开发过程中,无论是android那个方向的开发者,都需要对内存的优化有所了解,网上也有很多关于内存优化的方案,这里根据个人工作中总结的几个方面进行分析,什么是内存优化,如果进行优化.
内存优化总结起来就一句话:
防止app在运行过程中出现内存泄露和内存溢出以及因为应用频繁的GC导致的各种问题
先讲下概念
内存泄露
当一个对象在程序执行过后已经不需要再使用了,但是有其他的对象仍然持有该对象的引用,导致该对象不能被GC回收,那么这个对象会一直占据内存,从而导致该内存不可用,这种本该被GC回收的而又不能被回收导致停留在对内存中的对象就造成了内存泄露.
========常见的会导致出现内存泄露的例子:========
非静态内部类导致内存泄露:
过去经常有新手在Activity中直接使用handler进行线程通信
Handler handler = new Handler(){
@override
public void handlerMessage(Message msg){
super.handleMessage(msg);
}
}
还有错误创建线程
@override
protected void onCreate(Bundle saveInstanceState){
super.onCreate(saveInstanceState);
new Thread(new Runnable){
@override
public void run(){
}
}.start();
}
以handler为例:
这种创建的handler属于匿名内部类,其默认持有外部类的引用,当handler中消息出现延迟,而用户又离开了当前界面,导致当前界面开始进行回收,就会导致回收失败,出现内存泄露.
网上也提供了解决的方法,例如避免使用非静态内部类,继承handler时,要么将其放在单独的文件中,要么使用静态内部类
这里贴出本人项目中使用的全局handler,根据eventBus原理设计
自定义EventDispatcher继承handler,将其初始化方法设置为单例
public class EventDispatcher extends Handler {
private static EventDispatcher sInstance;
private EventListener mListener;
public static EventDispatcher getsInstance(EventListener listener) {
if (sInstance == null) {
sInstance = new EventDispatcher(listener);
}
return sInstance;
}
private EventDispatcher(EventListener listener) {
mListener = listener;
}
public void setListener(EventListener listener) {
mListener = listener;
}
@Override
public void handleMessage(Message msg) {
if (mListener != null) {
mListener.handleEvent(msg);
} else {
super.handleMessage(msg);
}
}
}
使用弱引用和引用队列保存界面的引用关系
并且使用ConcurrentHashMap保存引用,保证线程安全同时提高效率(HashMap在多线程中不稳定)
public class EventController implements EventListener {
protected ConcurrentHashMap<Integer, List<WeakReference<UIEventListener>>> mEventListeners;
protected ReferenceQueue<UIEventListener> mListenerReferenceQueue;
private static EventController sInstance;
public synchronized static EventController getsInstance() {
if (sInstance == null) {
sInstance = new EventController();
}
return sInstance;
}
private EventController() {
mEventListeners = new ConcurrentHashMap<>();
mListenerReferenceQueue = new ReferenceQueue<>();
}
//将界面的绑定关系添加到集合中
public void addUIEventListener(int eventId, UIEventListener listener) {
if (listener == null) return;
synchronized (mEventListeners) {
Reference<? extends UIEventListener> releaseListeners = null;
List<WeakReference<UIEventListener>> list = mEventListeners.get(eventId);
if (list != null && !list.isEmpty()) {
while ((releaseListeners = mListenerReferenceQueue.poll()) != null) {
list.remove(releaseListeners);
}
}
if (list == null) {
list = new ArrayList<>();
mEventListeners.put(eventId, list);
}
for (WeakReference<UIEventListener> wl : list) {
UIEventListener l = wl.get();
if (l == listener) {
return;
}
}
WeakReference<UIEventListener> newListener = new WeakReference<>(listener, mListenerReferenceQueue);
list.add(newListener);
}
}
//解除绑定
public void removeUIEventListener(int eventId, UIEventListener listener) {
if (listener == null) return;
synchronized (mEventListeners) {
List<WeakReference<UIEventListener>> list = mEventListeners.get(eventId);
if (list != null && !list.isEmpty()) {
List<WeakReference<UIEventListener>> dataList = new ArrayList<>();
for (WeakReference<UIEventListener> wl : list) {
if (wl.get() == listener) {
dataList.add(wl);
}
}
list.remove(dataList);
if (list.isEmpty()) {
mEventListeners.remove(eventId);
}
}
}
}
//线程同步
private void handleUIEvent(Message msg) {
List<WeakReference<UIEventListener>> lt = mEventListeners.get(msg.what);
if (lt != null) {
List<WeakReference<UIEventListener>> list = new ArrayList<>(lt);
for (Iterator<WeakReference<UIEventListener>> i = list.iterator(); i.hasNext(); ) {
UIEventListener listener = i.next().get();
if (listener != null) {
listener.handleUIEvent(msg);
}
}
}
}
@Override
public void handleEvent(Message msg) {
handleUIEvent(msg);
}
}
这个类中使用了两个自定义接口
public interface UIEventListener {
public void handleUIEvent(Message msg);
}
public interface EventListener {
public void handleEvent(Message msg);
}
使用方法如下:
1.Application中初始化
public static EventController sEventController = EventController.getsInstance();
public static EventDispatcher sEventDispatcher = EventDispatcher.getsInstance(sEventController);
2.发送handler事件
android.os.Message msg = android.os.Message.obtain();
//自定义的唯一值,方便在上面自定义handler集合中查找对应的绑定应用
msg.what = EventEnum.EVENT_MESSAGE_PRIVATE;
Application.sEventDispatcher.handleMessage(msg);
3.界面中使用handler
@Override
public void onResume() {
super.onResume();
Application.sEventController.addUIEventListener(EventEnum.EVENT_MESSAGE_PRIVATE, this);
}
@Override
public void onDestroy() {
super.onDestroy();
Application.sEventController.removeUIEventListener(EventEnum.EVENT_MESSAGE_PRIVATE, this);
}
@Override
public void handleUIEvent(Message msg) {
//根据 msg.what 处理收到的信息
}
另外关于线程通信,现在主流的方式是使用RXJava或者EventBus实现,这里不进行分析,下期再讲
其它常见导致内存泄露的问题
单例类中使用了Content,因为生命周期不相同,单例持有了Activity的引用,导致内存泄露
在静态变量中存入了Activity的对象,同样生命周期不一致导致泄露
执行耗时任务或者执行动画之后没有手动销毁,导致回收失败
项目中如果使用了webView,需要在onDestory()中手动清理
@Override
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
内存溢出
OOM,程序申请内存时,没有足够的内存空间供其使用
- 内存泄露导致内存溢出
- 一次加载的数据量过大
- 内存中加载的图片过大
- 频繁的GC,导致同时创建的对象过多
首先我们分析一下内存的概念
一般情况下所说的内存就是RAM,随机存储,工作状态下随时读写数据,断电数据丢失
另一种内存是ROM,持久化存储,特定条件下可写,如root
这中内存分成两个部分,一个供给系统使用,一个供给用户存储数据(/data)
目前市面上手机的RAM空间都比较大,但是android系统为了让比较多的进程常驻内存,给每个应用进程都分配一个heapSize作为内存空间的阈值,超过这个阈值就GC
在进行内存优化时候,需要先从应用的内存泄露开始处理,这部分是最消耗内存的,然后可以从图片进行处理
检测内存泄露
android Studio中默认带有Monitory可以监听内存使用情况
监听的工具一般是使用MAT 或者 LeakCanary
这两个工具的原理都是检测我们已经不需要对象是否存在强引用,如果发现有对象没有回收就去分析当前的内存快照,也就是.hprof文件,从这个文件中获取引用链信息
这个文件我们可以在ddms中获取
处理图片
目前项目中处理图片都是使用第三方工具,如Glide,Picasso,Fresco
这些框架已经做了图片的处理,不过有些地方还是需要了解一下
例如:
图片加载方式使用RGB_565,这种方式图片每个像素在内存中占2byte,而ARGB_8888,这种方式图片每个像素在内存中占4byte
图片放在drawable高分辨率文件中,因为高密度的系统去获取低密度文件夹中图片的时候会自动 放大图片,以适应高密度的精度,这样会消耗更多内存,如果出现缩小后图片显示过小,可以将图片存放在nodpi目录中
图片保存的地址:
在3.0之前,图片默认保存在Native空间中,因此不需要使用的时候需要recyle()方法回收
3.0之后保存在Dalvik虚拟机中,不需要手动调用回收
匿名共享内存空间,Fresco使用这一空间对bitmap对象存储
其源码中4.4版本以上,对bitmap的解码使用了另外的解码器
如果在4.4以下版本使用可以通过设置DecodeOptions
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.ARGB_8888;
//关键代码:下面两个设置
options.inPurgeable = true;//允许可清除
options.inInputShareable = true;// 以上options的两个属性必须联合使用才会有效果
AssetManager am = getAssets();
InputStream is = am.open("high_pixel_img.jpg");
Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);
imageView.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
另外设置图片的压缩尺寸SamplingSize
本地图片使用工具压缩后在放入项目
图片使用webp,矢量图都能够有效优化内存