Android Activity
活动(Activity)是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零到多个活动。
-
创建Activity 项目中所有的活动都要重写onCreate() 方法
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
默认调用父类的onCreate(savedInstanceState); 方法,后面需要增加自己的逻辑代码。
- 创建和加载布局
<?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
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1 " />
</LinearLayout>
- 在AndroidManifest文件中注册
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.firstactivity">
<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/Theme.FirstActivity">
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"></action>
<category android:name="android.intent.category.LAUNCHER"></category>
</intent-filter>
</activity>
</application>
</manifest>
activity 要放在 application 标签内
? android:name="" 指定活动
? 配置主活动(即点击应用程序图标时首先打开的这个活动)
<action android:name="android.intent.action.MAIN"></action> <category android:name="android.intent.category.LAUNCHER"></category>
Toast提醒
Toast是Android系统提供的一种提醒方式,在程序中可以使用它将一些短小的信息通知给用户,并在一段时间后消失,不会占用任何屏空间。
Toast通过makeText() 创建一个Toast对象,然后调用show() 显示
makeText() 方法需要三个参数。
1. Context 即Toast要求的上下文
2. msg Toast显示的文本内容
3. length 显示时长,有两个内置产量可以选择Toast.LENGTH_SHORT Toast.LENGTH.LONG
Menu菜单
要定义Menu,我们首先需要在res 文件夹下新建menu 文件夹,它将用于存储与Menu相关的所有XML文件。
我们可以使用<menu> 、<item> 、<group> 三种XML元素定义Menu,下面简单介绍一下它们:
<menu> 是菜单项的容器。<menu> 元素必须是该文件的根节点,并且能够包含一个或多个<item> 和<group> 元素。<item> 是菜单项,用于定义MenuItem ,可以嵌套<menu> 元素,以便创建子菜单。<group> 是<item> 元素的不可见容器(可选)。可以使用它对菜单项进行分组,使一组菜单项共享可用性和可见性等属性。
其中,<item> 是我们主要需要关注的元素,它的常见属性如下:
android:id :菜单项(MenuItem) 的唯一标识android:icon :菜单项的图标(可选)android:title :菜单项的标题(必选)android:showAsAction :指定菜单项的显示方式。常用的有ifRoom、never、always、withText ,多个属性值之间可以使用| 隔开。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/option_normal_1"
android:icon="@mipmap/ic_vpn_key_white_24dp"
android:title="普通菜单1"
app:showAsAction="ifRoom"/>
<item android:id="@+id/option_normal_2"
android:icon="@mipmap/ic_email_white_24dp"
android:title="普通菜单2"
app:showAsAction="always"/>
<item android:id="@+id/option_normal_3"
android:icon="@mipmap/ic_vpn_key_white_24dp"
android:title="普通菜单3"
app:showAsAction="withText|always"/>
<item android:id="@+id/option_normal_4"
android:title="普通菜单4"
app:showAsAction="never"/>
</menu>
我们需要知道,菜单栏中的菜单项会分为两个部分。一部分可以直接在菜单栏中看见,我们可以称之为常驻菜单;另一部分会被集中收纳到溢出菜单中(就是菜单栏右侧的小点状图标)。一般情况下,常驻菜单项以图标形式显示(需要定义icon 属性),而溢出菜单项则以文字形式显示(通过title 属性定义)。showAsAction 的差异如下所示:
always :菜单项永远不会被收纳到溢出菜单中,因此在菜单项过多的情况下可能超出菜单栏的显示范围。ifRoom :在空间足够时,菜单项会显示在菜单栏中,否则收纳入溢出菜单中。withText :无论菜单项是否定义了icon 属性,都只会显示它的标题,而不会显示图标。使用这种方式的菜单项默认会被收纳入溢出菜单中。never :菜单项永远只会出现在溢出菜单中。- 未写:默认出现在溢出菜单中。
接下来还需要在Java代码中进行加载
重写onCreateOptionsMenu 方法,在这个方法中完成加载Menu资源的操作
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.option_menu_normal,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.option_normal_1:
return true;
case R.id.option_normal_2:
return true;
case R.id.option_normal_3:
return true;
case R.id.option_normal_4:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
在onOptionsItemSelected 方法中,我们实现了菜单项的点击监听。可以看见,这里是通过MenuItem 的id 进行区分的,对应着XML文件中<item> 的id 属性。每次处理完点击事件后,记得要返回true,对系统而言这次点击事情才算是真正结束了。此外,在default 分支下,推荐调用父类的默认实现,即super.onOptionsItemSelected(item) ,避免在多个Activity使用公有父类的情况下菜单项点击事件无法触发。
包含多级子菜单的选项菜单
我们在前面提到过,<item> 是可以嵌套<menu> 的,而<menu> 又是<item> 的容器。因此,我们可以在应用中实现具有层级结构的子菜单。下面给出一个实际的例子:
XML代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/option_sub_file"
android:title="文件"
app:showAsAction="ifRoom">
<menu>
<item android:id="@+id/file_new"
android:title="新建"/>
<item android:id="@+id/file_save"
android:title="保存"/>
<item android:id="@+id/file_more"
android:title="更多">
<menu>
<item android:id="@+id/file_more_1"
android:title="更多1"/>
<item android:id="@+id/file_more_2"
android:title="更多2"/>
<item android:id="@+id/file_more_more"
android:title="更多更多">
<menu>
<item android:id="@+id/file_more_more_1"
android:title="更多更多1"/>
<item android:id="@+id/file_more_more_2"
android:title="更多更多2"/>
</menu>
</item>
</menu>
</item>
</menu>
</item>
</menu>
上面的代码实现了一个三级子菜单结构。理论上来说,子菜单的层级是没有限制的。但是在实际应用中,由于移动设备的显示特点,建议菜单层级不要超过两层,否则会给用户的操作带来诸多不便。
使用Intnet在活动之间穿梭
- 使用显式Intent
Intnet intnet = new Intnet(this,Activity.class);
传入当前类作为上下文,Activity.class作为目标活动。
- 使用隐式Intnet
在AndroidManifest.xml中的目标活动中添加参数
<intent-filter>
<action android:name="com.example.firstactivity.ACTION_START"></action>
<category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
Intnet intnet = new Intnet("com.example.firstactivity.ACTION_START");
Intnet中可以指定action,可以指定多个category
action 和 category 同时匹配时才能响应Intnet
未指定category 默认指定 android.intent.category.DEFAULT
<intent-filter>
<action android:name="com.example.firstactivity.ACTION_START"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="com.example.firstactivity.MY_CATEGORY"></category>
</intent-filter>
Intent intent = new Intent("com.example.firstactivity.ACTION_START");
intent.addCategory("com.example.firstactivity.MY_CATEGORY");
- 其他隐式Intnet
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
//intent.setData(Uri.parse("tel:15079059049"));
startActivity(intent);
Intnet传递数据
Intnet intnet = new Intnet(this,Activity.class);
intnet.putExtra("key","value");
...
startActivity(intnet);
接收数据
Intnet intnet = getIntnet();
String value = intnet.getStringExtra("key");
字符串 getStringExtra() 整型getIntExtra() 布尔型 getBooleanExtra() …
返回数据给上一个活动
Intnet intnet = new Intnet(this,Activity.class);
startActivityForResult(intnet,1);
使用startActivityForResult启动下一个活动
Intent intent = new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(1,intent);
finish();
上述代码可放在 onackPressed() 方法中 ,也可放入点击事件中。
当该活动结束返回上一个活动并传递数据。
接收数据 重写onActivityResult()方法
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case 1:
if (requestCode == 1){
String returnData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnData);
}
break;
default:
Log.d("FirstActivity", "fail");
}
}
Activity生命周期
? 返回栈
启动活动,入栈。
按下Back或者调用finish(),出栈。
? 活动状态
4种状态
- 运行状态
- 暂停状态
- 停止状态
- 销毁状态
Activity的生存期
Activity类中定义了7个回调方法,覆盖了Activity生命周期的每一个环节。
-
onCeate() Activity被创建时调用 -
onStart() Activity由不可见变为可见的时候调用 -
onResume() Activity准备好和用户进行交互的时候调用,此时的Activity位于栈顶,且处于运行状态 -
onPause() 系统准备区启动或者恢复另一个Activity的时候调用 -
onStop() Activity完全不可见的时候调用 -
onDestory() Activity被销毁之前调用,之后Activity的状态变为销毁状态 -
onRestart() Activity由停止状态变为运行状态之前调用
三种生存期
- 完整生存期 onCreate()方法和onDestory()方法之间所经历的。
- 可见生存期 onStart()方法和onStop()方法之间所经历的。
- 前台生存期 onResume()方法和onPause()方法之间所经历的。
数据回收
? 在Activity生命周期中,进入了停止状态,有可能被系统回收。
? 举个栗子:应用中有一个 Activity A ,用户在 Activity A 的基础上启动了 Activity B ,Activity A进入停止状态,此时由于系统资源不足,Activity A 被回收了,但用户按下 Back 返回 Activity A 时,会显示 Activity A ,但这时执行的不是onRestart() 方法,而是执行 Activity A 的onCreate() 方法,两个Activity A 其实是不同的,第一个Activity A 可能包含某些重要的数据,此时怎么办呢。
? 其实 Activity 中提供 onSaveInstanceState() 回调方法,此方法可以保证在Activity被回收之前一定会被调用,onSaveInstanceState() 有一个 Bundle 类型的参数, Bundle 提供了一系列的方法用于保存数据,如putString() 保存字符串,putInt() 保存整型数据 … ,保存时两个参数,一个是键,一个是想要保存的值。 此时已经将数据保存下来了,恢复的话也很简单,只是我们没有注意到而已,Activity 中的 onCreate() 方法中就包含一个 Bundle 类型的参数,我们可以在 onCreate() 方法中判断是否为空,不为空的话就可以通过对应的取值方法获得数据。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("key", "value");
outState.putInt("int", 1);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
if (savedInstanceState != null) {
String str = savedInstanceState.getString("key");
int num = savedInstanceState.getInt("int");
Log.d(TAG, "onCreate: str: " + str);
Log.d(TAG, "onCreate: num: " + num);
}
}
Activity的启动模式
Activity 启动模式一共4种,standard、singleTop、singleTask、singleInstance,在AndropidManifest.xml 中通过 <activity> 标签指定 android:launchMode 属性来选择启动模式。
standard(默认)模式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Log.d(TAG, "onCreate: "+ this.toString());
Button button = findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@9f9406d
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@2c4413c
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@43a5264
? 每次点击按钮就会创建一个新的FirstActivity 并入栈,此时需要连续3次Back才能退出程序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hs3qEWEE-1627977975407)(https://z3.ax1x.com/2021/08/03/fiCFVf.png)]
singleTop模式
<activity android:name=".FirstActivity" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"></action>
<category android:name="android.intent.category.LAUNCHER"></category>
</intent-filter>
</activity>
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@4c7c0f0
Activity 在栈顶时并不会再创建新的实例,但当 Activity 不在栈顶时还是会创建新的实例。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Log.d(TAG, "onCreate: "+ this.toString());
Button button = findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Log.d(TAG, "onCreate: "+ this.toString());
Button button = findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this, FirstActivity.class);
startActivity(intent);
}
});
}
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@a00982
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@10b1108
FirstActivity: onCreate: com.example.firstactivity.FirstActivity@18484be
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7O4BTvBt-1627977975408)(https://z3.ax1x.com/2021/08/03/fiibbq.png)]
singleTask模式
使用singlTop模式可以解决重复创建栈顶Activity的问题,但当Activity不处于栈顶时还是会创建多个实例。
singleTask模式则会向前搜索,若存在该 Activity ,依次出栈使该 Activity 处于栈顶从而启动 Activity 。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ooITjd3q-1627977975409)(https://z3.ax1x.com/2021/08/03/fiA58O.png)]
singleInstance模式
最复杂的一种启动模式,指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个活动。
Log.d(TAG, " Task id is "+ getTaskId());
通过日志打印返回栈id发现 singleInstance 模式的SecondActivity处于不同的返回栈中。
FirstActivity: Task id is 27
SecondActivity: Task id is 28
ThirdActivity: Task id is 27
从 ThirdActivity 点击 Back 返回时直接返回到了 FirstActivity ,再按 Back 返回才回到 SecondActivity 说明但主返回栈为空时才会显示另一个栈顶的 Activity。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tl1wInRZ-1627977975410)(https://z3.ax1x.com/2021/08/03/fiejmQ.png)]
直接退出程序
当Activity较多时需要多次点击Back,而Home键只是将程序挂起,并没有退出程序。
此时可以创建一个专门控制Activity的类来管理。
public class ActivityCollector {
public static List<Activity> activityList = new ArrayList<>();
public static void addActivity(Activity activity){
activityList.add(activity);
}
public static void removeActvity(Activity activity){
activityList.remove(activity);
}
public static void finishAll(){
for (Activity activity:activityList) {
if(!activity.isFinishing()){
activity.finish();
this.removeActvity(activity);
}
}
}
}
在每个 Activity 的 onCreate() 方法中调用 addActivity() ,在 onDestory() 方法中调用 removeActvity() ,
但需要直接退出程序时,可以在某些按钮的点击事件上添加 finishAll() 方法
|