Android基础之Activity与Intent

作者: Reathin | 来源:发表于2017-03-22 21:41 被阅读149次

    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,到此为止。

    相关文章

      网友评论

        本文标题:Android基础之Activity与Intent

        本文链接:https://www.haomeiwen.com/subject/mxeanttx.html