• 引言
  • startActivitys
  • TaskStackBuilder


引言

在什么场景下我么需要一次启动多个Activity 呢?一般来说用的最多的还是notification。点击一个notification,启动App 内的某一个TargetActivity,在TargetActivity界面 按back button 或者其他方式 把TargetActivity finish掉后不会返回到Home Screen 或者上一个应用的screen.而是返回到App 的MainActivity.

在讲解如何一次启动多个Activity 之前,我们来试想一下,要是我们自己来实现这种效果,我们怎么做呢?也许我们会这样做:

  1. 在notification 的 pendingIntent 中 设置一个标志位,标识从启动的这个Activity 返回的时候需要启动另外一个Activity,并在PendingIntent中把要启动的Activity 的className 一并传过去。
  2. 在被notification 启动的Activity 中 重写onBack 方法,或者捕获back 事件来实现finish 自身的同时start 另一个我们所需要的Activity.
  3. 如果返回的层级比较多。比如D–>C–>B–>MainActivity。那就每次启动下一个Activity 的intent 中 包含下一个Activity finish 后要启动的再下一个Activity。

按照上面的方式的确可以做到这种效果,如果返回的层级不多,比如就一次,还好说,如果返回的层级多,想想都麻烦。事实上在Android 3.0 之前,是不支持一次启动多个Activity的。所以在Android3.0之前,要实现上面所得效果也只能用这种笨的办法(也许有更好的方式,没有去仔细研究过)。

幸运的是在Android 3.0 api 16 Google 提供了一个新的Api startActivitys(Intent [] intents),通过传入一个Intent 数组来一次启动多个Activity.

startActivitys

Launch multiple new activities. This is generally the same as calling startActivity(Intent) for the first Intent in the array, that activity during its creation calling startActivity(Intent) for the second entry, etc. Note that unlike that approach, generally none of the activities except the last in the array will be created at this point, but rather will be created when the user first visits them (due to pressing back from the activity on top).

This method throws ActivityNotFoundException if there was no Activity found for any given Intent. In this case the state of the activity stack is undefined (some Intents in the list may be on it, some not), so you probably want to avoid such situations.

意思就是把要启动的Activity的intent 按照先后顺序放到一个数组里面,这种启动方式有点类似于通过startActivity(Intent)启动数组中第一个Intent 对应的Activity,在第一个Activity 创建的过程中再startActivity(Intent) 数组中的第二个Intent 对应的Activity.但是和这种方式有一点不一样的地方就是startActivitys这种启动方式只有数组中最后一个Intent 对应的Activity 会执行OnCreate 方法。其他Intent 对应的Activity只有在用户第一看到他们的时候才会执行onCreate方法(用户按back button finish 掉在它上面的Activity 后)。为了更直观的感受一下,可以通过adb shell dumpsys activity 命令查看所有Intent 对应的Activity在 Task and Back Stack的具体信息:

andorid 多个scheme android多个activity_android

TaskStackBuilder

要通过startActivitys 来启动多个Activity,首先就得构建一个Intent 数组,并且为每一个要启动的activity 设置好启动方式(通过flag 设置)。TaskStackBuilder就是干这个用的。TaskStackBuilder 不光会帮我们构建好要启动的Activity 数组并设置好activity的启动方式。并且还提供了一些封装好的api,比如PendingIntent getPendingIntent (int requestCode, int flags, Bundle options),startActivities, Intent[] getIntents ().

下面是一段发送一个通知启动Activity 的一段示例代码:

public void onSend(View v) {

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("My notification")
                .setOnlyAlertOnce(true)// 如果更新通知(同一个通知id),true表示只有第一次有alert 响声,false,每次都有
                .setAutoCancel(true) // 点击通知后自动cancel
                .setContentText("Hello World!");


        // 有两种方式构建启动Intent 数组,



        //第一种:

        /**
         *  通过TaskStackBuilder,TaskStackBuilder.addNextIntentWithParentStack(nextIntent) 会把nextIntent 的父Activity
         *  以及祖先Activity 按照先后顺序添加的一个intent 数组中。可以通过TaskStackBuilder.getIntents()方法得到一个intent 数组,
         *
         *  通过TaskStackBuilder是如何得知nextIntent 的父Activity以及祖先Activity 的呢?
         *
         *  <activity
         *       android:name="com.example.app.ChildActivity"
         *       android:label="@string/title_child_activity"
         *       android:parentActivityName="com.example.app.MainActivity" > // api 16 开始支持
         *
         *
         *          // 兼容 api 4 ~ api 15
         *           <meta-data
         *           android:name="android.support.PARENT_ACTIVITY"
         *           android:value="com.example.app.MainActivity" />
         *   </activity>
         *
         *
         *  是如何告知是通过在 AndroidManifest 中 activity 的 android:parentActivityName (APi 16 提供的) 属性来确定其父Activity,
         *  在 API 4~ Api 16 通过    <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity"/>
         *  来确定其父Activity。
         *
         *  ------------------------------------------------------------------------------------
         *  其中需要注意的是,TaskStackBuilder 会为 Intent 数组中的第一个 intent 设置 如下  Flag:
         *
         *  Intents[0] = new Intent(intents[0])
         *  .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
         *  |IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
         *  |IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
         *
         *  设置了IntentCompat.FLAG_ACTIVITY_CLEAR_TASK后,如果task已经存在,并且task
         *  里面有还没被finish 掉的Activity (也就是说app 可能正处��foreground 也可能在background,
         *  那么在点击通知启动 目标Activity 的时候会清空 task 里面的所有Activity
         *
         *  设置了IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME 后 从Intent 数组最后一个Activity 返回时直接进入到Launcher.
         *  比如说:你正在看电影,收到一个通知,点击启动后,然后一步一步返回,从最后一个Activity 返回时直接退到桌面Launcher 而不是之前的 电影app界面
         *
         *  --------------------------------------------------------------------------------------
         *
         *
         */


        Intent resultIntent = new Intent(this, ResultActivity.class);
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addNextIntentWithParentStack(resultIntent);
        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);


        // 第二种:

        /**
         * 自己手动构建一个Intent 数组。
         *
         *
         * 为了避免上面第一种方式出现的不管从什么地方点击启动通知退出app后都退到桌面已经清楚已存在task里面activity 的问题
         * 所以去掉了IntentCompat.FLAG_ACTIVITY_CLEAR_TASK 和 IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME。
         *
         *
         * 增加了Intent.ACTION_MAIN 和  Intent.CATEGORY_LAUNCHER 是为了实现点击通知启动activity 后,再按Home键
         * 回到launcher 后,再点击app icon 启动app时直接把已存在的task从background 带入到 foreground 而不是在已存在
         * 的task的 顶上在启动一个 app 的启动activity(MainActivity),
         *
         *
         *
         *  --------------------------------------------------------------------------------------------------------
         *  比如下面是 没有增加 Intent.ACTION_MAIN 和  Intent.CATEGORY_LAUNCHER 时的效果 通过 adb shell dumpsys activity 获得。
         *  Task id #1091
         *    TaskRecord{eecd5e3 #1091 A=com.example.practice.startactivities U=0 sz=4}
         *    Intent { flg=0x10000000 cmp=com.example.practice.startactivities/.MainActivity }
         *    Hist #3: ActivityRecord{8798dc0 u0 com.example.practice.startactivities/.MainActivity t1091}
         *    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10600000 cmp=com.example.practice.startactivities/.MainActivity bnds=[73,1209][1006,2142] (has extras) }
         *    ProcessRecord{c099968 22272:com.example.practice.startactivities/u0a241}
         *    Hist #2: ActivityRecord{25ea551 u0 com.example.practice.startactivities/.ResultActivity t1091}
         *    Intent { cmp=com.example.practice.startactivities/.ResultActivity }
         *    ProcessRecord{c099968 22272:com.example.practice.startactivities/u0a241}
         *    Hist #1: ActivityRecord{63c00ea u0 com.example.practice.startactivities/.SecondActivity t1091}
         *    Intent { cmp=com.example.practice.startactivities/.SecondActivity }
         *    Hist #0: ActivityRecord{d26b5c7 u0 com.example.practice.startactivities/.MainActivity t1091}
         *    Intent { flg=0x10000000 cmp=com.example.practice.startactivities/.MainActivity }
         * ---------------------------------------------------------------------------------------------------------------
         *
         * 增加了Intent.ACTION_MAIN 和  Intent.CATEGORY_LAUNCHER 就是为了保持和 在Launcher点击app icon 启动app时Launcher
         * 为启动activity设置的intent一样。这也是为什么我们平常在使用app 时按home task 进入到background,回到Launcher 后,
         * 从Launcher 再次点击app icon 不会启动给一个新的而是把之前在后台的task 带入到foreground。
         *

         *
         */



        Intent secondIntent = new Intent(this, SecondActivity.class);
        Intent mainIntent = new Intent(this, MainActivity.class);
        Intent [] intents = new Intent[]{mainIntent,secondIntent,resultIntent};
        mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mainIntent.setAction(Intent.ACTION_MAIN);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

        resultPendingIntent = PendingIntent.getActivities(this,0,intents,PendingIntent.FLAG_UPDATE_CURRENT);


        mBuilder.setDefaults(NotificationCompat.DEFAULT_ALL); // 设置默认铃声和震动
        mBuilder.setContentIntent(resultPendingIntent);
        mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);

        mNotificationManager.notify(mNotificationId, mBuilder.build());
    }

示例Demo 下载

产考资料:
Tasks and Back Stack