IntentService防止破坏活动

问题描述:

据我所知,当我旋转屏幕时,活动被破坏并重新创建.在以下情况下,似乎并非总是如此.所以问题是:它真的被破坏了吗?我是出于某种原因而泄漏内存吗?如果未销毁它,它是否与新创建的活动在同一线程上运行?

As far as i know, when i rotate the screen, the activity gets destroyed and recreated. In the following scenario, it looks like this is not always the case. So the question is: Is it really destroyed? Am i leaking memory for some reason? If it is not destroyed, does it run on the same thread as the newly created activity?

该场景是一个垂直"活动,它启动IntentService,该服务需要5秒钟才能完成,并使用ResultReceiver发送回结果.在这5秒钟内,屏幕旋转,并且创建了一个新的水平"活动.结果返回到垂直"活动,而不是新的水平"活动.那么,垂直"活动不会被破坏吗?

The scenario is an "vertical" Activity that starts an IntentService, the service takes 5 secs to complete, and uses a ResultReceiver to send back the result. During those 5 secs, the screen gets rotated, and a new "horizontal" activity gets created. The result gets back to the "vertical" activity, not the new "horizontal". So, the "vertical" activity is not destroyed?

我创建了一个项目来演示这一点.代码在这里.

I've created a project to demonstrate this. Here comes the code.

首先,具有活动布局的xml.只需两个按钮.

First, the xml with the activity layout. Just two buttons.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Show value of variable" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start IntentService" />

</LinearLayout>

之后,我们的IntentService

After that, our IntentService

public class SomeService extends IntentService {

    public SomeService(){
        super("SomeService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra("receiver");

        long t0, t1;
        t0 = System.currentTimeMillis();
        do {
            t1 = System.currentTimeMillis();
        } while (t1-t0 < 5000);

        receiver.send(0, null);
    }
}

最后是活动

public class TestActivityDestructionActivity extends Activity {

    private int mTestVar = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        if (savedInstanceState != null){
            mTestVar = savedInstanceState.getInt("tv");
        }

        mTestVar++;

        Button b1 = (Button) findViewById(R.id.button1);
        b1.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Toast.makeText(TestActivityDestructionActivity.this, "Value: " + mTestVar, Toast.LENGTH_SHORT).show();
            }
        });

        Button b2 = (Button) findViewById(R.id.button2);
        b2.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent intent = new Intent(TestActivityDestructionActivity.this, SomeService.class);
                intent.putExtra("receiver", new SomeReceiver(new Handler()));
                startService(intent);
                Toast.makeText(TestActivityDestructionActivity.this, "IntentService started", Toast.LENGTH_SHORT).show();
            }
        });

    }

    private class SomeReceiver extends ResultReceiver{
        public SomeReceiver(Handler handler) {
            super(handler);
        }

        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);
            Toast.makeText(TestActivityDestructionActivity.this, "Receiver value: " + mTestVar, Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("tv", mTestVar);
    }
}

活动首次启动时,mTestVar为0,而onCreate则增加为1.活动被销毁时,它将保存该值.下一个活动将其加载并将其增加到2,依此类推.

When the activity starts the first time, mTestVar is 0, and onCreate it is inceased to 1. When the activity gets destroyed, it saves the value. The next activity loads it and increases it to 2, and so on.

执行以下步骤:

  1. 单击Button1:mTestVar为1
  2. 单击Button2:服务开始
  3. 在5秒钟之前旋转屏幕
  4. 单击Button1:mTestVar为2(调用了onCreate)
  5. 等待5秒钟:mTestVar的值仍为1

据我所知,这意味着第一个活动没有被破坏.我想念什么吗?

As far as i can see, that means that the first activity was not destroyed. Am i missing something?

谢谢!

编辑

在看到这个问题的答案之后,我使用了MAT和堆转储,发现第一个活动永远不会被垃圾收集,即使该服务早已消失并已被垃圾收集,并且不再引用该活动了.我不知道为什么.

After seeing the answer to this question, i used MAT and heap dumps and found out that the first activity is never Garbage Collected, even though the service is long gone and garbage collected and there is no reference to the activity anymore. I don't know why.

但是,如果从未创建接收器且从未将接收器传递给服务,则不会发生内存泄漏.第一次活动已按预期被破坏.无论如何,没有理由不这样做.

However, if the receiver is never created and never passed to the service, there is no memory leak. The first activity is destroyed as expected. There is no reason not to, anyway.

通过调试器在代码中使用断点运行它,并在ResultReceiver中检查"this"的ID.它保留指向您先前活动的本地"this"副本,其中int mTestVar ==1.当前活动Activity是一个完全不同的对象,它是mTestVar == 2的副本.这是内存泄漏.

Run it through the debugger with breakpoints in your code and check the id of "this" in the ResultReceiver. It's keeping a local copy of "this" which points to your previous activity, where int mTestVar == 1. The current active Activity is a whole different object, with it's own copy of mTestVar == 2. This is a memory leak.

为更简洁地回答您的问题:Android销毁了"您的活动,但是您在回调中保留了对活动的引用,以防止对其进行垃圾回收.如果您希望某项内容在多个活动中存在",则应使用服务来存储该状态(请记住,服务不是单独的线程,不应像一个线程那样使用).

To answer your question more concisely: Android "destroyed" you activity, but you kept a reference to the Activity in your callback preventing it from being garbage collected. If you want something to "live" across multiple activities you should use a service to store that state (keep in mind a service is NOT a separate thread and should not be used like one).

附注:检查变量和通用日志记录时,最好使用Log.v/e/i等.您可以在命令行中使用adb logcat mytag:V *:S过滤它们,或使用内置的logcat蚀.

A side note: When checking variables and general purpose logging it's best to use Log.v/e/i etc. You can filter them at the command line with adb logcat mytag:V *:S or use the logcat built into eclipse.