Activity

[TOC]

An activity is a single, focused thing that the user can do.

生命周期

complete_android_fragment_lifecycle

启动Activity的请求会由Instruction来处理,然后通过Binder向AMS发送请求,AMS内部维护着一个ActivityTask并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期的调用。

比如说onCreate和onStart是由方法performLaunchActivity()调用。

onResume是由方法handleResumeActivity调用。

系统资源的释放

一般将系统资源的释放放在onPause中,比如Camera、Sensor、Receivers

但是因为onPause是切换前台Activity必须经历的过程,必须旧Activity执行onPause之后,新Activity才能呈现在屏幕上(即使是用singleTop的启动模式,启动相同的Activity,也会执行onPause(),onNewIntent(),onResume()),所以不能在onPause执行耗时的操作。

启动模式

Activity启动模式

  • launchMode

    • standard
      • 注意并不是在默认包名的任务栈,是出现在启动它时候的任务栈。比如TaskB中的Activity启动了它,它会继续停留在TaskB中
    • singleTop
    • singleTask

      • 检查当前栈中是否存在需要启动的Activity, 清理栈顶直到此Activity位于栈顶

      • 很有趣的一点,应该网上都没有讲到,栈里分别有ABC三个Activity,A为栈底且为singleTask启动模式,C启动A,则会先调用B的onDestroy,然后再调用C的onPause,启动A,销毁C

        1
        2
        3
        4
        5
        6
        7
        05-14 22:35:10.961 12758-12758/com.hgdendi.test D/Main2Activity: ====onDestroy: 
        05-14 22:35:10.970 12758-12758/com.hgdendi.test D/Main3Activity: ====onPause:
        05-14 22:35:10.980 12758-12758/com.hgdendi.test D/MainActivity: onNewIntent:
        05-14 22:35:10.983 12758-12758/com.hgdendi.test D/MainActivity: onResume:
        05-14 22:35:11.017 12758-12793/com.hgdendi.test D/OpenGLRenderer: endAllActiveAnimators on 0x7cfc1fec00 (RippleDrawable) with handle 0x7cfc0f1d40
        05-14 22:35:11.288 12758-12758/com.hgdendi.test D/Main3Activity: ====onStop:
        05-14 22:35:11.288 12758-12758/com.hgdendi.test D/Main3Activity: ====onDestroy:
      • 可以与TaskAffinity配合使用,而TaskAffinity也主要与singleTask和allowTaskReparenting配合使用

    • singleInstance
      • 会出现在一个新的任务栈中,且此任务栈中只存在这一个Activity
      • 此新的任务栈可被多个应用共享
      • 应用于需要与程序分离的界面,如调用紧急呼叫,就是使用这种启动模式
  • clearTaskOnLaunch
    • 只对根Activity生效
    • 每次返回该TASK,都会将该TASK之上的所有其他Activity清除
  • finishOnTaskLaunch
    • 当用户离开这个TASK,再返回的时候,该Activity就会被finish掉
  • alwaysRetainTaskState
    • 只对根Activity生效
    • 所在的TASK栈不接受任何清理命令,一直保持Task状态
  • allowTaskReparenting
    • 在应用退到后台时,能否从启动它的那个Task移动到具有公共affinity的task
    • 比如app启动了web浏览器的一个Activity,这个activity此时在app的task上,当app退到后台再进入前台,会发现这个activity已经到了浏览器应用的task中了

IntentFlag启动模式

  • NEW_TASK
    • Service因为不依附任何栈,所以若从Service中创建新的Activity需要使用此方法
  • SINGLE_TOP
    • 同single_top
  • CLEAR_TOP
    • 同single_task
    • 但是如果不与SINGLE_TOP同时指定,可能会导致启动的Activity被销毁然后再以该Intent进行重建(而不是直接调用onNewIntent生命周期)
  • NO_HISTORY
    • 该Activity启动其他Activity后,就消失了

Android任务栈

Android任务栈被称为一个TASK,一个TASK中的Activity可以来自不同的APP,同一个App的Activity也可能不在一个Task中

  • 不同TASK栈之间不能传递数据,只能由Intent传递

    • 比如调用startActivityForResult启动一个SingleInstance模式的Activity,会立即返回Activity.RESULT_CANCELED

Intent

显式

隐式

一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity

action

  • 存在且必须和任意的一个相同

category

  • 0~多个
  • 必须各个都匹配
  • 在startActivity时系统会加上DEFAULT,故而为了使Activity接收隐式调用,必须指定DEFAULT

Data

要求Intent中必须有data数据,并且能够完全匹配过滤规则中的某一个data

1
2
3
4
5
6
7
<data android:scheme = "string"
android:host = "string"
android:port = "string"
android:path = "string"
android:pathPattern = "string"
android:pathPrefix = "string"
android:mimeType = "string"

主要分为URI + MIMETYPE

Mimetype

指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*

可以表示图片、文本、视频等不同的媒体格式

URI
://:/[||]
比如content://com.example.project:200/folder/subfolder/etc

Scheme : URI的模式,比如http、file、content。不指定则只支持file和content

Host : URI的主机名,比如www.baidu.com

Port: URI中的端口号,

Path、pathPattern、pathPrefix: 表述路径信息(*前面加两个斜杠,表示单斜杠需要连打四个斜杠)

同时指定Uri和mimetype需要调用setDataAndType方法

1
2
3
4
5
6
7
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
//获取隐式intent匹配情况
//PackageManager
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);
//Intent
public abstract ResolveInfo resolveActivity(Intent intent,int flags);
//这里flag使用MATCH_DEFAULT_ONLY标记位,仅仅匹配带有DEFAULT的activity

打开应用市场对App进行评分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public ArrayList<String> queryInstalledMarketPkgs(Context context) {
ArrayList<String> pkgs = new ArrayList<String>();
if (context == null)
return pkgs;
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.APP_MARKET");
PackageManager pm = context.getPackageManager();
List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
if (infos == null || infos.size() == 0)
return pkgs;
int size = infos.size();
for (int i = 0; i < size; i++) {
String pkgName = "";
try {
ActivityInfo activityInfo = infos.get(i).activityInfo;
pkgName = activityInfo.packageName;
} catch (Exception e) {
e.printStackTrace();
}
if (!TextUtils.isEmpty(pkgName))
pkgs.add(pkgName);

}
return pkgs;
}

// 评分
private void tryToGradeApp(){
List<String> packages;
if(mContext==null) return;
packages= Utils.queryInstalledMarketPkgs(mContext);

if(null == packages || 0 == packages.size()){
Toast.makeText(mContext,"Do not find any installed market in your phone.",Toast.LENGTH_SHORT).show();
return;
}

String appName = mContext.getPackageName();
Uri uri = Uri.parse("market://details?id=" + appName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);

mContext.startActivity(intent);
}

内存重启

出现异常时候,会由Activity调用onSaveInstantceState方法,之后再由window调用顶层布局各个View的onSavedInstantceState方法。

原则 View的记录信息由View记录,Fragment的记录信息由Fragment记录。

  • intent(Activity)、argument(Fragment)中的信息会被自动保存
  • 其他需要持久化保存的信息需要在onSavedInstantceState()中保存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 自定义View的状态存储一般模式

@Override
public Parcelable onSaveInstanceState() {
return new MySavedState(super.onSaveInstanceState(), mFlag);
}

@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof MySavedState)) {
super.onRestoreInstanceState(state);
return;
}
MySavedState myState = (MySavedState) state;
super.onRestoreInstanceState(myState.savedState);
mFlag = myState.flag;
}

private static class MySavedState implements Parcelable {
// 父类的SavedState
Parcelable savedState;
// 本类自己的属性
boolean flag;

MySavedState(Parcelable savedState, boolean flag) {
this.savedState = savedState;
this.flag = flag;
}
}

故而带Fragment的Activity的建议写法为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ActivityWithFragment{
@Override
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.XXX);

Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (fragment == null) {
// Create the fragment
fragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), fragment, R.id.contentFrame);
}


// Load previously saved state, if available.
if (savedInstanceState != null) {
TasksFilterType currentFiltering =
(TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
mTasksPresenter.setFiltering(currentFiltering);
}
}
}

onSavedInstantceState是在onStop之前启动,但是和onPause没有必然联系。

横竖屏切换

横竖屏切换时Activity的生命周期

1、默认情况下,切屏会重新调用各个生命周期

2、设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法(3.2之前还是会调用各个生命周期)

如果横竖屏的界面布局不同,可以在res下新建layout-land和layout-port目录,然后把布局文件扔到这两个目录文件中

Configuration

描述了设备的所有配置信息,会影响到应用程序检索的资源。包括了用户指定的选项(locale和scaling),也包括设备本身的配置(例如input modes,screen size and screen orientation),可以在该类里查看所有影响Configuration Change的属性

常见的引发Configuration Change的属性:

  • 横竖屏切换
    • android:configChanges=”orientation”
  • 键盘可用性
    • android:configChanges=”keyboardHidden”
  • 屏幕大小变化
    • android:configChanges=”screenSize”
  • 语言的更改
    • android:configChanges=”locale”

可能引发的问题

重走生命流程

界面中用户选择了checkbox和radiobutton选项或者通过网络请求显示在界面上的数据在屏幕旋转后Activity被destroy-recreate,这些空间上被选择的状态和界面上的数据都会消失。

进入某个Activity时加载页面进行网络请求,此时旋转屏幕会重新创建网络请求,这样的用户体验非常不好。

伴随异步操作显示一个progressDialog的话,异步任务未完成去旋转屏幕,程序会因为Activity has leaked window而终止。而当old activity被销毁后,线程执行完毕后还是会把结果返回给old activity而非新的activity

建议要override onSaveInstanceState()方法

onConfigChange()

在onConfigChange()中获取不到新的Layout和控件的尺寸位置信息,必须通过消息异步或者延时调用

不可以动态加载xml,可能会有很多硬代码

Activity过渡动画

提供了三种Transition类型

  • 进入
    • 决定Activity中的所有的视图怎么进入屏幕
  • 退出
    • 决定一个Activity中的所有视图怎么退出屏幕
  • 共享元素
    • 决定两个Activities之间的过渡,怎么共享他们的视图

进入、退出

  • explode
    • 从屏幕中间进或出,移动视图
  • slide
    • 滑动,从屏幕边缘进或出,移动视图
  • fade
    • 淡出,通过改变屏幕上的视图的不透明度达到添加或者移除视图

共享元素

  • changeBounds
    • 改变目标视图的布局边界
  • changeClipBounds
    • 裁剪目标视图边界
  • changeTransform
    • 改变目标视图的缩放比例和旋转角度
  • changeImageTransform
    • 改变目标图片的大小和缩放比例

代码

普通动画

1
2
3
4
5
6
7
8
9
10
11
12
//ActivityA
startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this).toBundle());

//ActivityB先声明后设置
A在代码中声明
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
B在xml中声明
<item name="android:windowContentTransitions">true</item>

具体设置:
getWindow().setEnterTransition(new Explode());
getWindow().setExitTransition(new Slide());

淡出效果

1
2
3
4
5
6
7
8
9
10
11
12
13
在ActivityA和ActivityB中同时定义
android:transitionName="XXX"

如果只需要一个共享元素,可以在ActivityA中:
startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this,view,"share").toBundle());

如果需要多个共享元素
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(
this,
Pair.Create(view,"share"),Pair.create(fab,"fab")
).toBundle()
);

定义在Theme中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowAnimationStyle">@style/ActivityAnimTheme</item>
</style>

<style name="ActivityAnimTheme" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/activity_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/activity_right_out</item>
<item name="android:activityCloseEnterAnimation">@anim/activity_right_in</item>
<item name="android:activityCloseExitAnimation">@anim/activity_right_out</item>
</style>

//下面是xml的sample
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="100%"
android:fromYDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXDelta="0%"
android:toYDelta="0" />
</set>
0%