Android: How to get allowTaskReparenting=“true” to

2019-08-31 08:26发布

问题:

I am writing an app that can be launched from another app by receiving an intent with ACTION_VIEW or ACTION_EDIT. For example, it can be opened by viewing an email attachment. The trouble is that when you click on the home button and click again on the launch icon of the email app you were using, my activity is killed and any user edits that had been made are lost. What I want to happen is that when the user clicks the home button, my activity is re-parented so that it resumes when the user clicks on the launch icon of my app. I've tried setting android:allowTaskReparenting="true" in manifest.xml but this doesn't work. Sometimes it doesn't have any effect at all, and sometimes the activity is moved to my launch icon, yet still gets killed when you click again on the email app icon. The documentation on allowTaskReparenting is really vague. It says the property means:

“Whether or not the activity can move from the task that started it to the task it has an affinity for.”

What does the word can mean here? What I want is a guarantee that the activity does move (and stays there). Is there any way to achieve this?

Thanks in advance to anyone who can help.

EDIT

In response to comments below, I have put together a baby version demonstrating the problems I am encountering. When you start EditFileActivity by clicking on a file in another app (e.g, an attachment to an email) you can then edit the file. But clicking on the home icon and then clicking again on the email app icon causes the changes you have made to the file to be lost. I want the android system to only forget about an instance of EditFileActivity if the user explicitly clicks back and then says "yes" or "no". Ideally I want all instances of EditFileActivity to stack up on my app's launch icon. I could implement something similar to this by using singleTask or singleInstance and writing some kind of activity showing all open files in tabs, but it would be much easier if I could get the android system itself to help me. Any ideas?

Here is a complete project demonstrating the problem.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.example.Example"
         android:versionCode="1"
         android:versionName="1.0">
   <uses-sdk
       android:minSdkVersion="11"
       android:targetSdkVersion="11"/>
   <application
       android:label="Example"
       android:icon="@drawable/ic_launcher">
       <activity
           android:name=".LaunchActivity"
           android:label="LaunchActivity"
           android:screenOrientation="portrait">
           <intent-filter>
               <action android:name="android.intent.action.MAIN"/>
               <category android:name="android.intent.category.LAUNCHER"/>
           </intent-filter>
       </activity>
       <activity
           android:name=".EditFileActivity"
           android:label="EditFileActivity"
           android:screenOrientation="portrait">
           <!-- This is just an example. I wouldn't use this intent filter in a real app! -->
           <intent-filter>
               <action android:name="android.intent.action.VIEW"/>
               <action android:name="android.intent.action.EDIT"/>
               <category android:name="android.intent.category.DEFAULT"/>
               <category android:name="android.intent.category.BROWSABLE"/>
               <data android:scheme="file"/>
               <data android:scheme="content"/>
               <data android:mimeType="*/*"/>
               <data android:host="*"/>
           </intent-filter>
       </activity>
   </application>
</manifest>` 

LaunchActivity:

public class LaunchActivity extends Activity {

   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       TextView textView = new TextView(this);
       textView.setText("This is the activity you see when you click on the application's launch icon. It does absolutely nothing.");
       textView.setTextSize(18);
       setContentView(textView);
   }
}

EditFileActivity:

public class EditFileActivity extends Activity {

   // This String represents the contents of the file.
   // In a "real" app the String would be initialised by reading the data from the Intent that started the activity.
   // However, for the purposes of this example, the initial value is "Default".
   private String fileContents = "Default";
   private boolean editsMade = false;
   private TextView textView;

   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       textView = new TextView(this);
       textView.setText(fileContents);
       textView.setTextSize(18);
       textView.setPadding(10, 10, 10, 10);
       setContentView(textView);
       textView.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               makeEdits();
           }
       });     
   }

   @Override
   public void onBackPressed() {
       if (editsMade) {
           savePrompt();
       } else {
           finish();
       }
   }

   private void savePrompt() {
       DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {
               if (which == Dialog.BUTTON_POSITIVE) {
                   // Here is where I would save the edited file.
                   Toast.makeText(EditFileActivity.this, "File saved", Toast.LENGTH_LONG).show();
               }
               finish();
           }
       };
       new AlertDialog.Builder(this)
               .setTitle("Close File")
               .setMessage("Do you want to save the changes you made?")
               .setPositiveButton("Yes", listener)
               .setNegativeButton("No", listener)
               .show();
   }

   private void makeEdits() {
       final EditText editText = new EditText(this);
       editText.setText(fileContents);
       new AlertDialog.Builder(this)
               .setTitle("Edit File")
               .setView(editText)
               .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                   @Override
                   public void onClick(DialogInterface dialog, int whichButton) {
                       Editable editable = editText.getText();
                       assert editable != null;
                       String newContents = editable.toString();
                       if (!fileContents.equals(newContents)) {
                           editsMade = true;
                           fileContents = newContents;
                           textView.setText(fileContents);
                       }
                   }
               })
               .setNegativeButton("Cancel", null)
               .show();
   }
}

UPDATE 10/12/2014

The problems encountered were due to the use of the Intent flag FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET. Fortunately, Google have deprecated this flag, as of API Level 21.

回答1:

Issue:

The trouble is that when you click on the home button and click again on the launch icon of the email app you were using, my activity is killed and any user edits that had been made are lost.

This happens because the email application had set FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET intent flag while launching your activity. When this flag is set, next time when the task is brought to the foreground, your activity will be finished, so that user returns to the previous activity.

From docs:

This is useful for cases where you have a logical break in your application. For example, an e-mail application may have a command to view an attachment, which launches an image view activity to display it. This activity should be part of the e-mail application's task, since it is a part of the task the user is involved in. However, if the user leaves that task, and later selects the e-mail app from home, we may like them to return to the conversation they were viewing, not the picture attachment, since that is confusing. By setting this flag when launching the image viewer, that viewer and any activities it starts will be removed the next time the user returns to mail.

Solution: Use singleTask launchMode for your activity. The email app will not kill your activity, as the activity belongs to different task now.

If the activity instance is already in the task and an attempt is made to launch the activity again, then a new instance is not created. Instead onNewIntent is called. Here you can prompt the user to save the previous edit if any, before presenting new content.



回答2:

As discussed above, the system's default behavior preserves the state of an activity when it is stopped. This way, when users navigate back to a previous activity, its user interface appears the way they left it. However, you can—and should—proactively retain the state of your activities using callback methods, in case the activity is destroyed and must be recreated.

When the system stops one of your activities (such as when a new activity starts or the task moves to the background), the system might destroy that activity completely if it needs to recover system memory. When this happens, information about the activity state is lost. If this happens, the system still knows that the activity has a place in the back stack, but when the activity is brought to the top of the stack the system must recreate it (rather than resume it). In order to avoid losing the user's work, you should proactively retain it by implementing the onSaveInstanceState() callback methods in your activity.

Source