Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题

虚引用 (PhantomReference)

@Test

public void onPhantomReference()throws InterruptedException{

String str = new String(“123456”);

ReferenceQueue queue = new ReferenceQueue();

// 创建虚引用,要求必须与一个引用队列关联

PhantomReference pr = new PhantomReference(str, queue);

System.out.println(“PhantomReference:” + pr.get());

System.out.printf(“ReferenceQueue:” + queue.poll());

}

虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列 (ReferenceQueue) 联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

总结

| 引用类型 | 调用方式 | GC | 是否内存泄漏 |

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第1张图片

| — | — | — | — |

| 强引用 | 直接调用 | 不回收 | 是 |

| 软引用 | .get() | 视内存情况回收 | 否 |

| 弱引用 | .get() | 回收 | 不可能 |

| 虚引用 | null | 任何时候都可能被回收,相当于没有引用一样 | 否 |

分析内存常用工具


工具很多,掌握原理方法,工具随意挑选使用。

top/procrank

meinfo

Procstats

DDMS

MAT

Finder - Activity

LeakCanary

LeakInspector

内存泄漏


产生的原因: 一个长生命周期的对象持有一个短生命周期对象的引用,通俗点讲就是该回收的对象,因为引用问题没有被回收,最终会产生 OOM。

下面我们来利用 Profile 来检查项目是否有内存泄漏

怎么利用 profile 来查看项目中是否有内存泄漏

  1. 在 AS 中项目以 profile 运行

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第2张图片

  1. 在 MEMORY 界面中选择要分析的一段内存,右键 export

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第3张图片

Allocations: 动态分配对象个数

Deallocation: 解除分配的对象个数

Total count: 对象的总数

Shalow Size: 对象本身占用的内存大小

Retained Size: GC 回收能收走的内存大小

  1. 转换 profile 文件格式
  • 将 export 导出的 dprof 文件转换为 Mat 的 dprof 文件

  • cd /d 进入到 Android sdk/platform-tools/hprof-conv.exe

//转换命令 hprof-conv -z src des

D:\Android\AndroidDeveloper-sdk\android-sdk-windows\platform-tools>hprof-conv -z D:\temp_\temp_6.hprof D:\temp_\memory6.hprof

  1. 下载 Mat 工具

  2. 打开 MemoryAnalyzer.exe 点击左上角 File 菜单中的 Open Heap Dupm

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第4张图片

  1. 查看内存泄漏中的 GC Roots 强引用

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第5张图片

这里我们得知是一个 ilsLoginListener 引用了 LoginView,我们来看下代码最后怎么解决的。

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第6张图片

代码中我们找到了 LoginView 这个类,发现是一个单例中的回调引起的内存泄漏,下面怎么解决勒,请看第七小点。

  1. 2种解决单例中的内存泄漏

  2. 将引用置为 null

/**

  • 销毁监听

*/

public void unRemoveRegisterListener(){

mMessageController.unBindListener();

}

public void unBindListener(){

if (listener != null){

listener = null;

}

}

  1. 使用弱引用

//将监听器放入弱引用中

WeakReference listenerWeakReference = new WeakReference<>(listener);

//从弱引用中取出回调

listenerWeakReference.get();

  1. 通过第七小点就能完美的解决单例中回调引起的内存泄漏。

Android 中常见的内存泄漏经典案例及解决方法

  1. 单例

示例 :

public class AppManager {

private static AppManager sInstance;

private CallBack mCallBack;

private Context mContext;

private AppManager(Context context) {

this.mContext = context;

}

public static AppManager getInstance(Context context) {

if (sInstance == null) {

sInstance = new AppManager(context);

}

return sInstance;

}

public void addCallBack(CallBack call){

mCallBack = call;

}

}

  1. 通过上面的单列,如果 context 传入的是 Activity , Service 的 this,那么就会导致内存泄漏。

以 Activity 为例,当 Activity 调用 getInstance 传入 this ,那么 sInstance 就会持有 Activity 的引用,当 Activity 需要关闭的时候需要 回收的时候,发现 sInstance 还持有 没有用的 Activity 引用,导致 Activity 无法被 GC 回收,就会造成内存泄漏

  1. addCallBack(CallBack call) 这样写看起来是没有毛病的。但是当这样调用在看一下勒。

//在 Activity 中实现单例的回调

AppManager.getInstance(getAppcationContext()).addCallBack(new CallBack(){

@Override

public void onStart(){

}

});

这里的 new CallBack() 匿名内部类 默认持有外部的引用,造成 CallBack 释放不了,那么怎么解决了,请看下面解决方法

解决方法:

  1. getInstance(Context context) context 都传入 Appcation 级别的 Context,或者实在是需要传入 Activity 的引用就用 WeakReference 这种形式。

  2. 匿名内部类建议大家单独写一个文件或者

public void addCallBack(CallBack call){

WeakReference mCallBack= new WeakReference(call);

}

  1. Handler

示例:

//在 Activity 中实现 Handler

class MyHandler extends Handler{

private Activity m;

public MyHandler(Activity activity){

m=activity;

}

// class…

}

这里的 MyHandler 持有 activity 的引用,当 Activity 销毁的时候,导致 GC 不会回收造成 内存泄漏。

解决方法:

1.使用静态内部类 + 弱引用

2.在 Activity onDestoty() 中处理 removeCallbacksAndMessages()

@Override

protected void onDestroy() {

super.onDestroy();

if(null != handler){

handler.removeCallbacksAndMessages(null);

handler = null;

}

}

  1. 静态变量

示例:

public class MainActivity extends AppCompatActivity {

private static Police sPolice;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (sPolice != null) {

sPolice = new Police(this);

}

}

}

class Police {

public Police(Activity activity) {

}

}

这里 Police 持有 activity 的引用,会造成 activity 得不到释放,导致内存泄漏。

解决方法:

//1. sPolice 在 onDestory()中 sPolice = null;

//2. 在 Police 构造函数中 将强引用 to 弱引用;

  1. 非静态内部类

参考 第二点 Handler 的处理方式

  1. 匿名内部类

示例:

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

new Thread(){

@Override

public void run() {

super.run();

}

};

}

}

很多初学者都会像上面这样新建线程和异步任务,殊不知这样的写法非常地不友好,这种方式新建的子线程ThreadAsyncTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄露。

解决方法:

//静态内部类 + 弱引用

//单独写一个文件 + onDestory = null;

  1. 未取消注册或回调

示例:

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

registerReceiver(mReceiver, new IntentFilter());

}

private BroadcastReceiver mReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

// TODO ------

}

};

}

在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用Retrofit + RxJava注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。

解决方法:

//Activity 中实现 onDestory()反注册广播得到释放

@Override

protected void onDestroy() {

super.onDestroy();

this.unregisterReceiver(mReceiver);

}

  1. 定时任务

示例:

public class MainActivity extends AppCompatActivity {

/*模拟计数/

private int mCount = 1;

private Timer mTimer;

private TimerTask mTimerTask;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

init();

mTimer.schedule(mTimerTask, 1000, 1000);

}

private void init() {

mTimer = new Timer();

mTimerTask = new TimerTask() {

@Override

public void run() {

MainActivity.this.runOnUiThread(new Runnable() {

@Override

public void run() {

addCount();

}

});

}

};

}

private void addCount() {

mCount += 1;

}

}

当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity 的引用不能被 GC 回收,因此当我们 Activity 销毁的时候要立即cancelTimerTimerTask,以避免发生内存泄漏。

解决方法:

//当 Activity 关闭的时候,停止一切正在进行中的定时任务,避免造成内存泄漏。

private void stopTimer() {

if (mTimer != null) {

mTimer.cancel();

mTimer = null;

}

if (mTimerTask != null) {

mTimerTask.cancel();

mTimerTask = null;

}

}

@Override

protected void onDestroy() {

super.onDestroy();

stopTimer();

}

  1. 资源未关闭

示例:

ArrayList,HashMap,IO,File,SqLite,Cursor 等资源用完一定要记得 clear remove 等关闭一系列对资源的操作。

解决方法:

用完即刻销毁

  1. 属性动画

示例:

动画同样是一个耗时任务,比如在 Activity 中启动了属性动画 (ObjectAnimator) ,但是在销毁的时候,没有调用 cancle 方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用 Activity ,这就造成 Activity 无法正常释放。因此同样要在Activity 销毁的时候 cancel 掉属性动画,避免发生内存泄漏。

解决方法:

@Override

protected void onDestroy() {

super.onDestroy();

//当关闭 Activity 的时候记得关闭动画的操作

mAnimator.cancel();

}

  1. Android 源码或者第三方 SDK

示例:

//如果在开发调试中遇见 Android 源码或者 第三方 SDK 持有了我们当前的 Activity 或者其它类,那么现在怎么办了。

解决方法:

//当前是通过 Java 中的反射找到某个类或者成员,来进行手动 = null 的操作。

内存抖动


什么是内存抖动

内存频繁的分配与回收,(分配速度大于回收速度时) 最终产生 OOM 。

也许下面的录屏更能解释什么是内存抖动

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第7张图片

可以看出当我点击了一下 Button 内存就频繁的创建并回收(注意看垃圾桶)。

那么我们找出代码中具体那一块出现问题了勒,请看下面一段录屏

mButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

imPrettySureSortingIsFree();

}

});

/**

* 排序后打印二维数组,一行行打印

*/

public void imPrettySureSortingIsFree() {

int dimension = 300;

int[][] lotsOfInts = new int[dimension][dimension];

Random randomGenerator = new Random();

for (int i = 0; i < lotsOfInts.length; i++) {

for (int j = 0; j < lotsOfInts[i].length; j++) {

lotsOfInts[i][j] = randomGenerator.nextInt();

}

}

for (int i = 0; i < lotsOfInts.length; i++) {

String rowAsStr = “”;

//排序

int[] sorted = getSorted(lotsOfInts[i]);

//拼接打印

for (int j = 0; j < lotsOfInts[i].length; j++) {

rowAsStr += sorted[j];

if (j < (lotsOfInts[i].length - 1)) {

rowAsStr += ", ";

}

}

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第8张图片

七大模块学习资料:如NDK模块开发、Android框架体系架构…

Android性能优化:看完这篇文章,至少解决 APP 中 90 % 的内存异常问题_第9张图片

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
第一,学习知识比较碎片化,没有合理的学习路线与进阶方向。
第二,开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。
om();

for (int i = 0; i < lotsOfInts.length; i++) {

for (int j = 0; j < lotsOfInts[i].length; j++) {

lotsOfInts[i][j] = randomGenerator.nextInt();

}

}

for (int i = 0; i < lotsOfInts.length; i++) {

String rowAsStr = “”;

//排序

int[] sorted = getSorted(lotsOfInts[i]);

//拼接打印

for (int j = 0; j < lotsOfInts[i].length; j++) {

rowAsStr += sorted[j];

if (j < (lotsOfInts[i].length - 1)) {

rowAsStr += ", ";

}

}

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

[外链图片转存中…(img-7pPx9vkZ-1647525823310)]

七大模块学习资料:如NDK模块开发、Android框架体系架构…

[外链图片转存中…(img-855u5iEX-1647525823311)]

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
第一,学习知识比较碎片化,没有合理的学习路线与进阶方向。
第二,开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

你可能感兴趣的:(程序员,android,性能优化,java)