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模型如下:
举例:
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,到此为止。
网友评论