接下来,让我们先从看得见的入手,具体学习一下四大组件之一 —— 活动吧!
1 活动的基本用法
现在让我们手动创建一个活动,这样对活动的认识会更加深刻。
- 创建一个活动
- 建一个空工程(Add No Activity),取名 ActivityTest
- 项目结构手动改为 Project 模式
- 手动创建活动:app/src/main/java/com.example.activitytest → New → Activity → Empty Activity
- 创建一个布局
app/src/main/res 创建一个文件夹 layout,在里面创建布局文件:first_layout.xml,并将布局修改为线性布局,并添加一个按钮
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
① <Button>创建一个按钮
② 若需要在 XML 文件中定义一个 id,则需要使用“@+id/name”语法
③ layout_width / layout_height:元素宽/高
④ match_parent 同父元素一样
⑤ wrap_content 刚好包含里里面内容即可
⑥ fill_parent 等同于④,官方推荐使用 match_parent
-->
<Button
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"/>
</LinearLayout>
- 在 AndroidManifest 文件中注册
可以看到Android Studio已经帮我们注册了:
现在我们来声明一个 FirstActivity为主活动。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.activitytest">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ActivityTest"
tools:targetApi="31">
<!--
android:exported:是否支持其它应用调用当前组件。
如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false。
-->
<activity
android:name=".FirstActivity"
android:exported="true"
android:label="This is FirstActivity!">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- 在 FirstActivity.java 引入布局
package com.example.activitytest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
}
}
- 这样我们就成功创建好了一个活动,点击运行你就可以看到一个有一个按钮的界面
注意:如果你的应用程序中没有声明任何一个活动作为主活动,虽然这个程序是可以安装的,但你却无法在启动器中看到或者打开这个程序。
2 认识相关控件
2.1 Toast
Toast 是 Android 系统提供的一种非常好的提醒方式,可以提醒一些短小的信息
通知给用户,会在一段时间后自动消失
,不会占用任何屏幕空间
。
/*
makeTest(Context, 显示的内容, 显示时长)需要3个参数。
第三个参数有两个内置常量:Toast.LENGTH_SHORT / Toast.LENGTH_LONG
*/
Toast.makeText(FirstActivity.this, "You Clicked Button 1", Toast.LENGTH_SHORT).show();
2.2 Menu
Android 提供了一种让菜单展示的同时,还能不占用任何屏幕空间
的方式。
/res/ 创建 menu 文件夹,新建一个文件:main.xml
<item> 标签:用来创建具体的菜单项
- 在活动中重写
onCreateOptionsMenu()
方法,并重写onOptionsItemSelected()
方法来定义菜单相应事件。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 参数①:指定通过哪个资源文件创建菜单
// 参数②:指定将菜单项添加到哪个Menu对象中
getMenuInflater().inflate(R.menu.main,menu); // 给当前活动创建菜单
return true; // 表示允许创建的菜单显示出来。
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()){ // item.getItemId() 获取点击的菜单项
case R.id.add_item:
Toast.makeText(this,"Add",Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this,"remove",Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
3 销毁一个活动
主要有两个方法:
- 按 Back 键
- 通过代码销毁:
finish()
4 使用 Intent 在活动之间穿梭
Intent 是 Android 程序中各组件之间交互的一种重要方式
,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据
。Intent 一般可以被用于启动活动、启动服务以及广播等场景
。
Intent可分为:显示Intent 和 隐式Intent。
4.1 显示 Intent
// 参数1:启动活动的上下文
// 参数2:要启动的活动
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
// 上述代码意图:在活动 FirstActivity 中启动 SecondActivity
4.2 隐式 Intent
隐式 Intent 并不指出我们想要启动哪一个活动
,而是指定了一系列更为抽象的 action
和 category
等信息,然后交由系统去分析这个 Intent,并帮我们找到合适的活动去启动。
- 修改 AndroidManifest.xml :在要启动的活动中添加如下代码
<!--
<action>标签中指明当前活动可以相应 com.example.activitytest.ACTION_START 这个 action
<category>标签则包含一些附加信息,更加精确的指明了当前活动能相应的 Intent 还可能带有的 category
-->
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
只有
<action>
和<category>
中的内容同时能够匹配上 Intent 中指定的 action 和 category
时,这个活动才能相应 Intent。
4.2.1 默认 category
Intent intent = new Intent("com.example.activitytest.ACTION_START");
// 这里没有指明 category,系统默认为 android.intent.category.DEFAULT
startActivity(intent);
4.2.2 显示指定 category
每个 Intent 中只能指定
一个 action
,但却能够指定多个 category
。
使用
addCategory()
来添加一个 category。
【注意:此时将无默认的 category !!!】
Intent intent = new Intent("com.example.activitytest.ACTION_START");
intent.addCategory("android.intent.category.MY_CREATION");
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.MY_CREATION"/>
<!-- 注意:如果没有
<category android:name="android.intent.category.DEFAULT"/>
将会报错!
-->
4.3 更多隐式 Intent 的用法
使用隐式 Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动
,这使得 Android 多个应用程序之间的功能共享成为可能。
例1:在活动中调用系统浏览器启动百度网页:
Intent.ACTION_VIEW
系统内置的动作,其常量值为 android.intent.action.view
Intent intent = new Intent(Intent.ACTION_VIEW);
// Uri.parse()将网址字符串解析成Uri对象,再通过setData传递进去。
// setData() 主要用于指定当前 Intent 正在操作的数据。
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
例2:在程序中调用系统拨号界面
Intent.ACTION_DIAL
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
同时,我们还可以在<intent-filter>标签中再配置一个<data>标签,更加精确地指定当前活动能够相应什么类型的数据。一般在<data>标签不会指定过多内容。
<data>标签主要可以配置以下内容:
- android:scheme:指定数据的协议部分,如 http、geo(显示地理位置)、tel(拨打电话)
- android:host:指定数据的主机名部分,如 www.baidu.com
- android:port:指定数据的端口号
- android:path:指定主机名和端口之后的部分
- android:mimeType:指定可以处理的数据类型,允许使用通配符的方式进行指定。
<data android:scheme="http"/>
只有<data>标签中指定的内容与Intent中携带的Data完全一致时,当前活动才能响应 Intent。
4.4 向下一个活动传递数据
上面我们学习了如何通过Intent来启动活动,现在我们来学习一下:如何通过Intent在启动活动时传递数据。
思路:将要传递的数据暂存在 Intent,到另一个活动中再从 Intent 中取出来。
- putExtra():intent.putExtra(“extra_data”, data);
- 接收两个参数:(键,传递的数据)
- getStringExtra():intent.getStringExtra(“extra_data”);
- 接收一个参数:(要取得数据对应的键)
- getIntExtra()、getBooleanExtra()…… 根据要取的数据的数据类型选择
4.5 返回数据给上一个活动
既然可以传递数据给下一个活动,同样也可以返回数据给上一个活动!
之前我们都是通过 startActivity() 启动活动的,其实 Activity 还有一个启动活动的方法:
- startActivityForResult():在
活动销毁时
能够返回一个结果给上一个活动- 接收两个参数:(Intent,请求码)
- 其中请求码用于之后回调中判断数据的来源,是个唯一值即可。
- putExtra():两个参数(键,要返回的数据)
- setResult():向上一个活动返回数据
- 接收两个参数:(处理结果,intent)
- 处理结果有两个内置常量:RESULT_OK、RESULT_CANCELED
- getStringExtra():一个参数(键)
SecondActivity 销毁活动后会调用前一个活动的 onActivityResult()方法,所以要在 FirstActivity 重写
onActivityResult
方法,以获得返回的数据。
// FirstActivity
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent,1);
// SecondActivity
Intent intent = new Intent();
intent.putExtra("data_return","Hello");
setResult(RESULT_OK,intent);
finish()
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
System.out.println(returnedData);
Toast.makeText(FirstActivity.this,returnedData,Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
此时也许你会问:如果我是按 back 键返回的呢?
答:这也有解决方法,在 SecondActivity 重写onBackPressed()
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("return_data","Hello");
setResult(RESULT_OK,intent);
finish();
}
注意:目前 startActivityForResult() 已被弃用,现用
registerForActivityResult()
取代。
5 活动生命周期
掌握活动的生命周期对于任何 Android 开发者是非常重要的。
Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Task)。
5.1 活动状态
每个活动在其生命周期最多可能有4中状态。
- 运行状态:位于
栈顶
的活动。系统最不愿意回收。 - 暂停状态:活动
不位于栈顶但可见
。系统不愿回收,除非处于内存极低状态。 - 停止状态:活动
不位于栈顶也不可见
。可能会被系统回收。 - 销毁状态:活动
从栈中移除
。系统倾向回收。
5.2 活动的生存期
Activity 类中定义了七个方法,覆盖了活动生命周期的每一个环节。
- onCreate:活动第一次被创建时调用。完成活动的初始化(如加载布局,绑定事件)。
- onStart:活动由 不可见 → 可见。
- onResume:活动准备好和用户进行交互时候调用。此时活动处于栈顶,且处于运行状态。
- onPause:系统准备取启动或恢复另一个活动时调用。
- onStop:活动完全不可见时调用。
- onDestroy:活动被销毁之前调用。
- onRestart:活动由停止状态变为运行状态时调用。
5.3 活动被回收了怎么办?
一个活动进入停止状态时被系统回收,那么如果该活动存储着用户的临时数据,那么这些数据将会丢失,给用户造成不好的体验。那么如何避免这些问题呢?
Activity 提供了一个 onSaveInstanceState()
回调方法。这个方法保证在活动被回收之前一定会调用
,因此我们可以通过这个方法来解决上述问题。
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Hello";
outState.putString("data",tempData);
}
onSaveInstanceState() 需要一个 Bundle 参数,
Bundle 提供一系列方法用于保存数据,如:putString、putInt,均需要参数(键,值)。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
if(savedInstanceState != null){
String tempData = savedInstanceState.getString("data");
}
}
onSaveInstanceState() 在 Activity 被暂时停止时(被其他程序中断或锁屏)调用,而 Activity 在完全关闭时(调用 finish() 函数)时不会被调用的。
当暂停的 Activity 被恢复时,系统会调用
onRestoreInstance()
函数。
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if(savedInstanceState != null){
String tempData = savedInstanceState.getString("data");
}
}
Intent 还可以结合 Bundle 一起用于传递数据。可以把数据存于Bundle,再将Bundle存于Intent,之后再从Intent取出Bundle,再从Bundle取出数据。
6 活动的启动模式
启动模式一共有4种,在实际项目中我们应该根据特定的需求为每个活动指定恰当的模式。
通过在 AndroidManifest.xml 中给 <activity> 标签指定 android:launchMode
属性来选择启动模式。
standard
:默认的启动方式。这种方式下系统不会在乎这个活动是否已经在栈中存在,每次启动都会创建该活动的一个新的实例。singleTop
:在启动活动时如果发现返回栈的栈顶是该活动,则直接使用它,不会再创建新的活动实例。若不处于栈顶,则创建新的活动实例。singleTask
:如果返回栈中存在这个活动,则会将在这个活动之上的所有活动通通出栈,并将该活动放于栈顶直接使用。只要栈中没有该活动时才会创建新实例。singleInstance
:这个模式比较复杂。此模式下活动会启动一个新的返回栈来管理这个活动,不管是哪个应用程序来返回这个活动,都共用一个返回栈。
7 知晓当前是处于哪一个活动
- 创建一个 BaseActivity 类(不是活动,是Java Class 类),继承 AppCompatActivity,重写 onCreate
package com.example.myapplication;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
接下来,只需要让
BaseActivity 成为所有活动的父类
即可。
8 随时随地退出程序
当活动创建很时,退出需要重复按多次back键,很不方便,那如何解决这个问题呢?
只需要创建一个专门对所有活动进行管理的集合类即可:
ActivityCollector
类
package com.example.myapplication;
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity){
activities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for(Activity activity : activities){
if(!activity.isFinishing()){
activity.finish();
}
}
activities.clear();
}
}
修改 BaseActivity 代码:
package com.example.myapplication;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
当要进行彻底退出程序时,需要调用:
ActivityCollector.finishAll();