Activity是Android应用中负责与用户交互的组件,给Android应用提供可视化用户界面。Activity是Window的容器,Activity包含一个getWindow()方法,返回该Activity所包含的窗口。
创建Activity
需要在清单文件中为其配置一个activity标签
-
标签中如果带有这个子节点,则会在系统中多创建一个快捷图标
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
一个应用程序可以在桌面创建多个快捷图标。
-
activity的名称、图标可以和应用程序的名称、图标不相同
android:icon="@drawable/ic_launcher" android:label="@string/app_name"
Activity的生命周期
Activity的四个状态
- 活动状态:当前Activity位于前台,用户可见,可以获得焦点进行交互。
- 暂停状态:其他Activity位于前台,该Activity依然可见,只是不能获得焦点进行交互,常见如弹出PopupWindow。
- 停止状态:该Activity不可见,失去焦点。
- 销毁状态:该Activity结束,或Activity所在的Dalvik进程结束。
Activity生命周期及回调方法
- onCreate(Bundle savedStatus):创建Activity时被回调;
- onStart():启动Activity时被回调,Activity已经显示在屏幕,但没有得到焦;
- onRestart():重新启动Activity时被回调,Activity从不可见变成可见时会执行此方法;
- onResume():恢复Activity时被回调, Activity得到焦点,可以与用户交互;
- onPause():暂停Activity时被回调,Activity失去焦点,无法再与用户交互,但依然可见;(可用于保护界面当前状态)
- onStop():停止Activity时被回调,Activity不可见,进入后台;
- onDestroy():销毁Activity时被回调。
Activity实践中的生命周期方法回调过程
- 启动Activity:onCreate->onStart->onResume;
- 点击Home键返回系统桌面:onPause->onStop;
- 点击应用列表的图标重新进入应用:onRestart->onStart->onResume;
- 点击返回键(或程序调用finish()方法):onPause->onStop->onDestory;
Activity跳转时的生命周期方法回调过程
横竖屏切换的生命周期
默认情况下 ,横竖屏切换, 销毁当前的activity,重新创建一个新的activity。在一些特殊的应用程序常见下,比如游戏,不希望横竖屏切换activity被销毁重新创建。
默认情况(manifest清单文件中不对Activity的configChanges属性做任何设置):
1、android3.2之前的版本:onSaveInstanceState->onPause->onStop->onDestroy->onCreate->onStart->onResume;
2、android3.2以后的版本:onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onResume;设置让Activity对横竖屏切换不敏感(清单文件中设置android:configChanges="orientation"):
1、android3.2之前的版本:调用onConfigurationChanged(不会重建Activity,也不会调用任何生命周期方法)
2、android3.2之后的版本,又分为两种情况:
①targetSdkVersion<=12:调用onConfigurationChanged(不会重建Activity,也不会调用任何生命周期方法);
②targetSdkVersion>12:onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onResume(配置的android:configChanges="orientation"没起作用);
③如果targetSdkVersion>12时,想让横竖屏切换时不重建Activity,还得配置screenSize,也就是android:configChanges="orientation|screenSize"(因为google在android3.2中添加了screensize改变的通知,在转屏的时候,不仅是orientation发生了改变,screensize同样也发生了改变),这是最保险的做法。开发实践中禁用掉横竖屏重建Activity:
1、直接将Activity的横竖屏写死(简单、但是用户体验不友好,具体根据产品需求):
//①通过清单文件配置Activity
android:screenOrientation="landscape"//始终横屏
android:screenOrientation="portrait"//始终竖屏
//②在代码中实现:
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//始终横屏
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //始终竖屏
2、通过清单文件配置Activity不再敏感横竖屏切换(相对更友好安全的做法,参见上文这也是兼容性更好的做法,如果需要在横竖屏切换时做业务逻辑处理,可以在onConfigurationChanged方法中实现):
在manifest.xml中配置activity:
android:configChanges="orientation|screenSize"
Activity的启动模式
所有的Activity都将存放在任务栈中,通过设置Activity的启动模式,可以修改任务栈的排列模式。我们可以在可以通过adb指令来查看系统的任务栈情况:adb shell dumpsys activity。 adb shell dumpsys activity activities | findstr "ResumedActivity"(这是用来查看当前活跃的栈顶Activity)。
Activity的四种启动模式
- Standard 标准启动模式:Android创建Activity时的默认模式,假设没有为Activity设置启动模式的话,默觉得标准模式。每次启动一个Activity都会又一次创建一个新的实例入栈,无论这个实例是否存在。
- SingleTop 栈顶复用模式:假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存。
①如果需要创建的Activity已经不处于栈顶,将会创建一个新的Activity入栈,同Standard模式一样。
②如果须要创建的Activity已经处于栈顶时,此时不会再创建新的Activity,而是直接复用栈顶的Activity,保证栈顶如果存在,不会重复创建。此时Activity的onCreate、onStart不会被系统调用,由于它并没有发生改变。但是它的的 onNewIntent会被回调。
- SingleTask 栈内复用模式: 当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空让此Activity位于栈顶,并且回调Activity的onNewIntent方法。保证整个任务栈里面只有一个实例存在。如果不存在同Standard模式一样。(如果一个activity的创建需要占用大量的系统资源一般配置这个activity为singletask的启动模式,一般应用的主界面也会配置为SingleTask模式) 。
- SingleInstance启动模式非常特殊,是全局单例模式,是一种加强的SingleTask模式。它除了具有SingleTask的所有特性外,还有一个特点,SingleInstance启动的activity会运行在自己的任务栈里面,并且这个任务栈里面只有一个实例存在。一般应用开发很少用到,如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance。比如Launch、锁屏键、电话拨打界面等。
①我们可以试着做一个SingleInstance启动模式的小猜想:假设我们从MainActivity ->(跳转到)SingleInstanceActivity ->(跳转到) ->SecondActivity。 然后我们点击返回按钮,页面的返回顺序会是怎样?如果你试了之后不理解其原理,下文有关于任务栈的详细介绍。
任务栈的相关理解
在上文的启动模式中我们多次提到任务栈这个概念,任务是一个activity的集合,它用栈(后进先出)的方式来管理activity;这个栈被称为返回栈(back stack),栈里的activity顺序是按打开顺序放置。
①当用户在home界面点击应用图标时候,这个应用的任务就会被转移到前台,如果这个应用的任务是空的,说明最近这个应用没有被启动过,系统就会去创建一个新的任务,将该应用的主activity放入到返回栈中。
②当一个activity启动了一个新activity的时候,新的activity会被放置到返回栈的栈顶并获取焦点(如果是SingleInstance启动模式,新的Activity会新建一个栈并独享);前一个activity仍然保留在任务栈,但处于停止状态。
③当用户按下返回键的时候,处于栈顶的activity会被移除掉,前一个activity就会重新回到栈顶的位置。我们只能向栈顶添加activity或者将栈顶的activity移除掉。
④如果一直按返回键,返回栈中的activity会一个一个的被移除,最终返回到主屏幕,这时候返回栈中activity全被清空,对应的任务也就不存在了。
- 当打开一个应用,对应的任务处于前台;这时候点击home键回到主屏幕,任务就被转移到后台;当任务处于后台状态的时候,返回栈中的activity都进入停止状态,但在返回栈中的顺序不会变,每个activity的信息和数据都在;当处于内存不足的情况下有可能会被销毁回收(涉及到进程优先级问题)。
启动模式的使用方式
1、在Manifest.xml清单文件中静态指定启动模式:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"/>
2、在Intent中动态指定启动模式去创建Activity:
Intent intent = new Intent();
intent.setClass(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
- 两种方式的区别:在Intent中动态指定的方式,优先级更高,若两者同时存在,以动态指定方式为准;
Activity异常结束时保存状态
Activity异常结束
Activity的异常结束是指,非人为主动结束和退出Activity的行为,一般有以下两种情况:
1、资源系统设置变更:如横竖屏切换,可能会导致Activity销毁重建,或者设备语言发生变化,或者键盘发生变化时。我们可以在清单文件中配置Activity的configChanges属性来避免(具体参照上文)。
2、系统资源不足时:由于虚拟机的垃圾回收机制,在系统内存不足时,会自动回收掉低优先级的进程,释放内存保证优先级高的进程能正常运行(Android进程优先级相关知识)。
在Activity异常结束时保存和恢复数据
- 保存数据:onSaveInstanceState(),只会在异常退出时才会回调。如果用户显式关闭Activity时(点击返回,或者触发Activity的finish方法),则系统不会回调此方法。调用的时机android3.2之前的版本:onSaveInstanceState在onPause之前回调,Android3.2之后在onPause之后回调。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("name", "rave");
}
- 恢复数据:onRestoreInstanceState(),会在页面异常关闭之后,重新进入时回调。调用时机是在onStart之后,onResume之前(没有做过低版本测试)。
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if(savedInstanceState != null){
String name = savedInstanceState.getString("name");
System.out.println(name);
}
}
- 对于控件来说,在Activity异常结束时,部分系统控件会自动保存和恢复它的状态(如EditView)。因为View类里面也有onSaveInstanceState()和onRestoreInstanceState(Parcelable state),如果要自定义控件实现该功能可以参考系统控件的做法。
Activity的之间的数据传递
当一个Activity启动另一个Activity时,或者当一个Activity关闭并返回时,常常会有一些数据需要传递。Android的做法是将数据以键值对的方式存放在Bundle中,让Intent携带Bundle在Activity之间完成数据传递。我们通过intent的putExtras()来添加数据,里面可以存放所以可序列化的类型(基本数据类型及其数组:byte、boolean、char、short、int、long、float、double、byte[]...;实现Parcelable接口的类;实现Serializable接口的类;还有这些类型的ArrayList集合类:ArrayList<? extends Parcelable>、ArrayList<Integer>、ArrayList<String>、ArrayList<CharSequence>)。???疑问这里Parcelable和Serializable有什么区别
- Activity启动另一个Activity传递数据:
Bundle bundle = new Bundle();
bundle.putBoolean("booleanValue", true);
Intent intent = new Intent();
intent.putExtras(bundle);
startActivity(intent);
//startActivityForResult(intent, 0);
- 从Intent里面提取数据:
Bundle bundle = getIntent().getExtras();
boolean value = bundle.getBoolean("booleanValue");
- Activity关闭并返回数据:
Bundle bundle = new Bundle();
bundle.putSerializable("booleanValue", true);
Intent intent = new Intent();
intent.putExtras(bundle);
setResult(1, intent);
finish();
- 从返回的Activity里提取数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Bundle bundle = data.getExtras();
}
Activity转场动画
使用Scheme方式唤起Activity
Android中启动Activity主要有两种方式,一种是通过显示意图启动,一种是通过隐式意图启动。这里介绍的使用Scheme方式唤起,其实是隐式意图启动的一种,因为这种方式在hybird开发中很常用所以提出来单独介绍。
android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面。客户端应用可以通过清单文件向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。
URL Scheme使用场景大致分以下几种
- 服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面
- H5页面点击锚点,根据锚点具体跳转路径APP端跳转具体的页面
- APP端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面
- APP根据URL跳转到另外一个APP指定页面
URL scheme协议格式
例如:xl://goods:8888/goodsDetail?goodsId=10011002
- xl代表该Scheme 协议名称
- goods代表Scheme作用于哪个地址域
- goodsDetail代表Scheme指定的页面
- goodsId代表传递的参数
- 8888代表该路径的端口号
使用方式
- 唤起外部应用的Activity:
1、必须在目标Activity的Manifest.xml中配置如下过滤器(关于Intent和Intent-filter会单独说明):
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="artist"
android:host="first"
android:path="/enter"/>
</intent-filter>
2、启动方式:
Intent intent = new Intent();
intent.setData(Uri.parse("artist://first/enter"));
startActivity(intent);
- 唤起应用内部Activity(也可以用上面的方式唤起内部Activity),可以用下面的方式:
1、在目标Activity的Manifest.xml中配置如下过滤器
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="artist"
android:host="first"
android:path="/enter"/>
</intent-filter>
2、启动方式:
WebView.loadUrl("artist://first/enter");