Android内存优化

发布时间:2023-05-21

一、内存泄漏

1、什么是内存泄漏

内存泄漏指在程序运行过程中分配的内存空间没有及时释放,导致内存空间占用过多,造成程序的崩溃或者系统的崩溃的现象。一般体现为App启动后,内存不断增加,操作时间越长,内存越大,容易造成ANR或者崩溃。

2、常见的内存泄漏场景

a.静态变量被持有:

public class Singleton {
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    //...
}

在单例模式中,只要有一个对象持有了该类的引用,那么这个对象和其中的所有成员信息都不会被回收,从而引发了内存泄漏。

b. handler引起内存泄漏:

public class MainActivity extends Activity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    //...
}

在Activity中使用了handler,由于handler持有外部类Activity的引用,那么当Activity被摧毁时,由于handler还持有Activity的引用,会导致Activity无法被回收。此时,建议在Activity退出时清除handler的消息队列。

c. 监听器泄漏:

public class MainActivity extends Activity {
    private MyOnClickListener mListener = new MyOnClickListener() {
        @Override
        public void onClick(View view) {
        }
    };
    //...
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mButton.setOnClickListener(null);
    }
}

在Activity中使用监听器时,如果不及时销毁,会造成Activity的内存泄漏,同样的,在Activity销毁时,需要及时清除持有监听器的引用。

二、Bitmap优化

1、Bitmap内存计算

Bitmap在内存中占用的大小 = 图片的宽度 * 图片的高度 * 每个像素点的字节数。

2、Bitmap内存优化

a. 采样率缩放

public static Bitmap decodeSampledBitmapFromFile(String filename, int width, int height) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filename, options);
    options.inSampleSize = calculateInSampleSize(options, width, height);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(filename, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;
    if (height > reqHeight || width > reqWidth) {
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
}

b. 使用软引用和弱引用

public HashMap<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
//...
SoftReference<Bitmap> reference = new SoftReference<Bitmap>(bitmap);
imageCache.put(url, reference);

c. LruCache

public class MemoryCache extends LruCache<String, Bitmap> {
    public MemoryCache(int maxSize) {
        super(maxSize);
    }
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }
    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        super.entryRemoved(evicted, key, oldValue, newValue);
        // 释放oldValue的内存空间
        oldValue.recycle();
    }
}
//...
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new MemoryCache(cacheSize);

三、布局优化

1、Layout XML的优化

a. 使用include标签

<include layout="@layout/title_layout" />

b. 使用RelativeLayout,减少deep-level嵌套

<RelativeLayout>
    <TextView id="@+id/title" />
    <ImageView id="@+id/icon" />
    <TextView id="@+id/content" />
</RelativeLayout>

c. 不使用过于复杂的布局文件

2、代码布局优化

a. 手动布局时,减少measure和layout的操作次数

private LinearLayout layoutExample;
layoutExample.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
layoutExample.setOrientation(LinearLayout.VERTICAL);
TextView textView = new TextView(context);
textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
textView.setText("Custom Text");
layoutExample.addView(textView);
setContentView(layoutExample);

b. 使用自定义View,复用已有的控件

四、资源文件优化

1、资源文件分类

将资源文件按不同的类型进行分类,方便开发和维护。

2、资源文件压缩

a. 使用lint工具

打开Android Studio中的预编译检查功能,可检测和提示不必要的资源文件,方便开发时的及时优化。

b. 使用TinyPNG或者PngQuant工具

可以无损或者有损地对图片资源进行压缩,减小apk的大小。

五、线程优化

1、线程池的使用

a. 避免反复地创建和销毁线程,提高线程的重复利用率。

b. 通过Executors类创建线程池,来控制线程的数量和资源。

// 使用newFixedThreadPool来创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        //...
    }
});

2、AsyncTask的使用

通过AsyncTask来处理UI线程和后台线程的交互,避免在主线程中进行长时间的操作,从而导致ANR。

public class DownloadTask extends AsyncTask<String, Integer, String> {
    @Override
    protected String doInBackground(String... params) {
        // 这里进行耗时操作
        return result;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // 更新进度条
    }
    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        // 更新UI
    }
}

3、使用Handler和ThreadLocal减少内存占用

a. Handler

private static class MyHandler extends Handler {
    private final WeakReference<Activity> mActivity;
    public MyHandler(Activity activity) {
        mActivity = new WeakReference<Activity>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (mActivity.get() != null) {
            // 执行UI操作或者其他操作
        }
    }
}
private MyHandler mHandler = new MyHandler(this);

b. ThreadLocal

private ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};
private class SimpleThread extends Thread {
    @Override
    public void run() {
        SimpleDateFormat sdf = threadLocal.get();
        //...
    }
}

六、APP启动优化

1、延迟加载

a. 使用Fragment或者ViewPager可以延迟加载不必要的资源或者页面。

public class MyFragment extends Fragment {
    //...
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            // 加载数据或者页面
        } else {
            // 释放资源
        }
    }
}

b. 通过线程池进行延迟加载和预加载,提高App启动速度。

2、懒加载

延迟加载过后,还可以通过懒加载的方式加载不必要的资源,提高App的启动速度。

public class LazyLoadFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_lazy, container, false);
        return rootView;
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getUserVisibleHint()) {
            // 加载数据或者页面
        }
    }
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser && isAdded()) {
            // 加载数据或者页面
        }
    }
}

七、总结

内存优化是Android开发中的一个重要环节,在开发过程中,多注意内存泄漏的问题,对Bitmap进行优化和管理,对布局和资源文件进行分类和压缩,选用合适的线程池和AsyncTask,延迟和懒加载,可以大大提高App的启动速度和运行效率。