Android基础之Activity与Intent

Task和BackStack的基本概念

Task的理解

Task是多个Activity的集合,用户进行操作时将与这些Activity进行交互。 这些 Activity 按照启动顺序排队存入一个栈(即“back stack”)。

Back Stack

大部分Task都启动自Home屏幕。当用户触摸application launcher中的图标(Home屏幕上的快捷图标)时,应用程序的Task就进入前台。 如果该应用不存在Task(最近没有使用过此应用),则会新建一个Task,同时android为新 Task创建一个新的返回栈(back stack),该应用的“main”activity作为栈的根Activity被打开。

Task和Back Stack的关系

当用户主页执行另一个新的Task时,一个Task被移动到后台执行,此时它的返回栈(Back Stack)也被保存在后台, 同时android为新Task创建一个新的返回栈(Back Stack)。当后台的Task被再次运行从而返回前台时,它的返回栈(Back Stack)被移到前台,并恢复其之前执行的Activity如果后台有太多运行Task,系统将会杀死一些Task释放内存。

如果当前Activity启动了另一个Activity,则新的Activity被压入栈顶并获得焦点。 前一个Activity仍保存在栈中,但是被停止。Activity停止时,系统会保存用户界面的当前状态。 当用户按下返回键,则当前Activity将从栈顶弹出(被销毁),前一个Activity将被恢复(之前的用户界面状态被恢复)。Activity在栈中的顺序永远不会改变,只会压入和弹出——被当前Activity启动时压入栈顶,用户用返回键离开时从栈顶退出。 这样,Back Stack以“后进先出”的方式运行。

实现退出APP时销毁回退栈中所有Activity(context.finishAffinity());

Activity的启动模式

standard

默认模式,可以不用写配置。在这个模式下,每次激活 Activity 时都会创建 Activity实例,并放入回退栈中(栈顶)。该Activity可以被实例化多次,各个实例可以属于不同的Task,一个Task中也可以存在多个实例。

例如:
若我有一个Activity名为A1, 上面有一个按钮可跳转到A1。那么如果我点击按钮,便会新启一个 Activity A1叠在刚才的A1之上,再点击,又会再新启一个在它之上……
点Back键会依照栈顺序依次退出。

singleTop

如果在回退栈的栈顶正好存在该Activity的实例,就重用该实例(会调用实例的 onNewIntent()),否则就会创建新的实例并放入栈顶(注:即使栈中已经存在该 Activity的实例,只要不在栈顶,都会创建实例),即:不允许多个相同 Activity 叠加,保证Task栈顶部有且只有一个Activity实例对象

例如:
若我有两个Activity名为B1,B2,两个Activity内容功能完全相同,都有两个按钮可以跳到B1或者B2,唯一不同的是B1为standard,B2为singleTop。
若我意图打开的顺序为B1->B2->B2,则实际打开的顺序为B1->B2(后一次意图打开B2,实际只调用了前一个的onNewIntent方法)
若我意图打开的顺序为B1->B2->B1->B2,则实际打开的顺序与意图的一致,为B1->B2->B1->B2。和默认启动没区别。

singleTask

如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent ())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。

即:保证Task栈中有且只有一个该Activity实例对象。

singleInstance

在一个新栈中创建该Activity实例,并让多个应用共享该Activity实例。一旦这种模式的Activity实例存在于某个栈中,任何应用再激活这个Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity 都会进入同一个应用中。此启动模式和我们电脑使用的浏览器工作原理类似,在多个程序中访问浏览器时,如果当前浏览器没有打开,则打开浏览器,否则会在当前打开的浏览器中访问。此模式会节省大量的系统资源,因为他能保证要请求的Activity对象在当前的栈中只存在一个。

“launchMode”设置为”singleInstance”的Activity总是在栈底, 只能被实例化一次, 不允许其它的Activity压入”singleInstance”的 Activity所在Task栈,即整个 Task栈中只能有这么一个 Activity。

注意: 虽然Activity启动了一个新的Task,但用户仍然可以用回退键返回前一个 Task。

即:只有一个实例,并且这个实例独立运行在一个Task中,这个Task只有这个实例,不允许有别的Activity 存在。

例如:
程序有三个ActivityD1,D2,D3,三个Activity 可互相启动,其中D2为singleInstance模式。那么程序从D1开始运行, 假设D1的taskId为200,那么从D1启动D2时,D2会新启动一个Task,即D2与D1不在一个Task中运行。假设D2的taskId为201,再从D2启动D3时,D3的taskId为200,也就是说它被压到了D1启动的任务栈中。

若是在别的应用程序打开D2,假设Other的taskId为200,打开D2,D2会新建一个Task运行,假设它的taskId为201,那么如果这 时再从D2启动D1或者D3,则又会再创建一个Task,因此,若操作步骤为other->D2->D1,这过程就涉及到了3个Task了。

注: 当应用中有三个Activity 为 A、B、C,B启动模式为singleInstance ,若此时启动顺序为 A > B > C ,那么此时按返回键将回到A,再按返回键A结束后才会回到B。即:只有当当前任务的回退栈中的所有 Activity都结束 ,才会回到上一个Task。

Intent属性的特点及用法

说完Activity,再来说说Intent的属性

Intent中的ComponentName属性

明确指定Intent将要启动哪个组件,因此这种Intent被称为显式Intent,没有指定 ComponentName属性的Intent被称为隐式Intent。隐式Intent没有明确要启动哪个组件,应用会根据Intent指定的规则(动作)去启动符合条件的组件。ComponentName不仅可以启动本程序中的Activity,还可以启动其它程序的 Activity。

打开本应用程序的其他 Activity

    Intent intent =new Intent();
    ComponentNamecomponent=new ComponentName(Context, TwoActivity.class);
    intent.setComponent(component);
    startActivity(intent);

打开其他应用程序的 Activity

    Intent intent = new Intent();
    ComponentName componentName = new ComponentName("目标应用程序的包名","目标Activity的类(含路径)");
    intent.setComponent(componentName);
    startActivity(intent);

Intent中Action属性

Action、Category属性与intent-filter配置:
通常,Action, Category 属性结合使用。定义这两个属性都是在主配置文件 AndroidManifest.xml 的<intent-filter>节点中。Intent通过定义Action属性(其实就是定义一段自定义的字符串),这样就可以把Intent与具体的某个Activity分离。否则,每次跳转都有写成类似new Intent(MainActivity.this,NextActivity.class)这样的形式,也就是说必须将要跳转的目标Activity的名字写出来,这样的编码其实是“硬编码”,并没有实现松耦合。调用Intent对象的setAction()方法实现页面跳转虽然略微复杂(需要在 AndroidManifest.xml文件中配置),但是实现松耦合。

例如,在 A 应用(甚至其他应用)中对某个 Activity 做如下配置:

<activity
      android:name=".MainActivity"
      android:enabled="true"
      android:exported="true"
      android:label="@string/app_name" >
      <intent-filter>
             ……
      </intent-filter>
      <intent-filter>
             <action android:name="com.rair.xxx" />

             <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
</activity>

然后在B 应用中启动配置以上信息的界面,使用如下代码:

    Intent intent = new Intent();
    intent.setAction("com.rair.xxx");
    intent.addCategory(Intent.CATEGORY_DEFAULT);//可以省略,默认就是DEFAULT
    startActivity(intent);

这样子所有配置了该信息的Activity都会弹出来让用户选择。类似打开音乐弹出多个音乐播放器、打开连接弹出多个浏览器等。

Intent中Category属性

Category 属性用于指定当前动作(Action)被执行的环境。通过 addCategory() 方法或在清单文件 AndroidManifest.xml 中设置。默认为:CATEGORY_DEFAULT。一般我们配置的 隐式意图 都需要加上该属性值

例如:
CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。 
CATEGORY_HOME:设置该组件为HomeActivity。
CATEGORY_LAUNCHER:设置该组件为在当前应用程序启动器中优先级最高的Activity,通常为入口ACTION_MAIN配合使用。 
CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。 
等...

该属性大部分都是配合系统组件使用。

例如:
实现页面跳转到Home界面:

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_HOME);
    startActivity(intent);

实现页面跳转到浏览器界面:

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_APP_BROWSER);
    startActivity(intent);

以及让某一个Activity作为启动页面,则清单配置文件中对Activity配置:

<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
        <intent-filter>
               <action android:name="android.intent.action.MAIN" />

               <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
</activity>

Intent中Data属性

用于添加数据。通常是启动某个系统程序或其他程序,带给此程序的信息。Data属性通常用于向Action属性提供操作的数据。Data属性的值是个Uri对象。
Uri的格式如下:scheme://host:port /path

系统内置的属性常量:

tel://:号码数据格式,后跟电话号码。 
mailto://:邮件数据格式,后跟邮件收件人地址。
smsto://:短息数据格式,后跟短信接收号码。
content://:内容数据格式,后跟需要读取的内容。 
file://:文件数据格式,后跟文件路径。
market://search?q=pname:pkgname:市场数据格式,在GoogleMarket里搜索包名为pkgname的应用。
geo://latitude,longitude:经纬数据格式,在地图上显示经纬度指定的位置。

在intent-filter中指定data属性的实际目的是:要求接收的Intent中的data必须符合intent-filter中指定的data属性,这样达到反向限定Intent的作用。

例如:在AndroidManifest.xml中进行如下设置:

<activity android:name=".TestActivity">  
    <intent-filter>  
         <action android:name="com.rair.test"/>  
         <category android:name="android.intent.category.DEFAULT" />
         <data android:scheme="file"/>  
    </intent-filter>  
</activity>

那么启动该 Activity 的 Intent 必须进行如下设置:

    Intent intent = new Intent();  
    intent.setAction("com.rair.test");
    Uri uri =  Uri.parse("file://****");  
    intent.setData(uri);  

data属性解析:android:scheme、android:host、android:port、android:path
data的前四个属性构成了URI的组成部分
data元素组成的URI模型如下:

scheme://host:port/path

举例:

URI file://com.android.rair.test:520/mnt/sdcard

scheme-->file:
host-->com.android.rair.test
port-->520
path-->mnt/sdcard
其中host和port为URI的authority,如果没有指定host,port将被忽略
data的各属性并不是独立的,data的各属性构成了URI的整个组成部分。要使 authority(host和port)有意义,必须指定scheme;要使path有意义,必须使 scheme和authority(host 和 port)有意义。

URI和intent-filter匹配:
Intent中URI和intent-filter进行比较的时候只会进行部分的比较:
(1)当intent-filter中只设置了scheme,只会比较URI的scheme部分;
(2)当intent-filter中只设置了scheme和authority,那么只会匹配URI中的 scheme和authority;
(3)当intent-filter中设置了scheme、authority和path,那么会匹配URI中的 scheme、authority、path;(path可以使用通配符进行匹配)

Intent利用Action属性和Data属性启动Android系统内置组件的代码
(一)、直接拨打电话:需要打电话权限

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_CALL);
    intent.setData(Uri.parse("tel:5556"));
    startActivity(intent);
    //调用拨号面板:
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setData(Uri.parse("tel:654654"));
    startActivity(intent);

(二)、利用Uri打开浏览器等:

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    Uri uri = Uri.parse("https://www.baidu.com");//浏览器
    intent.setData(uri);
    startActivity(intent);

Intent中Type属性

Type属性用于指定Data所指定的Uri对应的MIME类型。MIME只要符合“abc/xyz”这样的字符串格式即可。

注意: 如果格式不符合 "abc/xyz" 则程序不能编译运行。

利用Action、Data 和Type属性启动Android系统内置组件的代码:
例如:播放视频

    Intent intent = new Intent();
    Uri uri =Uri.parse("file:///sdcard/media.mp4");
    intent.setAction(Intent.ACTION_VIEW);
    intent.setDataAndType(uri,"video/*");
    startActivity(intent);

Intent中Extra属性

1、通过 intent.putExtra(键, 值)的形式在多个Activity之间进行数据交换。
2、利用 Action、Data 、Extra 属性启动 Android 系统内置组件的代码:
例如:调用发送短信的程序

    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_SENDTO);
    Uri uri = Uri.parse("smsto:13343333433");
    intent.setData(uri);
    intent.putExtra("sms_body", "已经编辑好的短信内容....");
    startActivity(intent);

Intent中Flags属性

FLAG_ACTIVITY_SINGLE_TOP:
和上面Activity的LaunchMode的singleTop类似。如果某个Intent添加了这个标志,并且这个Intent的目标Activity就是栈顶的Activity,那么将不会新建一个实例压入栈中。

FLAG_ACTIVITY_CLEAR_TOP:
例如现在的栈情况为:A B C D。D此时通过intent跳转到B,如果这个Intent添加FLAG_ACTIVITY_CLEAR_TOP标记,则栈情况变为:AB。如果没有添加这个标记,则栈情况将会变成:A B C D B。也就是说,如果添加了FLAG_ACTIVITY_CLEAR_TOP标记,并且目标Activity在栈中已经存在,则将会把位于该目标Activity之上的Activity从栈中弹出销毁。这跟上面把B的 Launchmode设置成singleTask类似。

FLAG_ACTIVITY_NO_HISTORY:
例如现在栈情况为:ABC。C通过 intent 跳转到D,这个Intent添加FLAG_ACTIVITY_NO_HISTORY标志,则此时界面显示D的内容,但是它并不会压入栈中。如果按返回键,返回到C,栈的情况还是:A B C。如果此时D中又跳转到E,栈的情况变为:A B C E,此时按返回键会回到C,因为D根本就没有被压入栈中。

FLAG_ACTIVITY_NEW_TASK:
例如现在栈1的情况是:A B C。C通过Intent跳转到D,并且这个Intent添加了FLAG_ACTIVITY_NEW_TASK标记,如果D这个Activity在Manifest.xml中的声明中添加了Taskaffinity,并且和栈1的affinity不同,系统首先会查找有没有和D的Taskaffinity相同的Task栈存在,如果有存在,将D压入那个栈,如果不存在则会新建一个D的affinity的栈将其压入。如果D的Taskaffinity默认没有设置,或者和栈1的affinity相同,则会把其压入栈1,变成:A B C D,这样就和不加FLAG_ACTIVITY_NEW_TASK标记效果是一样的了。

注意: 如果试图从非Activity的非正常途径启动一个Activity,比如,从一个service中启动一个Activity,则Intent必须要添加FLAG_ACTIVITY_NEW_TASK标记。

Activity的管理及退出程序

在处理Activity时可能需要跳转,删除指定的Activity或所有的Activity。当退出应用程序时,我们可能需要关闭所有的Activity,这里对于新手来说经常会存在MainActivity已经finish掉了,但是还有其他的Activity存在。我们可以设计一个全局的Activity栈,使用这个栈来管理Activity。

/** 
 * Activity管理类 
 *  
 */  
public class ActivityControl {  
  
    private static Stack<Activity> activityStack;  
    private static ActivityControl instance;  
  
    private ActivityControl() {  
    }  
  
    /** 
     * 单一实例 
     */  
    public static ActivityControl getInstance() {  
        if (instance == null) {  
            instance = new ActivityControl();  
        }  
        return instance;  
    }  
  
    /** 
     * 添加Activity到堆栈 
     */  
    public void addActivity(Activity activity) {  
        if (activityStack == null) {  
            activityStack = new Stack<Activity>();  
        }  
        activityStack.add(activity);  
    }  
  
    /** 
     * 获取当前Activity(堆栈中最后一个压入的) 
     */  
    public Activity currentActivity() {  
        Activity activity = activityStack.lastElement();  
        return activity;  
    }  
  
    /** 
     * 结束当前Activity(堆栈中最后一个压入的) 
     */  
    public void finishActivity() {  
        Activity activity = activityStack.lastElement();  
        finishActivity(activity);  
    }  
  
    /** 
     * 结束指定的Activity 
     */  
    public void finishActivity(Activity activity) {  
        if (activity != null) {  
            activityStack.remove(activity);  
            activity.finish();  
            activity = null;  
        }  
    }  
  
    /** 
     * 结束指定类名的Activity 
     */  
    public void finishActivity(Class<?> cls) {  
        for (Activity activity : activityStack) {  
            if (activity.getClass().equals(cls)) {  
                finishActivity(activity);  
            }  
        }  
    }  
  
    /** 
     * 结束所有Activity 
     */  
    public void finishAllActivity() {  
        for (int i = 0, size = activityStack.size(); i < size; i++) {  
            if (null != activityStack.get(i)) {  
                activityStack.get(i).finish();  
            }  
        }  
        activityStack.clear();  
    }  
  
    /** 
     * 退出应用程序 
     */  
    @SuppressWarnings("deprecation")  
    public void AppExit(Context context) {  
        try {  
            finishAllActivity();  
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);  
            activityManager.restartPackage(context.getPackageName());  
            System.exit(0);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
} 

Activity是Android组件中最基本也是最为常见用的四大组件之一。是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务。Activity中所有操作都与用户密切相关,是一个负责与用户交互的组件,可以通过setContentView(View)来显示指定控件。在一个android应用中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。Activity之间通过Intent进行通信。因此,Activity在我们开发中很重要,ok,到此为止。

推荐阅读更多精彩内容