先从看得到的入手.Android学习.02
萧禾财 Lv4

活动是什么

​ 活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于 和用户进行交互。一个应用程序中可以包含零个或多个活动,但不包含任何活动的应用程序很少见。

具体操作实例

Button元素

这是一个最简单的Button属性设置

image-20220726143701014

  • android:id:给与Button一个名字
  • android:layout_width 指定了当前元素的宽 度,这里使用 match_parent 表示让当前元素和父元素一样宽。
  • android:layout_height 指定 了当前元素的高度,这里使用 wrap_content 表示当前元素的高度只要能刚好包含里面的内容就行。
  • android:text 指定了元素中显示的文字内容。

这是一个比较复杂的Button属性设置

image-20220726144240750

具体参考

Android基础布局之ConstraintLayout_

Toast元素

Toast 是 Android 系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息 通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间,我们现在就尝试 一下如何在活动中使用 Toast。

image-20220726145508190

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//使用Toast弹窗 和 退出
Button button1 =findViewById(R.id.testbutton1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "玩个屁", Toast.LENGTH_SHORT).show();
}
});

在活动中,可以通过 findViewById()方法获取到在布局文件中定义的元素。

我们传入R.id.testbutton1 即在layout中定义的Button的id。

得到按钮的实例之后,我们通过调用 setOnClickListener()方法为按钮注册一个监听器, 点击按钮时就会执行监听器中的 onClick()方法。

Toast 的用法非常简单,通过静态方法 makeText()创建出一个 Toast 对象,然后调用 show() 将 Toast 显示出来就可以了。这里需要注意的是,makeText()方法需

要传入 3 个参数。

第一个参 数是 Context,也就是 Toast 要求的上下文,由于活动本身就是一个 Context 对象,因此这里直 接传入MainActivity.this即可。

第二个参数是 Toast 显示的文本内容。

第三个参数是 Toast 显示的时长,有两个内置常量可以选择 Toast.LENGTH_SHORT 和 Toast.LENGTH_LONG。

首先在 res 目录下新建一个 menu 文件夹

image-20220726150312403

名称为menu,再右击 menu 文件夹

image-20220726150445990

文件名输入main。

创建完成后再main.xml中输入如下代码

1
2
3
4
5
6
7
8
9
10
11
12
<?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"/>
<!-- id:标识符 title:指定名称 -->
<item
android:id="@+id/remove_item"
android:title="Remove"/>


</menu>

​ 这里我们创建了两个菜单项,其中标签就是用来创建具体的某一个菜单项,然后通 过 android:id 给这个菜单项指定一个唯一的标识符,通过 android:title 给这个菜单项指定 一个名称。

随后重新回到MainActivity重写onCreateOptionsMenu()方法,重写方法可以使用 Ctrl + O 快捷键(Mac 系统是 control + O)

image-20220726151156566

1
2
3
4
5
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}

通过 getMenuInflater()方法能够得到 MenuInflater 对象,再调用它的 inflate()方法 就可以给当前活动创建菜单了。

inflate()方法接收两个参数,第一个参数用于指定我们通过哪 一个资源文件来创建菜单,这里当然传入 R.menu.main。第二个参数用于指定我们的菜单项将添加到哪一个 Menu 对象当中,这里直接使用 onCreateOptionsMenu()方法中传入的 menu 参数。

然后给这个方法返回 true,表示允许创建的菜单显示出来,如果返回了 false,创建的菜单将无法显示。

当然,仅仅让菜单显示出来是不够的,我们定义菜单不仅是为了看的,关键是要菜单真正可 用才行,因此还要再定义菜单响应事件。在 MainActivity中重写 onOptionsItemSelected()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(this, "充值令你变强", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this,"删除失败",Toast.LENGTH_SHORT).show();
break;
default:
break;

}
return true;
}

这时启动程序就会有不同

你会发现在标题栏的右侧多了一个三点的符号,这个就是菜单按钮了

image-20220726151855998

点击不同的菜单按钮 就会出现不同的 Toast弹窗提示

销毁一个活动

最简单的一个方法就是按一下back键就可以销毁档前的活动。

不过如果你不想通过按 键的方式,而是希望在程序中通过代码来销毁活动,当然也可以,Activity 类提供了一个 finish() 方法,我们在活动中调用一下这个方法就可以销毁当前活动了。

修改按钮监听器中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//使用Toast弹窗 和 退出
Button button1 =findViewById(R.id.testbutton1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "玩个屁", Toast.LENGTH_SHORT).show();
finish();//关闭程序 仍然会留在后台
}
});

重新运行程序,这时点击一下按钮,当前的活动就被成功销毁了,效果和按下 Back 键是一 样的。

使用Intent在活动之间穿梭

​ Intent 是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想 要执行的动作,还可以在不同组件之间传递数据。Intent 一般可被用于启动活动、启动服务以及 发送广播等场景。

​ Intent 大致可以分为两种:显式 Intent 和隐式 Intent。

​ Intent 有多个构造函数的重载,其中一个是 Intent(Context packageContext, Class cls)。这个构造函数接收两个参数,第一个参数 Context 要求提供一个启动活动的上下文,第 二个参数 Class 则是指定想要启动的目标活动。

​ Activity 类中提供了一个 startActivity()方法,这 个方法是专门用于启动活动的,它接收一个 Intent 参数,这里我们将构建好的 Intent 传入 startActivity()方法就可以启动目标活动了。

使用显式 Intent

​ 我们现在存在两个活动,现在要由主活动跳转导其他活动。

​ 先在主活动的布局文件中创建一个Button用于进行跳转操作

1
2
3
4
5
6
7
8
9
10
11
12
13
<Button
android:id="@+id/testbutton3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:text="快来康康"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.949"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.928"
tools:ignore="MissingConstraints" />

​ 在主活动的Java中对Button按钮进行设置

1
2
3
4
5
6
7
8
Button button3 =findViewById(R.id.testbutton3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent =new Intent(MainActivity.this,MainActivity2.class);
startActivity(intent);
}
});

​ 我们首先构建出了一个 Intent,传入 MainActivity.this 作为上下文,传入 MainActivity2.class作为目标活动,这样我们的“意图”就非常明显了,即在MainActivity这个活 动的基础上打开 MainActivity2 这个活动。然后通过 startActivity()方法来执行这个 Intent。

​ 使用这种方式来启动活动,Intent 的“意图”非常明显,因此我们称之为显式 Intent。

使用隐式 Intent

​ 相比于显式 Intent,隐式 Intent 则含蓄了许多,它并不明确指出我们想要启动哪一个活动, 而是指定了一系列更为抽象的 action 和 category 等信息,然后交由系统去分析这个 Intent, 并帮我们找出合适的活动去启动。

通过在标签下配置的内容,可以指定当前活动能够响应的 action 和 category

1
2
3
4
5
6
7
8
9
10
<activity
android:name=".MainActivity2"
android:exported="true">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
<activity

在标签中我们指明了当前活动可以响应 com.example.activitytest.ACTION_ START 这个 action,而标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的 Intent 中还可能带有的 category。只有和中的内容同时能够匹配 上 Intent 中指定的 action 和 category 时,这个活动才能响应该 Intent。

1
2
3
4
5
6
7
8
9
//隐式intent
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent =new Intent("com.example.activitytest.ACTION_START");
intent.addCategory("com.example.activitytest.MY_CATEGORY");
startActivity(intent);
}
});

我们使用了 Intent 的另一个构造函数,直接将 action 的字符串传了进去,表明 我们想要启动能够响应 com.example.activitytest.ACTION_START 这个 action 的活动。android.intent.category.DEFAULT 是一种默认的 category,在调 用 startActivity()方法的时候会自动将这个 category 添加到 Intent 中。

每个 Intent 中只能指定一个 action,但却能指定多个 category。

更多隐式 Intent 的用法

使用隐式 Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得 Android多个应用程序之间的功能共享成为了可能。

1
2
3
4
5
6
7
8
9
Button button2 =findViewById(R.id.testbutton2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://m.bilibili.com/video/BV1GJ411x7h7?ts=1658824194&spmid=333.938.click.openapp_popup&h5_buvid=68ACC3D1-102A-14F2-15EE-8869CEC0FC1A04348infoc&unique_k=&bsource="));
startActivity(intent);
}
});

上面这段代码可以调用浏览器进入指定的网站

这里我们首先指定了 Intent 的 action 是 Intent.ACTION_VIEW,这是一个 Android 系统内 置的动作,其常量值为 android.intent.action.VIEW。然后通过 Uri.parse()方法,将一个 网址字符串解析成一个 Uri 对象,再调用 Intent 的 setData()方法将这个 Uri 对象传递进去。

image-20220726163612913

与此对应,我们还可以在标签中再配置一个标签,用于更精确地 指定当前活动能够响应什么类型的数据。标签中主要可以配置以下内容。

  • android:scheme。用于指定数据的协议部分,如上例中的 http 部分。
  • android:host。用于指定数据的主机名部分,例如 www.baidu.com
  • android:port。用于指定数据的端口部分,一般紧随在主机名之后。
  • android:path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
  • android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定。

只有标签中指定的内容和 Intent 中携带的 Data 完全一致时,当前活动才能够响应该 Intent。

这里我们自己建立一个活动,让它也可以响应打开网页的Intent

在 AndroidManifest.xml 中修改此活动的注册信息

1
2
3
4
5
6
7
8
9
10
<activity
android:name=".MainActivity3"
android:exported="true">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- 响应一个打开网页的intent -->
<data android:scheme="https" />
</intent-filter>
</activity>

重新运行程序 此时点击上个活动的链接就会多出一个选项,说明我们成功了

image-20220726165139298

此时如果你选择chrome打开,就可以正常打开。

但如果你选择我们的写的活动,就会进入我们写的活动。

除了 http 协议外,我们还可以指定很多其他协议,比如 geo 表示显示地理位置、tel 表示拨打 电话。

1
2
3
4
5
6
7
8
9
Button button4 =findViewById(R.id.testbutton4);
button4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent =new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("tel:110"));
startActivity(intent);
}
});

这一段代码就可以让我们调用系统拨号界面,并自动填写电话号码。

image-20220726165939229

向下一个活动传递数据

在启动活动时传递数据的思路很简单,Intent 中提供了一系列 putExtra()方法的重载,可 以把我们想要传递的数据暂存在 Intent 中,启动了另一个活动后,只需要把这些数据再从 Intent 中取出就可以了。

1
2
3
4
5
6
7
8
9
10
Button button3 =findViewById(R.id.testbutton3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String data = "can can need";
Intent intent =new Intent(MainActivity.this,MainActivity2.class);
intent.putExtra("extra_data",data);//向下传递数据data
startActivity(intent);
}
});

这里我们还是使用显式 Intent 的方式来启动 MainActivity,并通过 putExtra()方法传递了 一个字符串。注意这里 putExtra()方法接收两个参数,第一个参数是键,用于后面从 Intent 中 取值,第二个参数才是真正要传递的数据。

接下来我们在MainActivity2中将传递的数据取出,并打印出来

1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Intent intent =getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("MainActivity_2",data);
}

首先可以通过 getIntent()方法获取到用于启动 SecondActivity 的 Intent,然后调用 getStringExtra()方法,传入相应的键值,就可以得到传递的数据了。这里由于我们传递的是 字符串,所以使用 getStringExtra()方法来获取传递的数据。如果传递的是整型数据,则使 用 getIntExtra()方法;如果传递的是布尔型数据,则使用 getBooleanExtra()方法,以此 类推。

活动的生命周期

返回栈

Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集 合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当 我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键 或调用 finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会 重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。

活动状态

每个活动在其生命周期中最多可能会有 4 种状态。

  1. 运行状态
    当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。
  2. 暂停状态
    当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。
  3. 停止状态
    当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为 这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于 停止状态的活动有可能会被系统回收。
  4. 销毁状态
    当一个活动从返回栈中移除后就变成了销毁状态。

活动的生存期

Activity 类中定义了 7 个回调方法,覆盖了活动生命周期的每一个环节,下面就来一一介绍这7个方法。

  • onCreate()。它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如 说加载布局、绑定事件等。
  • onStart()。这个方法在活动由不可见变为可见的时候调用。
  • onResume()。这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于 返回栈的栈顶,并且处于运行状态。
  • onPause()。这个方法在系统准备去启动或者恢复另一个活动的时候调用。
  • onStop()。这个方法在活动完全不可见的时候调用。它和 onPause()方法的主要区别在 于,如果启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而 onStop()方法并不会执行。
  • onDestroy()。这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
  • onRestart()。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

以上 7 个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为 3 种生存期。

  • 完整生存期。活动在 onCreate()方法和 onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动会在 onCreate()方法中完成各种初始化操作,而在 onDestroy()方法中完成释放内存的操作。

  • 可见生存期。活动在 onStart()方法和 onStop()方法之间所经历的,就是可见生存期。 在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可 以通过这两个方法,合理地管理那些对用户可见的资源。比如在 onStart()方法中对资源进行加载,而在 onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。

  • 前台生存期。活动在onResume()方法和 onPause()方法之间所经历的就是前台生存期。 在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我 们平时看到和接触最多的也就是这个状态下的活动。

用图文表示

image-20220727100503972

onSaveInstanceState()回调方法

onSaveInstanceState()回调方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临 时数据得不到保存的问题。

onSaveInstanceState()方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方 法用于保存数据,比如可以使用 putString()方法保存字符串,使用 putInt()方法保存整型数 据,以此类推。

每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值,第二个参数是真正要保存的内容。

在 MainActivity 中添加如下代码就可以将临时数据进行保存:

1
2
3
4
5
6
7
//暂存数据
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
String temData = "show show word";
outState.putString("data_key",temData);
}

在onCreate方法中获得数据,并打印出来

1
2
3
4
5
//获得销毁前的数据
if(savedInstanceState!=null){
String temData =savedInstanceState.getString("data_key");
Log.d("TAG",temData);
}

取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框上。

活动的启动模式

  1. standard模式

    standard 是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。
    在 standard 模式(即默认情况)下,每当启 动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard 模式的活动, 系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。

    image-20220727141443101

  2. singleTop 模式
    当活动的启动模式指定为 singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
    每当想要再启动一个 FirstActivity 时都会直接使用栈顶的活动,因此 FirstActivity 也只会有一个实例,仅按一次 Back 键就可以退出程序。
    不过当 FirstActivity 并未处于栈顶位置时,这时再启动 FirstActivity,还是会创建新的实例的。
    image-20220727141916711

  3. singleTask模式
    当活动的启动模式指定为 singleTask,每次启动该活动时系统首先会在返回栈中检查是否 存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
    image-20220727142209381

  4. singleInstance 模式

指定为singleInstance 模式的活动会启用一个新的返回栈来管理这个活动。
image-20220727142723039

  1. singleInstancePerTask模式
    Android12版本新增运行模式
    和singleTask几乎一样,不过singleInstancePerTask不需要为启动的Activity设置一个特殊的taskAffinity才能创建一个新的Task。

小技巧

知晓当前是在哪一个活动

新建一个BaseActivity类,让BaseActivity继承AppCompatActivity,并重写onCreate()方法。

1
2
3
4
5
6
7
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());//打印活动名
}
}

接下来我们需要让 BaseActivity 成为 ActivityTest 项目中所有活动的父类。

而由于 BaseActivity 又是继承自 AppCompatActivity 的,所以 项目中所有活动的现有功能并不受影响,它们仍然完全继承了 Activity 中的所有特性。

现在每当我们进入到一个活动的界面,该活动的类名就会被打印出来,这样我们就可以时时 刻刻知晓当前界面对应的是哪一个活动了

随时随地退出程序

用一个专门的集合类对所有的活动进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}
}

我们通过一个 List 来暂存活动,然后提供了一个 addActivity()方法用 于向 List 中添加一个活动,提供了一个 removeActivity()方法用于从 List 中移除活动,最后提 供了一个 finishAll()方法用于将 List 中存储的活动全部销毁掉。

还要修改BaseActivity类中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}

在 BaseActivity 的 onCreate()方法中调用了 ActivityCollector 的 addActivity()方 法,表明将当前正在创建的活动添加到活动管理器里。然后在 BaseActivity 中重写 onDestroy() 方法,并调用了 ActivityCollector 的 removeActivity()方法,表明将一个马上要销毁的活 动从活动管理器里移除。

从此以后,不管你想在什么地方退出程序,只需要调用 ActivityCollector.finishAll() 方法就可以了。

前提是:所有类都继承了BaseActivity。

  • 本文标题:先从看得到的入手.Android学习.02
  • 本文作者:萧禾财
  • 创建时间:2022-07-26 14:22:20
  • 本文链接:https://ipartmentxhc.github.io/2022/07/26/先从看得到的入手-Android学习-02/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!