Carson_Ho:Android性能优化:这是一份全面&详细的内存优化指南
定义 | 原因 | 解决 | |
---|---|---|---|
ANR | application not response,应用程序的UI线程响应超时 | 一般是主线程未及时响应用户的输入事件(如触摸、按键);或者当前的事件正在被处理,但是由于耗时太长没有能够及时完成 常见:主线程频繁进行耗时操作 |
使用多线程,将耗时操作交给工作线程执行 |
Crash | 应用程序崩溃 | 引起应用程序崩溃的很多原因时因为内存溢出OOM,因此需要避免OOM现象 | 内存优化,如: 1. 避免内存泄露 2. 避免内存抖动 3. 图片Bitmap优化 4. 提高代码质量 & 减少代码数量 |
public void example(){
ArrayList<Object> arr = new ArrayList<>();
for(int i=0;i<10;i++){
Object obj = new Object();
arr.add(obj); // arr中存储obj的引用(在栈内存中的地址)
obj = null; // 虽释放元素obj本身,但由于arr中仍持有obj的引用,导致GC仍无法回收obj对象,引起内存泄露
}
}
// 清空集合对象 & 设置为null
arr.clear();
arr = null;
(2)Static关键字修饰成员变量
// 单例模式
// 由于单例模式中对象由于其静态特性,因此单例模式引用对象的生命周期 = 应用程序生命周期
// 则若单例模式持有一个 生命周期小于应用生命周期 的对象引用,则当该实例对象被销毁时,由于单例对象仍持有该对象的引用,导致该对象无法销毁,引起内存泄露
public class SingleInstance{
public static SingleInstance instance;
public Context context;
public SingleInstance(context){
this.context = context;
}
public SingleInstance getInstance(Context context){
if(instance == null)
synchronize(this)
if(instance == null)
instance = new SingleInstance(context);
// 若传入的context 是 activity 的 context
// 则当该activiy生命周期结束被销毁时,由于单例模式的instance扔持有该activity的引用,导致activity无法被销毁,引起内存泄露
return instance;
}
}
public SingleInstance(context){
// 单例模式的context 应该为应用的context(ApplicationContext)
this.context = context.getApplicationContext();
}
(3)非静态内部类/匿名类
菜鸟教程:Java 内部类详解
非静态内部类 / 匿名类 默认持有 外部类的引用:因为非静态内部类依赖外部类,可以通过内部类对外部类的引用来访问外部类的成员变量和成员方法。
而静态内部类则不持有外部类的引用:静态内部类不依赖外部类。
(3.1)多线程:AsyncTask、实现Runnable接口、继承Thread类
public class MainActivity extends Activity{
@override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread t = new MyThread(); // 创建多线程实例new MyThread(),此时t默认持有外部类MainActivity的引用
t.start(); // 开启线程
}
// 创建Thread内部类,实现多线程
public class MyThread extends Thread{
@override
public void run(){
try{
// 若线程执行的5秒内,MainActivity被销毁,但由于工作线程持有外部类的引用,因此MainActivity无法被GC回收,会造成内存泄露
Thread.sleep(5000);
}catch(InterruptedException e){e.printStackTrace();}
}
}
}
public static class MyThread extends Thread{
(2)当外部类结束生命周期时,强制结束线程
@override
public void onDestroy(){
super.onDestroy();
thread.stop(); // 外部类Activity生命周期结束时,强制结束线程
}
(3.2)消息传递机制Handler
public class MainActivity extends Activity{
@override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 传入当前Activity实例(弱引用)
MyHandler mh = new MyHandler(this);
}
// 1. 自定义Handler子类,设置为静态内部类
public static class MyHandler extends Handler{
// 2. 定义弱引用实例
private WeakReference<Activity> reference;
public MyHandler(Activity activity){
refrence = new WeakReference<Activity>(activity);
}
@override
public void handleMessage(Message msg){
System.out.println(msg.obj);
}
}
}
(4)资源对象使用后未关闭
// 对于 广播BraodcastReceiver:注销注册
unregisterReceiver()
// 对于 文件流File:关闭流
InputStream / OutputStream.close()
// 对于数据库游标cursor:使用后关闭游标
cursor.close()
// 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null
Bitmap.recycle();
Bitmap = null;
// 对于动画(属性动画)
// 将动画设置成无限循环播放repeatCount = “infinite”后
// 在Activity退出时记得停止动画
(5)其他
定义 | 原因 | 解决 | |
---|---|---|---|
内存溢出 | 应用程序所需内存超出系统分配的内存限额,从而导致内存溢出 | 内存中加载的数据量过于庞大,如一次从数据库取出过多数据 内存泄露 代码中存在死循环或循环产生过多重复的对象实体(内存抖动) 使用的第三方软件中的BUG 启动参数内存值设定的过小 |
|
内存泄露 | 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏 | 持有引用者的生命周期>被引用者的生命周期: 集合类 Static关键字修饰成员变量 非静态内部类/匿名类 资源对象使用后未关闭 |
集合类:回收集合元素 Static关键字修饰的成员变量:避免Static引用过多实例 非静态内部类/匿名类:使用静态内部类 资源使用后未关闭:关闭资源对象 |
内存抖动 | 内存大小不断浮动的现象 | 由于大量、临时的小对象频繁创建,导致程序频繁地分配内存 & 垃圾回收器(GC)频繁回收内存 垃圾收集器(GC)频繁地回收内存会导致卡顿,甚至内存溢出(OOM)——大量、临时的小对象频繁创建会导致内存碎片,使得当需分配内存时,虽总体上有剩余内存可分配,但由于这些内存不连续,导致无法模块分配。系统则视为内存不够,故导致内存溢出OOM |
尽量避免频繁创建大量、临时的小对象 |
// 方案1:采用软引用
reference = new SoftReference<Bitmap>(bm);
// 方案2:Bitmap像素数据回收
bm.recycle();
public void decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight){
// 1. 加载图片前获取图片实际长宽值
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 禁止为bitmap分配内存
BitmapFactory.decodeResource(res,resId,options); // 对位图进行解析
// 2. 计算图片的压缩比inSampleSize,对图片进行压缩
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
// 3. 用获取到的inSampleSzie再次解析图片,可获得压缩后的图片
options.inJustDecodeBounds = false; // 在解析图片后创建Bitmap对象并为图片分配内存
return Bitmap.decodeResource(res,resId,options);
}
// 2. 根据图片控件的宽/高对图片大小进行适配——计算对应的缩放比inSampleSize=实际宽高/目标宽高
public int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
int inSampleSize = 1;
if(imageHeight > reqHeight || imageWidth>reqWidth){
final int heightRatio = Math.round((float)imageHeight/(float)reqHeight);
final int widthRatio = Math.round((float)imageWidth/(float)reqWidth);
inSampleSize = heightRatio < widthRatio?heightRatio : widthRatio;
}
return inSampleSize
}
/**
* 从缓存(内存缓存,磁盘缓存)中获取Bitmap
*/
@Override
public Bitmap getBitmap(String url) {
if (mLruCache.get(url) != null) {
// 从LruCache缓存中取
Log.i(TAG,"从LruCahce获取");
return mLruCache.get(url);
} else {
String key = MD5Utils.md5(url);
try {
if (mDiskLruCache.get(key) != null) {
// 从DiskLruCahce取
Snapshot snapshot = mDiskLruCache.get(key);
Bitmap bitmap = null;
if (snapshot != null) {
bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
}
else{
bitmap = HttpUtils.getImageFromNet(url);
}
// 存入缓存
putBitmap(url, bitmap);
return bitmap;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 存入缓存(内存缓存,磁盘缓存)
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
// 存入LruCache缓存
mLruCache.put(url, bitmap);
// 判断是否存在DiskLruCache缓存,若没有存入
String key = MD5Utils.md5(url);
try {
if (mDiskLruCache.get(key) == null) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (bitmap.compress(CompressFormat.JPEG, 100, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
方式 | 冷启动 | 热启动 |
---|---|---|
定义 | 启动应用时,后台没有该应用的进程(例:第一次开启应用,上一次彻底退出应用),这时系统会重新创建一个新的进程分配给该应用,这种启动方式就是冷启动 | 启动应用时,后台已有该应用的进程(例:按back,home键,应用退出,但仍保留在后台,可进入任务列表查看),从已有的进程中启动应用,这种启动方式就是热启动 |
特点 | 系统会重新创建一个新进程分配给它。 因此会先创建和初始化Application类,再创建和初始化MainActivity类,包括一系列测量布局绘制,最后显示在界面上 |
系统直接从已有进程中启动应用。 因此不必创建和初始化Application,直接创建和初始化MainActivity,包括一系列测量不聚会知,显示在界面上 |
流程 | Zygote进程中fork创建出一个新的进程 –> Application构造器 –> attachBaseContext() –> onCreate() –> Activity构造器 –> onCreate –> 配置主题背景等属性 –> onStart() –> onResume –> 测量布局绘制显示在界面上 | (没有Application创建和初始化)Activity构造器 –> onCreate –> 配置主题背景等属性 –> onStart() –> onResume –> 测量布局绘制显示在界面上 |
<style name="Theme.AppLauncher" parent="@android:style/Theme.NoTitleBar.Fullscreen">
- "android:windowBackground"
>@drawable/bg_splash
style>
配置启动页面SplashActivity的清单文件
<activity android:name="tv.douyu.view.activity.SplashActivity"
android:screenOrientation="portrait" android:theme="@style/Theme.AppLauncher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
Android性能优化之布局优化
Overdraw:描述的是屏幕上的某个像素在同一帧时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,就会导致某些像素区域被绘制了多次,浪费大量的CPU以及GPU资源。