内存泄漏场景:
- 静态变量引用非静态内部类/匿名类实例。
- 子线程相关的非静态内部类/匿名内部类引用。
- Handler导致的泄漏。
- 不正确使用上下文。
- 广播资源未取消注册。
- 绑定服务后,未解绑服务。
- 使用静态View。
- File,Cusor等需要关闭的资源对象,未关闭。
- Bitmap未及时回收。
- WebView。
- 集合中的对象未及时清理。
Java匿名类实例和内部类实例会持有外部类实例的引用,内部类实例的引用不释放,外部类实例也不会释放。
Java中创建的对象很多都是根对象(例如静态引用,线程等),当根对象的生命周期长时,被它直接或间接持有的对象不会被释放,引发内存泄漏。
泄漏场景实例分析
1. 静态变量引用非静态内部类/匿名类实例。
- 静态变量引用非静态内部类实例。
- 静态变量引用匿名类实例。
静态变量引用非静态内部类,泄漏样例代码:
public class MainActivity extends AppCompatActivity {
private static View.OnClickListener listener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建内部类实例,listener静态变量引用这个实例
listener = new MyListener();
findViewById(R.id.btn_click).setOnClickListener(listener);
}
class MyListener implements View.OnClickListener {
@Override
public void onClick(View v) {
System.out.println("Onclick");
}
}
}
静态变量引用匿名类实例,泄漏样例代码:
public class MainActivity extends AppCompatActivity {
private static View.OnClickListener listener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//listener静态变量引用匿名类实例
listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("Onclick");
}
};
findViewById(R.id.btn_click).setOnClickListener(listener);
}
}
解决方法:
- 方法一:
listener
改成非静态成员(推荐),只作为Activity
对象中的一个普通成员。- 方法二:
listener
主动置null
,此示例引用的是普通实例,置空可被回收(假如引用一个内部类/匿名类线程实例,依然泄漏)。- 方法三:
MyListener
使用static
修饰,即静态类,不间接持有Activity
实例。- 方法四:匿名类使用Lambda替代,或使用静态内部类,不间接持有
Activity
实例。
后面两种方法虽然
Activity
不泄漏,但是listener
对象无法被回收,第一种解决方法更佳。
2. 子线程相关的非静态内部类/匿名内部类引用。
- 子线程为非静态的内部类。
- 子线程为匿名类。
- 子线程中引用非静态内部类实例。
- 子线程中引用匿名类实例。
子线程为非静态的内部类,泄漏样例代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private MyThread thread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread = new MyThread();
thread.start();
}
class MyThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
如果thread
为静态变量,即使在onDestroy
方法中,主动置空thread
变量,依然会有内存泄漏。因为此时泄漏包含了静态变量
和子线程为非静态内部类实例
两种情况,他们都持有Activity
,且生命周期比Activity
更长,线程相关的下面三种泄露情况也是一样的。
子线程为匿名类实现,泄漏样例代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Thread thread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
}
子线程中引用非静态内部类实例,泄漏样例代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_click).setOnClickListener(v ->
SingleTest.getInstant().setListener(new MyCallback())
);
}
class MyCallback implements SingleTest.Callback {
@Override
public void onResult(boolean success) {
Log.d(TAG, "Callback result=" + success);
}
}
}
子线程中引用匿名类实例,泄漏样例代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_click).setOnClickListener(v ->
SingleTest.getInstant().setListener(new SingleTest.Callback() {
@Override
public void onResult(boolean success) {
Log.d(TAG, "Callback result=" + success);
}
}));
}
}
子线程在SingleTest
单例中:
public class SingleTest {
private static volatile SingleTest instance;
private SingleTest() {
}
public static SingleTest getInstant() {
if (instance == null) {
synchronized (SingleTest.class) {
if (instance == null) {
instance = new SingleTest();
}
}
}
return instance;
}
public void setListener(Callback callback) {
new Thread(() -> {
try {
Thread.sleep(120000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (callback != null) {
callback.onResult(true);
}
String aa = callback == null ? "null" : "none null";
Log.d("SingleTest callback=", aa);
}).start();
}
interface Callback {
void onResult(boolean success);
}
}
解决方法:
- ‘子线程为非静态的内部类’,使用静态内部类。
- ‘子线程为匿名类’,
Lambda
替代,或使用静态内部类。- ‘子线程中引用非静态内部类实例’,使用静态内部类。
- ‘子线程中引用匿名类实例’,
Lambda
替代,或使用静态内部类。
注意:
- 使用静态内部类,只是让线程实例不持有
Activity
,从而线程即使仍在执行任务,不会引起Activity
泄漏,但是线程执行所需要的内存依然会被占据,
管理好线程的存活也很重要,及时取消不必要的执行线程。
3. Handler
导致的泄漏。
Handler + Runnable
循环调用。样例代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private int count = 0;
private final Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.postDelayed(runnable, 1000);
}
private final Runnable runnable = new Runnable() {
@Override
public void run() {
count++;
if (runnable != null) {
Log.d(TAG, "count=" + count);
handler.postDelayed(runnable, 1000);
}
}
};
}
解决方法:
- 调用
handler.removeCallbacks(runnable)
(推荐)。Handler
,Runnable
定义为静态内部类,handler
变量使用static
修饰。
4. 不正确使用上下文。
- 单例中直接持有上下文。
- 单例中间接持有上下文。
单例中直接持有
Activity
,代码示例:
单例:
public class SingleTest {
private static volatile SingleTest instance;
private Context context = null;
private SingleTest() {
}
public static SingleTest getInstant() {
if (instance == null) {
synchronized (SingleTest.class) {
if (instance == null) {
instance = new SingleTest();
}
}
}
return instance;
}
public void setContext(Context context) {
this.context = context;
}
}
Activity:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//将Activity传给了单例
SingleTest.getInstant().setContext(this);
}
}
单例中间接持有上下文。
单例:
public class SingleTest {
private static volatile SingleTest instance;
private Callback callback = null;
private SingleTest() {
}
public static SingleTest getInstant() {
if (instance == null) {
synchronized (SingleTest.class) {
if (instance == null) {
instance = new SingleTest();
}
}
}
return instance;
}
public void setCallback(Callback callback) {
this.callback = callback;
}
interface Callback {
void onResult(boolean result);
}
}
Activity::
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//单例间接持有Activity
SingleTest.getInstant().setCallback(new SingleTest.Callback() {
@Override
public void onResult(boolean result) {
}
});
}
}
解决方法:
- 方法一:在
Activity
销毁时,将单例中会持有上下文的变量置空。- 方法二:单例中使用
Application
上下文,代替Context
上下文。- 方法三:
Callback
类用静态类方式实现,不让单例间接持有Activity
上下文。
5. 广播资源未取消注册。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final BroadcastReceiver mReceiver = new MyReceiver();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyFilter intentFilter = new MyFilter(Intent.ACTION_USER_PRESENT);
registerReceiver(mReceiver, intentFilter);
}
static class MyFilter extends IntentFilter {
MyFilter(String action) {
super(action);
}
}
static class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "MyReceiver, " + intent.getAction());
}
}
}
解决方法:
Activity
销毁时,取消注册。
还有一种情况,把registerReceiver(mReceiver, intentFilter)
改成getApplication().getApplicationContext().registerReceiver(mReceiver, intentFilter)
,即Application
代替Activity
注册广播,如果MyReceiver
和MyFilter
也都是静态类,不取消注册不会导致Activity
泄露,因为使用的是Application
的上下文。但如果它哥俩有一个是内部类或者匿名类,也等于AMS
间接持有Activity
,则仍会引发Activity
泄露。最好还是注册广播后,在销毁时主动取消注册。
6. 绑定服务后,未解绑服务。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final MyConnection conn = new MyConnection();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
}
static class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onBindingDied(ComponentName name) {
}
}
}
解决方法:
Activity
销毁时,解除绑定。
如果用Application
绑定服务,情况和注册广播一样。
7. 使用静态View
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static Button btnClick;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnClick = findViewById(R.id.btn_click);
}
}
解决方法:
- 方法一:移除
static
修饰符,使用非静态View
。- 方法二:
Activity
销毁时,将对象置null
。
8. File,Cusor
等需要关闭的资源对象,未关闭。
及时close
资源。
9. Bitmap
未及时回收。
调用bitmap.recycle()
回收。
10.WebView
。
- 方法一:使用一个全局的
Webview
。 - 方法二:销毁。
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
11. 集合中的对象未及时清理。
及时清空集合中的对象。