在 Android 开发中,内存走漏是一个常见的问题。这个问题或许会导致使用程序变慢、溃散或者消耗很多的内存,终究导致设备功能下降。
什么是内存走漏
内存走漏指的是使用程序中存在一些目标或者资源无法被废物收回器收回,导致内存占用不断添加,终究导致设备功能下降。
内存走漏的原因
目标未被正确收回
当目标的引证仍然存在时,但不再需求该目标时,没有及时开释目标会导致内存走漏。
示例代码:
public void onCreate() {
// ...
MyObject object = new MyObject();
// ...
}
// 解决方案:
public void onCreate() {
// ...
MyObject object = new MyObject();
// 运用完object后,及时调用object = null,开释目标
object = null;
// ...
}
匿名类和内部类的引证
因为匿名类和内部类会隐式持有外部类的引证,假如不留意处理,或许导致外部类无法被正确收回。
示例代码:
public class MainActivity extends AppCompatActivity {
public void onCreate() {
// ...
MyListener listener = new MyListener() {
// ...
};
// ...
}
}
// 解决方案:
public class MainActivity extends AppCompatActivity {
private MyListener listener;
public void onCreate() {
// ...
listener = new MyListener() {
// ...
};
// ...
}
protected void onDestroy() {
super.onDestroy();
// 在适宜的机遇,及时将listener置空,开释外部类引证
listener = null;
}
}
单例形式导致的内存走漏
假如运用单例形式的目标无法被开释或当令整理,会导致该目标一向存在于内存中。
示例代码:
public class MySingleton {
private static MySingleton instance;
public static MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
// ...
}
// 解决方案:
public class MySingleton {
private static MySingleton instance;
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
if (instance == null) {
instance = new MySingleton();
}
}
}
return instance;
}
public static void releaseInstance() {
instance = null;
}
// ...
}
Handler 导致的内存走漏
假如在运用Handler时,未正确处理消息行列和对外部类弱引证,或许导致外部类无法被收回。
示例代码:
public class MyActivity extends AppCompatActivity {
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
// ...
}
};
// ...
}
// 解决方案:
public class MyActivity extends AppCompatActivity {
private static class MyHandler extends Handler {
private final WeakReference<MyActivity> mActivity;
public MyHandler(MyActivity activity) {
mActivity = new WeakReference<>(activity);
}
public void handleMessage(Message msg) {
MyActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private MyHandler handler = new MyHandler(this);
// ...
}
长期运转的后台任务
假如使用程序启动了一个后台任务,而且该任务的生命周期很长,这或许会导致内存走漏。如在后台线程中执行网络请求或数据库操作,在任务完成后未正确处理目标的引证会导致内存走漏。
示例代码:
public void startBackgroundTask() {
new Thread(new Runnable() {
public void run() {
// 长期运转的后台任务
}
}).start();
}
// 解决方案:
public void startBackgroundTask() {
new Thread(new Runnable() {
public void run() {
// 长期运转的后台任务
// 任务执行完毕后,及时将相关目标引证置空
}
}).start();
}
Context 的过错引证
在Android开发中,Context引证对错常常见的内存走漏原因。当将一个长生命周期的目标与Context关联时,假如未正确解除引证,将导致Context无法被收回。
示例代码:
public class MyActivity extends AppCompatActivity {
public static MyActivity sInstance;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sInstance = this;
}
}
// 解决方案:
public class MyActivity extends AppCompatActivity {
private static MyActivity sInstance;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sInstance = this;
}
protected void onDestroy() {
super.onDestroy();
// 在封闭Activity时,及时解除引证
sInstance = null;
}
}
运用缓存导致的内存走漏
运用缓存是为了提高功能和减少资源运用,但假如在缓存中坚持过长期的目标引证,有或许导致内存走漏。
示例代码:
public class ObjectCache {
private static final int MAX_SIZE = 100;
private Map<String, Object> cache = new HashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
// 未添加移除操作
}
public Object get(String key) {
return cache.get(key);
}
}
// 解决方案:
public class ObjectCache {
private static final int MAX_SIZE = 100;
private Map<String, WeakReference<Object>> cache = new HashMap<>();
public void put(String key, Object value) {
if (cache.size() >= MAX_SIZE) {
// 当缓存超过最大值时,尽或许移除一些旧的目标
removeOldestObject();
}
cache.put(key, new WeakReference<>(value));
}
public Object get(String key) {
WeakReference<Object> weakRef = cache.get(key);
if (weakRef != null) {
return weakRef.get();
}
return null;
}
private void removeOldestObject() {
// 移除一些旧的目标
}
}
未封闭的资源
在运用一些资源,如数据库衔接、文件输入/输出流等时,假如在运用完毕后未显式封闭这些资源,会导致资源走漏和内存走漏。
示例代码:
public void readFromFile() {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("file.txt");
// 读取数据
} catch (IOException e) {
e.printStackTrace();
} finally {
// 未及时封闭资源
}
}
// 解决方案:
public void readFromFile() {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("file.txt");
// 读取数据
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
怎么检测内存走漏
Android Studio 供给了一些工具,可以协助开发者检测内存走漏问题。例如:
- Memory Profiler:可用于剖析使用程序的内存运用状况,并查看目标的实例数、生命周期和内存走漏状况。
- Allocation Tracker:可用于跟踪目标的创建和开释,协助开发者识别内存走漏问题。
- LeakCanary:一个开源库,专门用于检测和记录内存走漏状况,并供给具体的堆转储(heap dump)和内存走漏剖析。
怎么防止内存走漏
以下是一些常见的内存走漏防止方法:
- 及时开释目标:在不再需求目标时,及时将其引证置空,以便废物收回器可以正确收回目标。
- 运用弱引证:对于或许导致内存走漏的目标引证,运用弱引证来防止强引证导致的无法收回问题。
- 防止运用静态目标:静态目标生命周期长,简单导致内存走漏,尽量防止过度运用静态目标。
- 防止运用匿名类和内部类:匿名类和内部类隐式地持有外部类的引证,简单导致外部类无法被收回。
- 防止运用单例形式:假如单例形式目标无法当令开释,会一向存在于内存中,添加内存占用。
- 防止 Handler 导致的内存走漏:运用静态内部类和对外部类的弱引证来防止Handler导致的内存走漏。
结论
内存走漏是一个常见的问题,在 Android 开发中需求留意。开发者需求了解内存走漏的原因,以及怎么检测和防止内存走漏问题。经过及时开释目标、运用弱引证、防止运用静态目标、匿名类和内部类,以及正确处理Handler,开发者可以有效地防止内存走漏问题,然后提高使用程序的稳定性和功能。
别的,Android Studio供给的内存剖析工具如Memory Profiler、Allocation Tracker和LeakCanary可以协助开发者检测和解决内存走漏问题,主张开发者加以利用。
引荐
android_startup: 供给一种在使用启动时可以愈加简单、高效的方式来初始化组件,优化启动速度。不仅支撑Jetpack App Startup的全部功能,还供给额定的同步与异步等待、线程操控与多进程支撑等功能。
AwesomeGithub: 根据Github的客户端,纯练习项目,支撑组件化开发,支撑账户暗码与认证登陆。运用Kotlin语言进行开发,项目架构是根据JetPack&DataBinding的MVVM;项目中运用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。
flutter_github: 根据Flutter的跨渠道版本Github客户端,与AwesomeGithub相对应。
android-api-analysis: 结合具体的Demo来全面解析Android相关的知识点, 协助读者可以更快的掌握与了解所论述的要点。
daily_algorithm: 每日一算法,由浅入深,欢迎加入一同共勉。