Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
我们新建的工程中带有一个基础 activity。
新建工程中,需要注意3个文件。
这3个文件分布在不同的地方。简单来说,java文件可以控制界面逻辑。 layout文件(这里指的是activity_main.xml)预设了UI如何摆放。 清单文件告诉系统,我这个app有哪些组件,申请了什么权限。
新建的 layout 中,as 一般会默认给一个ConstraintLayout
。比如 activity_main.xml。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 省略默认的TextView -->
</androidx.constraintlayout.widget.ConstraintLayout>
这里为了用起来方便,我们把它换成LinearLayout 有的朋友会问,都2021年了,为什么不直接用 ConstraintLayout?
现在不做什么功能,先用LinearLayout,就是为了方便。 换成LinearLayout后,layout文件长这样。
换成LinearLayout后的activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 省略默认的TextView -->
</LinearLayout>
可以看到,标签的开头和结尾都换成了LinearLayout
。其他地方暂时不修改。
as功能强大,非常便利。我们可以用鼠标选中标签开始的androidx...Layout
,然后直接键盘输入LinearLayout
的前几位字母。
as会自动弹出选择框,在里面双击选择LinearLayout
或者回车选择,标签就替换完成了。
layout文件设计的是界面的初始布局。它决定了初始界面上放着什么UI组件以及组件是怎么组织安排的。
这里我们说的是「初始界面」或者「初始布局」。也就是说,我们可以控制界面上的UI元素。
先看默认的 MainActivity.java。在onCreate
方法里,R.layout.activity_main
指的就是activity_main.xml。
现在layout中有一个TextView,它可以用来显示文字。我们想在MainActivity
中控制它,该怎么做呢?
现在改一下这个TextView。删掉原来ConstraintLayout用到的那些属性。
给它添加一个id。这个id在这个layout文件中必须是独一无二的。给它分配一个id叫做tv1
,就像下面。
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
现在TextView有了身份证,我们在activity中就可以找到它。用的是findViewById
方法。
TextView tv1 = findViewById(R.id.tv1);
现在我们就拿到了界面上的这个TextView对象。可以对它进行操作了。 比如改变它显示的文字。
TextView tv1 = findViewById(R.id.tv1); // 拿到textView的对象
tv1.setText("Today is a good day."); // 改变文字
也可以简称为「manifest文件」。清单文件非常重要,它告诉系统我们的app有哪些activity,用到了什么权限等等信息。
如果要新建activity,需要在清单中注册。
AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
从这个默认的清单文件中我们可以得知,activity 是属于 application 的。application 就是我们的应用。
application 标签中也指定了各种元素,例如应用的图标,名字,主题等等。
MainActivity 是应用启动的第一个 activity。可以观察到它设置了 action 和category 属性。
android.intent.action.MAIN
决定应用程序最先启动的Activity。android.intent.category.LAUNCHER
表示可以在手机“桌面”上看到应用图标。设置了这 2 个标签,决定了这个 activity 是用户点击应用图标时第一个启动的界面。
小结 activity是应用重要的组件之一。纷繁复杂的内容需要activity来承载。
之后我们会在activity中控制各种各样的UI组件,处理用户的操作,申请权限等等。还要了解activity的生命周期,启动方式和跳转方法。
生命周期图示
执行一些常见的操作,打log看一下生命周期的变化。测试机型:RedMi。
启动然后退出
onCreate
onStart
onResume
onWindowFocusChanged: hasFocus: true
onWindowFocusChanged: hasFocus: false
onPause
onStop
onDestroy
启动后按home键
Act1: onCreate
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
// 按home键
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
// 再回来
Act1: onRestart
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
// 按返回键退出act
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
Act1: onDestroy
旋转手机 activity 在切换横竖屏的时候的生命周期。
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
// 横屏
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
// 竖屏
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
// 返回
[Life]: onWindowFocusChanged: hasFocus: false
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
来回切换的生命周期变化 以2个Activity启动为例。
Act1: onCreate
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
Act1: onPause
Act1: onWindowFocusChanged: hasFocus: false
Act2: onCreate
Act2: onStart
Act2: onResume
Act2: onWindowFocusChanged: hasFocus: true
Act1: onStop
Act2: onWindowFocusChanged: hasFocus: false
Act2: onPause
Act1: onRestart
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
Act2: onStop
Act2: onDestroy
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
Act1: onDestroy
弹出 AlertDialog
点击按钮弹出一个AlertDialog
。观察发现调用 onWindowFocusChanged
。
onWindowFocusChanged: hasFocus: false
onWindowFocusChanged: hasFocus: true
这里也可以用 DialogFragment 来做测试。
recreate 调用 recreate() 方法
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
[Life]: click [recreate]
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
可以看到,调用recreate()
方法后并没有走onWindowFocusChanged
回调。
activity的状态区别
onCreate
在系统首次创建 Activity 时触发。Activity会在创建后进入已创建状态。onStart()
调用使 Activity 对用户可见,因为应用会为 Activity 进入前台并支持交互做准备。onStart()
方法会非常快速地完成,并且与“已创建”状态一样,Activity 不会一直处于“已开始”状态。一旦此回调结束,Activity 便会进入已恢复状态,系统将调用 onResume()
方法。
onPause()
执行非常简单,而且不一定要有足够的时间来执行保存操作。 因此,您不应使用 onPause()
来保存应用或用户数据、进行网络调用,或执行数据库事务。因为在该方法完成之前,此类工作可能无法完成。
已进入已停止状态,因此系统将调用 onStop()
回调。举例而言,如果新启动的 Activity 覆盖整个屏幕,就可能会发生这种情况。
在 onStop()
方法中,应用应释放或调整应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从细粒度位置更新切换到粗粒度位置更新。 使用 onStop()
而非 onPause()
可确保与界面相关的工作继续进行,即使用户在多窗口模式下查看您的 Activity 也能如此。 您还应该使用 onStop()
执行 CPU 相对密集的关闭操作。
前面大致了解了Activity是一个应用组件,能为用户提供一个界面。以及如何新增activity。 一个App中,通常有多个界面。假设每一个界面对应一个activity,不同界面之间怎么跳转呢?
通常activity之间的跳转离不开Intent这个类。 Intent,直译为“意图”。我们把信息包裹在intent对象中,然后执行。 比如启动RelativeLayoutGuideAct
这个activity。
startActivity(new Intent(getApplicationContext(), RelativeLayoutGuideAct.class));
这里用到一个很常见的方法startActivity (Intent intent)
。 startActivity
属于Context类,Activity是Context的子类。
java.lang.Object
android.content.Context
android.content.ContextWrapper
android.view.ContextThemeWrapper
android.app.Activity
现在我们知道了,启动activity需要使用Intent,调用startActivity
方法。
在跳转去下一个页面时,我们可能会想携带一些信息到下一个界面去。例如携带一些文本,数字等等。 或者是一个对象。 这些信息我们可以交给Intent,传递到下一个activity去。下一个activity中拿到我们传入的Intent。
携带基本类型和String
我们直接看intent的方法。
Intent intent = new Intent(getApplicationContext(), SendParamsDemo.class);
intent.putExtra(SendParamsDemo.K_INT, 100);
intent.putExtra(SendParamsDemo.K_BOOL, true);
intent.putExtra(SendParamsDemo.K_STR, "Input string");
startActivity(intent);
intent的putExtra
方法,可以传入参数。它接受1个String作为key,然后是具体参数。 例子中我们跳转去了SendParamsDemo。
public class SendParamsDemo extends AbsActivity {
public static final String K_INT = "k_int";
public static final String K_BOOL = "k_bool";
public static final String K_STR = "k_str";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gotInput();
}
private void gotInput() {
Intent intent = getIntent();
if (intent != null) {
int i = intent.getIntExtra(K_INT, -1);
boolean b = intent.getBooleanExtra(K_BOOL, false);
String str = intent.getStringExtra(K_STR);
Log.d(TAG, "gotInput: i:" + i + ", b: " + b + ", str: " + str);
} else {
Log.d(TAG, "gotInput: input null.");
}
}
}
// log:
// com.rustfisher.tutorial2020 D/rustAppSendParamsDemo: gotInput: i:100, b: true, str: Input string
在这个activity中我们接收到了传入的参数。
观察intent的putExtra
方法,我们发现它支持传入很多种参数。
int,byte, char, float, double, long, boolean,string,CharSequence或是它们的数组。 也可以传入Parcelable,Serializable对象或是对象数组。
传入Serializable对象
除了基本类型和String,可以传送对象吗? 答案是肯定的。Intent可以携带Serializable
对象。 Serializable
本身是一个接口,自定义的对象实现这个接口后,就可以被Intent携带。 比如我们改造一下DataTest类,让它实现Serializable
接口。
public class DataTest implements Serializable { // 实现接口
然后将对象送给intent,再启动activity。
Intent intent = new Intent(getApplicationContext(), RecyclerViewDemo2Act.class);
DataTest out = new DataTest("input time", 233, 666, 999);
Log.d(TAG, "startInputData: sending object: " + out);
intent.putExtra(RecyclerViewDemo2Act.K_INPUT_DATA, out);
startActivity(intent);
被启动的activity接受传入的intent并取出对象。
Intent intent = getIntent();
if (intent != null) {
DataTest d = (DataTest) intent.getSerializableExtra(K_INPUT_DATA);
// 取出了对象,拿去显示
}
Serializable
接口不含任何方法。实现了这个接口的类,系统会自动将其序列化。
我们打印出发送和接收到的对象。
startInputData: sending object: com.rustfisher.tutorial2020.recycler.data.DataTest@fb43df5
getInputData: input data object: com.rustfisher.tutorial2020.recycler.data.DataTest@a588b5c
可以发现这2个对象并不是同一个引用。但它们的“内容”是一样的。对象经历了序列化和反序列化的过程。
值得注意的是,Intent 能携带的对象大小并不是无限制的。实际开发中,需要开发者自己预估传输的数据大小。
传送 Parcelable 对象和传送 Serializable 对象类似,用同样的存入和取出操作。
首先理解android是使用Task来管理活动,一个Task就是一组存放在栈里的活动的集合,这个栈就叫做返回栈,每启动一个新的活动,就会将其放入栈顶,当我们点击back回退或调用activity的finish函数处于栈顶的活动就会出栈,前一个入栈的活动就会到栈顶,系统总是显示处于栈顶的活动。
活动的生存期分为三个:
完整生存期:onCreate()方法与onDestory()都处于完整生存期,一般情况下,Activity会在onCreate()方法中完成各种初始化操作,而在onDestory()方法中完成释放内存的操作。
可见生存期:onStart()方法与onStop()方法就是可见生存期,Activity对于用户是可见的,但无法与用户交互。onStart()方法中对资源进行加载,onStop()方法中对资源进行释放。
前台生存期:onResume方法与onPause方法就是前台生存期,在前台生存期内,活动处于运行状态,此时可以与用户交互。
onRestart() -> onStart() -> onResume() 然后重新running
当Activity结束(调用finish()方法)就会调用onDestory()方法释放所有占用的资源。
生命周期的切换过程
Activity的优先级
优先级低的 Activity 在内存不足被回收后重新打开(横竖屏切换的过程中)会引发Activity重建。
在 Activity 由于异常情况被终止时,系统会调用 onSaveInstanceState
方法来保存当前 Activity 的状态,该方法调用于 onStop 之前,与 onPause 方法没有时序关系。
当异常终止的 Activity 被重建时,会调用 onRestoreInstanceState
方法(该方法在 onStart 之后),并且把 Activity 销毁时 onSaveInstanceState
保存的 Bundle 对象参数同时传递给 onCreate
方法和onRestoreInstanceState
方法。该方法的调用是在 onStart
之前。
因此可通过 onRestoreInstanceState(Bundle savedInstanceState)
和 onCreate((Bundle savedInstanceState)
来判断 Activity
是否被重建,并取出数据进行恢复。但需要注意的是,在 onCreate
取出数据时一定要先判断savedInstanceState
是否为空。
补充:其中 onCreate 和 onRestoreInstanceState
方法来恢复 Activity 的状态的区别: onRestoreInstanceState
方法回调则说明 bundle 对象非空,不需要加非空判断,而 onCreate 需要非空判断。
onSaveInstanceState()
与 onRestoreIntanceState()
onSaveInstanceState() 这两个方法并不是生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用,该方法的调用在onStop之前,与onPause没有时序关系。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。
onSaveInstanceState()
时机:
(1)用户按下Home键
(2)横竖屏切换
(3)按下电源按钮(关闭屏幕显示)
(4)内存不足导致优先级的Activity被杀死
onRestoreIntanceState() 当被系统异常销毁的 Activity 被重建时,会调用 onRestoreIntanceState 或 onCreate 方法来恢复,而 onRestoreInstance 与 Oncreate 方法中传入的 Bundle 对象是销毁时 onSaveInstanceState 保存的,onRestoreIntanceState 在 onStart之后。
onSaveInstanceState()
只适合用于保存一些临时性的状态,而onPause()
适合用于数据的持久化保存。
要切记这里活动已经被销毁了。 onPause->onSaveInstanceState->onStop->onDestory()->onCreate->onStart->onRestoreIntanceState->onResume
通过对AndroidManifest文件的Activity中指定(configChanges)属性:
android:configChanges = “orientation| screensize”
来避免横竖屏切换时,Activity的销毁和重建,而是回调了onCofigurationChanged()方法
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
这里附上android configChanges 的所有属性解释
“mcc“ 移动国家号码,由三位数字组成,每个国家都有自己独立的MCC,可以识别手机用户所属国家。
“mnc“ 移动网号,在一个国家或者地区中,用于区分手机用户的服务商。
“locale“ 所在地区发生变化。
“touchscreen“ 触摸屏已经改变。(这不应该常发生。)
“keyboard“ 键盘模式发生变化,例如:用户接入外部键盘输入。
“keyboardHidden“ 用户打开手机硬件键盘
“navigation“ 导航型发生了变化。(这不应该常发生。)
“orientation“ 设备旋转,横向显示和竖向显示模式切换。
“fontScale“ 全局字体大小缩放发生改变
singleTop 模式的含义是(参考上面问题),singleTask 模式的含义是(参考上面问题)
因此二者的差别为:
有两个调用时机,分别是singleTop模式下与singleTask模式下启动Activity。 singleTop模式:当启动的Activity是在任务栈的栈顶时,会回调onNewIntent方法。 singleTask模式:当启动的Activity存在于任务栈中,会回调onNewIntent方法。
更多Android零基础入门教程学习参考:
https://www.bilibili.com/video/BV19r4y117cF
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。