概念
Activity是一个应用的组件,他在屏幕上提供一个区域,这个区域用户可以操作,与之交互,他可以充满整个屏幕或者比屏幕小,也可以在其他窗口的上方,总之一句话:它用于显示用户操作界面,用户可以与他交互,一个应用可以有多个Activity,负责UI元素的加载和页面的跳转,代表一个页面单元
1. 生命周期
代码表示:
public class TestLifeCycleActivity extends AppCompatActivity {
@Override //创建基本的ui元素,一般进行界面的初始化,获取上个界面传来的参数
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_life_cycle);
}
//这个方法的调用时机,是stop之后,没有destory的话
//(比如,切到别的应用,或者按home键回到桌面等),再次回到这个界面就回调这个方法,然后再点用onstart方法
@Override //界面从不可见变为可见执行的方法
protected void onRestart() {
super.onRestart();
}
@Override //界面已经显示在屏幕上,但是还没有获得焦点
protected void onStart() {
super.onStart();
}
@Override //界面获得焦点
protected void onResume() {
super.onResume();
}
@Override //失去焦点,但是依然可见,不能和用户交互
protected void onPause() {
super.onPause();
}
@Override //界面不可见,清除活动的资源,避免浪费
protected void onStop() {
super.onStop();
}
@Override //界面销毁,在这里销毁Handler的引用,清楚开启的线程
protected void onDestroy() {
super.onDestroy();
}
@Override //当主动调用finish()方法的时候,系统不会调用这个方法(这个方法会在离开前台时触发,触发时机在onStop()之前)
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d("TestLifeCycleActivity", "执行了保存状态");
}
这里有几个问题:
- A activity跳转到 B activity的生命周期
- A onPause() —> 这里还会调用onSaveInstanceState()方法,不属于声明周期,属于方法
- B onCreate()
- B onStart()
- B onResume()
- A onStop()
- dialog 和nitifcation对生命周期的影响
在Activity中弹出Dialog或者PopuWindow,是不会走onPause()方法的,因为这个方法是另外一个Activity启动后才调用的,如果想要调用这个,需要的是一个Dialog样式的Activity才可以
1.1 横竖屏切换对生命周期的影响
分为两种情况:
1.1.1 设置Activity的android:configChanges属性为orientation|screenSize
在进行横竖屏切换的时候调用的方法是onConfigurationChanged,而不会重新加载Activity的各个生命周期
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
{
System.out.println("现在是横屏转竖屏");
}else if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
{
System.out.println("现在是竖屏转横屏");
}
}
1.1.2 没有设置
会完整的走一遍生命周期
onPause()-->onStop()-->onDestory-->onCreate()-->onStart()-->onResume()
横竖屏切换就是摧毁一个Activity再重建的过程,最主要的是保证数据不丢失
保存数据还是要使用下面这个方法
@Override
public void onSaveInstanceState(Bundle outState) { //这个方法要写自己的逻辑的,Bundle存储键值对
Log.i("linc", "onSaveInstanceState(Bundle)");
super.onSaveInstanceState(outState);
outState.putString(TEXT_ONE, ""+editText1.getTag(R.id.tag_first));//avoid null point
// outState.putSerializable();//object
}
在onCreate()中把数据取出来
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_land_port_switch);
Log.e("linc","oncreate");
editText1 = (EditText)findViewById(R.id.txt1);
if (savedInstanceState != null) {
editText1.setTag(R.id.tag_first,savedInstanceState.getString(TEXT_ONE));
}
}
因为onSaveInstanceState()不是总被调用,所以你应该只用它来为activity保存一些临时的状态,而不能用来保存持久性数据。而是应该用onPause()来达到这个目的。
onSaveInstanceState()的调用时机:
- 当用户按下HOME键时。
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
- 长按HOME键,选择运行其他的程序时。
- 按下电源按键(关闭屏幕显示)时。
- 从activity A中启动一个新的activity时。
- 屏幕方向切换时,例如从竖屏切换到横屏时。(如果不指定configchange属性) 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行
2. Activity的跳转
2.1 显示跳转
跳转代码:
2.1.1 startActivity(new Intent(MainActivity.this,SecondActivity.class));
实际开发中,都是在第二类中写一个静态launch方法,将需要的参数传递过来
public static void launch(Activity activity, String teacherId) {
Intent intent = new Intent(activity, SingleTeacherActivity.class);
intent.putExtra("teacherId", teacherId + ""); //年级id
activity.startActivity(intent);
}
2.1.2 通过ComponentName
启动指定界面
Intent intent = new Intent("com.autopai.system.settings.ACTION");
ComponentName cn = new ComponentName("com.autopai.system.settings", "com.autopai.system.settings.MainActivity");
intent.setComponent(cn);
intent.putExtra("module", "bluetooth");//蓝牙
if (ActivityUtils.isActivityResponse(intent)) { //判断是否有响应的,不然会发生异常,和隐式启动的判断方式相同
mMainActivity.startActivity(intent);
} else {
Log.e(TAG, "ActivityNotFoundException: No Activity found to handle Intent");
}
2.2 隐式跳转
跳转代码:
Intent intent = new Intent();
//启动系统自带的拨号器应用
intent.setAction(Intent.ACTION_DIAL);
startActivity(intent);
要让一个Activity可以被隐式启动,需要在清单文件的activity节点中设置intent-filter子节点
<intent-filter >
<action android:name="com.itheima.second"/>
<data android:scheme="asd" android:mimeType="aa/bb"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
注意的点:
action 指定动作(可以自定义,可以使用系统自带的)
data 指定数据(操作什么内容)
category 类别 (默认类别,机顶盒,车载电脑)
隐式意图启动Activity,需要为intent设置以上三个属性,且值必须与该Activity在清单文件中对三个属性的定义匹配
隐式启动时,如果没有匹配的activity响应的话,程序会崩掉
隐式启动,action只能有一个,category可以有多个
注意:隐式启动时,如果没有匹配的activity响应的话,程序会崩掉
需要添加防护:
/**
* 隐式启动判断是否有对应的Activity响应
* @param intent 启动的Intent
* @return 有相应返回true
*/
public static boolean isActivityResponse(Intent intent) {
PackageManager pm = MusicApplication.getInstance().getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
if (null == activities || activities.size() <= 0) {
//不存在匹配跳转隐式intent的Activity
return false;
} else {
//存在匹配跳转隐式intent的Activity
return true;
}
}
可以使用上述代码,先进行判断,或者进行try,catch,将异常捕获
3. Activity之间传递数据
3.1 使用Bundle传递数据
Intent intent = new Intent(this, YinYuanActivity.class);
//Activity跳转时传递数据,是通过把数据封装在意图中实现的
Bundle bundle = new Bundle();
bundle.putString("maleName", maleName);
bundle.putString("femaleName", femaleName);
intent.putExtras(bundle);
startActivity(intent);
取出数据
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String maleName = bundle.getString("maleName");
String femaleName = bundle.getString("femaleName");
3.2 使用Intent传递数据
Activity通过Intent启动时,可以通过Intent对象携带数据到目标Activity
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("maleName", maleName);
intent.putExtra("femaleName", femaleName);
startActivity(intent);
取出数据
Intent intent = getIntent();
String maleName = intent.getStringExtra("maleName");
String femaleName = intent.getStringExtra("femaleName");
3.3 startActivityForResult()
A->B(返回数据)->A
A部分的代码:
startActivityForResult(Intent intent, int requestCode); //requestCode一般会定义一个静态常量
//通过data获取返回的数据 这里是简单处理了,只有一个界面返回的情况下可以这么处理,但是多个界面返回的情况下还要对requestCode进行判断
onActivityResult(int requestCode, int resultCode, Intent data) { //这里还需要对data是否为空进行判断
if(resultCode == RESULT_OK){
String returnedDate = data.getStringExtra("phone");
}
}
B部分代码:
Intent intnet = new Intent();
intnet.putExtra("phone", phone);
//设置一个结果数据,数据会返回给调用者
setResult(RESULT_OK, intnet); //这个方法是用来给上一个活动返回数据的,第一个参数一般是两个值,RESULT_OK和RESULT_CANCELED,第二个参数是intnet
finish();//关闭掉当前的activity,才会返回数据
界面跳转的小问题:A界面有两个地方可以跳转到B界面,需要判断是从哪里跳转的
A界面有两个地方跳到B界面,携带不同的参数,在B界面需要判断是哪个地方跳转的,然后做不同的处理:(阅卷易处理头像时使用到)
Intent intent = getIntent();
if (intent != null) {
int key = intent.getIntExtra("key", 0);
if (key == 1){ //本地图片过来的
int bitmapId = intent.getIntExtra("bitmapId", 0);
mBitmap =BitmapFactory.decodeResource(this.getResources(), bitmapId);
Log.d("ClipPhotoActivity", "本地");
}else {
String url = intent.getStringExtra("filePath");
mBitmap = decodeSampledBitmapFromResource(url, 300, 500);
Log.d("ClipPhotoActivity", "图库");
}
}
A界面开启的时候,传一个key,B界面根据key去做判断
Intent intent = new Intent(ChoosePhotoActivity.this, ClipPhotoActivity.class);
intent.putExtra("bitmapId", mData.get(position).phonePath);//图片资源id
intent.putExtra("key",1);
startActivityForResult(intent, REQUESTCODE_CLIPPHOTO);
Intent intent = new Intent(ChoosePhotoActivity.this, ClipPhotoActivity.class);
intent.putExtra("filePath", filePath);
intent.putExtra("key",2);
startActivityForResult(intent, REQUESTCODE_CLIPPHOTO);
这个方法需要注意的点:
- B界面按返回键退出界面,这就需要对返回键进行复写,执行上面的逻辑操作
@Override
public void onBackPressed() {
super.onBackPressed();
Log.d("SecondActivity", "我是按下的物理键");
}
- 启动模式对这个方法的影响
startActivityForResult方法能够起效:standard和singleTop
startActivityForResult方法不能够起效:singleTask和singleInstance
只要将被启动的Activity属性设置为singleTask则一定不起效
只要将被启动的Activity属性设置为singleInstance则一定不起效
只要将启动的Activity模式设置为singleInstance则不论被启动的Activity为什么模式均不起效
3.4 可以使用EventBus等三方手段进行数据的传输
3.5 广播等
4/5 一般是Activity比较深的时候使用,性能有影响
4. 启动模式
任务栈简介:一个Task(栈,先进后出)中的Activity可能来自不同的App,同一个App的Activity可能不在一个Task中
4.1 standard
一个命令一个页面实例,可以连续启动本页面,栈结构:A->A->A是可以的
4.2 singleTop
如果要启动的activity在栈顶,就不会启动新的,会重用位于栈顶的那个实例(不是会创建新的实例,而是会调用onNewIntent()方法),如果存在于任务栈但是不在栈顶的话,会启动新的activity(和标准模式一样)
栈结构:A->B->A 是可以的,A->A是不可以的
4.3 singleTask
只允许栈中有一个实例,没有的话创建,如果已经存在并且在栈顶,就不会创建新的,直接复用;如果不在栈顶,会移除它上面的所有activity,从而升到栈顶;((不是会创建新的实例,而是会调用onNewIntent()方法)
栈结构 A->B->C->A 这样调用之后,会将BC都移除,只剩下A
对第三种模式的一个小例子:
比如A和B在同一个栈中(A在B上面),X,Y,Z在一个栈中(X在Y上面,Y在Z上面),从A界面启动Y界面(Y是singleTask模式),则Y所在的栈将切换到前台来,那么从Y界面按返回键是会先到Z界面,Z界面继续点返回键才会到A界面,再返回到B界面,这里一定要理解,返回时先返回到自己的这个栈里面的界面
4.4 singleInstance
同一时刻有且只有一个实例,单独在一个栈中,当再次调用,也是调用onNewIntent()方法;
(一般用于需要共享的Activity的启动模式,供其他的应用调用) 申明为singleInstance的Activity会出现在一个新的任务栈中,而且该任务栈只会存在这一个Activity,这种模式常用于需要和程序分离的界面,有点类似于浏器的工作原理,无论从哪个task中启动,系统中只有这一个activity,如果没有就创建,并将它加入到task的栈顶;如果存在就直接启动原来的复用
一般都需要在系统内保持唯一,例如:电话拨打页面,拍照页面等,开发中几乎用不到
对第四种模式的一个小例子:
比如A–>B–>C 是这样的启动顺序,如果B界面使用了singleInstance()作为启动模式的话,那么从C界面返回会先到A界面,再返回到B界面,最后退出程序,这是因为,A和C是存放在同一个返回栈的,当在C界面按返回键时,C界面就会出栈,那么A界面就成了栈顶的界面,所以就形成了从C到A的情况,在A界面再按返回键,因为当前返回栈已经空了,于是就显示了另一个返回栈的栈顶活动,即B界面,在B界面再返回时,这时所有的返回栈都空了,也就自然的退出了程序
栈和栈之间默认是不能传递数据的,系统在framework层做了限制,如果一定要传数据,需要使用Intent绑定数据(startActivityForResult()设置不同的启动模式就会不同的效果)
开启Activity模式的方法:
- 清单文件注册
- 代码中设置Intent Flag启动模式
4.5 官方一段对Task的解释
5. Activity清单文件的配置信息
5.1 android:windowSoftInputMode属性详解,这个属性影响下面两点:
- 当有焦点产生时,软键盘是隐藏还是显示
- 是否减少主窗口大小以便腾出空间释放软键盘
下面是各值的代表:
【A】stateUnspecified:软键盘的状态并没有指定,系统将选择一个合适的状态或依赖于主题的设置
【B】stateUnchanged:当这个activity出现时,软键盘将一直保持在上一个activity里的状态,无论是隐藏还是显示
【C】stateHidden:用户选择activity时,软键盘总是被隐藏
【D】stateAlwaysHidden:当该Activity主窗口获取焦点时,软键盘也总是被隐藏的
【E】stateVisible:软键盘通常是可见的
【F】stateAlwaysVisible:用户选择activity时,软键盘总是显示的状态
【G】adjustUnspecified:默认设置,通常由系统自行决定是隐藏还是显示
【H】adjustResize:该Activity总是调整屏幕的大小以便留出软键盘的空间
【I】adjustPan:当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分
5.2 android:screenOrientation属性详解,这个是控制横竖屏切换的问题
下面是各值的代表:
landscape:限制界面为横屏,旋转屏幕也不会改变当前状态。
portrait:限制界面为竖屏,旋转屏幕也不会改变当前状态。
sensor:根据传感器定位方向,旋转手机90度,180,270,360,界面都会发生变化。
sensorLandscape:(横屏的旋转,不会出现竖屏的现象)根据传感器定位方向,旋转手机180度界面旋转。一般横屏游戏会是这个属性。
sensorPortrait:(竖屏的旋转,不会出现横屏的现象)根据传感器定位方向,旋转手机180度界面会旋转。
unspecified:由系统选择显示方向,不同的设备可能会有所不同。(旋转手机,界面会跟着旋转)
user:用户当前的首选方向。
nosensor:不由传感器确定方向。旋转设备的时候,界面不会跟着旋转。初始界面方向由系统提供。
未完待续,需要持续优化…