Android:Activity,线程,和内存泄漏

30 篇文章 0 订阅
本文深入探讨了在Android应用开发中如何避免内存泄漏的问题,提供了避免内存泄漏的几种常见方法,包括使用静态内部类、实现取消策略以及考虑是否使用线程等。文章还强调了在Activity生命周期事件中清理资源的重要性。
摘要由CSDN通过智能技术生成


http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html

Activitys, Threads, & Memory Leaks

A common difficulty in Android programming is coordinating long-running tasks over the Activity lifecycle and avoiding the subtle memory leaks which might result. Consider the Activity code below, which starts and loops a new thread upon its creation:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
  * Example illustrating how threads persist across configuration
  * changes (which cause the underlying Activity instance to be
  * destroyed). The Activity context also leaks because the thread
  * is instantiated as an anonymous class, which holds an implicit
  * reference to the outer Activity instance, therefore preventing
  * it from being garbage collected.
  */
public class MainActivity extends Activity {
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super .onCreate(savedInstanceState);
     exampleOne();
   }
 
   private void exampleOne() {
     new Thread() {
       @Override
       public void run() {
         while ( true ) {
           SystemClock.sleep( 1000 );
         }
       }
     }.start();
   }
}

Note: the source code in this blog post is available on GitHub.

When a configuration change occurs, causing the entire Activity to be destroyed and re-created, it is easy to assume that Android will clean up after us and reclaim the memory associated with the Activity and its running thread. However, this is not the case. Both will leak never to be reclaimed, and the result will likely be a significant reduction in performance.

How to Leak an Activity

The first memory leak should be immediately obvious if you read my previous post on Handlers and inner classes. In Java, non-static anonymous classes hold an implicit reference to their enclosing class. If you're not careful, storing this reference can result in the Activity being retained when it would otherwise be eligible for garbage collection. Activity objects hold a reference to their entire view hierarchy and all its resources, so if you leak one, you leak a lot of memory.

The problem is only exacerbated by configuration changes, which signal the destruction and re-creation of the entire underlying Activity. For example, after ten orientation changes running the code above, we can see (using Eclipse Memory Analyzer) that each Activity object is in fact retained in memory as a result of these implicit references:

Figure 1. Activity instances retained in memory after ten orientation changes.

After each configuration change, the Android system creates a new Activity and leaves the old one behind to be garbage collected. However, the thread holds an implicit reference to the old Activity and prevents it from ever being reclaimed. As a result, each new Activity is leaked and all resources associated with them are never able to be reclaimed.

The fix is easy once we've identified the source of the problem: declare the thread as a private static inner class as shown below.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
  * This example avoids leaking an Activity context by declaring the
  * thread as a private static inner class, but the threads still
  * continue to run even across configuration changes. The DVM has a
  * reference to all running threads and whether or not these threads
  * are garbaged collected has nothing to do with the Activity lifecycle.
  * Active threads will continue to run until the kernel destroys your
  * application's process.
  */
public class MainActivity extends Activity {
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super .onCreate(savedInstanceState);
     exampleTwo();
   }
 
   private void exampleTwo() {
     new MyThread().start();
   }
 
   private static class MyThread extends Thread {
     @Override
     public void run() {
       while ( true ) {
         SystemClock.sleep( 1000 );
       }
     }
   }
}

The new thread no longer holds an implicit reference to the Activity, and the Activity will be eligible for garbage collection after the configuration change.

How to Leak a Thread

The second issue is that for each new Activity that is created, a thread is leaked and never able to be reclaimed. Threads in Java are GC roots; that is, the Dalvik Virtual Machine (DVM) keeps hard references to all active threads in the runtime system, and as a result, threads that are left running will never be eligible for garbage collection. For this reason, you must remember to implement cancellation policies for your background threads! One example of how this might be done is shown below:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
  * Same as example two, except for this time we have implemented a
  * cancellation policy for our thread, ensuring that it is never
  * leaked! onDestroy() is usually a good place to close your active
  * threads before exiting the Activity.
  */
public class MainActivity extends Activity {
   private MyThread mThread;
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super .onCreate(savedInstanceState);
     exampleThree();
   }
 
   private void exampleThree() {
     mThread = new MyThread();
     mThread.start();
   }
 
   /**
    * Static inner classes don't hold implicit references to their
    * enclosing class, so the Activity instance won't be leaked across
    * configuration changes.
    */
   private static class MyThread extends Thread {
     private boolean mRunning = false ;
 
     @Override
     public void run() {
       mRunning = true ;
       while (mRunning) {
         SystemClock.sleep( 1000 );
       }
     }
 
     public void close() {
       mRunning = false ;
     }
   }
 
   @Override
   protected void onDestroy() {
     super .onDestroy();
     mThread.close();
   }
}

In the code above, closing the thread in onDestroy() ensures that you never accidentally leak the thread. If you want to persist the same thread across configuration changes (as opposed to closing and re-creating a new thread each time), consider using a retained, UI-less worker fragment to perform the long-running task. Check out my blog post, titled Handling Configuration Changes with Fragments, for an example explaining how this can be done. There is also a comprehensive example available in the API demos which illustrates the concept.

Conclusion

In Android, coordinating long-running tasks over the Activity lifecycle can be difficult and memory leaks can result if you aren't careful. Here are some general tips to consider when dealing with coordinating your long-running background tasks with the Activity lifecycle:

  • Favor static inner classes over nonstatic. Each instance of a nonstatic inner class will have an extraneous reference to its outer Activity instance. Storing this reference can result in the Activity being retained when it would otherwise be eligible for garbage collection. If your static inner class requires a reference to the underlying Activity in order to function properly, make sure you wrap the object in aWeakReference to ensure that you don't accidentally leak the Activity.

  • Don't assume that Java will ever clean up your running threads for you. In the example above, it is easy to assume that when the user exits the Activity and the Activity instance is finalized for garbage collection, any running threads associated with that Activity will be reclaimed as well. This is never the case. Java threads will persist until either they are explicitly closed or the entire process is killed by the Android system. As a result, it is extremely important that you remember to implement cancellation policies for your background threads, and to take appropriate action when Activity lifecycle events occur.

  • Consider whether or not you should use a Thread. The Android application framework provides many classes designed to make background threading easier for developers. For example, consider using a Loader instead of a thread for performing short-lived asynchronous background queries in conjunction with the Activity lifecycle. Likewise, if your the background thread is not tied to any specific Activity, consider using a Service and report the results back to the UI using a BroadcastReceiver. Lastly, remember that everything discussed regarding threads in this blog post also applies to AsyncTasks (since the AsyncTask class uses an ExecutorService to execute its tasks). However, given that AsyncTasks should only be used for short-lived operations ("a few seconds at most", as per thedocumentation), leaking an Activity or a thread by these means should never be an issue.

The source code for this blog post is available on GitHub. A standalone application (which mirrors the source code exactly) is also available for download on Google Play.

As always, leave a comment if you have any questions and don't forget to +1 this blog in the top right corner!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值