2.2.4 在活动中使用Toast
2.2.5 在活动中使用Menu
2.2.6 销毁一个活动
2.3 Intent实现在活动之间切换
2.4 活动的生命周期
2.5 活动的启动模式
2.6 活动的最佳实践
2.2.4 在活动中使用Toast
-
使用Toast将一些简短的消息通知给用户,这些消息会在一段时间后自动消失,并且不占用屏幕空间
-
使用方法:
- 静态方法
makeText()
创建出一个Toast对象makeText()
需传入三个参数:- Context对象
- 显示的文本
- 显示的时间,有两个内置常量供选择:
LENGTH_SHORT
、LENGTH_LONG
- 然后调用
show()
将Toast显示出来
e.g.
Toast.makeText(FirstActivity.this, "You clicked button1",Toast.LENGTH_SHORT).show();
- 静态方法
2.2.5 在活动中使用Menu
- 显示一个菜单,同时不占用屏幕空间
- 使用方法:
- 在res目录下的menu文件夹下新建一个Menu resource file, 如 main.xml
- 在main.xml中添加
<item></item>
标签,用于创建具体的菜单项 - 回到活动类中重写
onCreatOptionsMenu()
方法
e.g.@Override public boolean onCreateOptionsMenu(Menu menu) { /** * getMenuInflater获得MenuInflater对象,调用inflate()为活动创建菜单 * inflate参数: * 指定资源文件 * 指定Menu对象 */ getMenuInflater().inflate(R.menu.main, menu); return true; //返回true表示允许显示 }
- 菜单显示出来后,还要定义菜单响应事件,在活动类中重写
onOptionsItemSelected()
方法
e.g.@Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { //getItemId()判断点击的是哪个菜单项 switch (item.getItemId()) { case R.id.add_item: Toast.makeText(FirstActivity.this, "You clicked Add",Toast.LENGTH_SHORT).show(); break; case R.id.remove_item: Toast.makeText(FirstActivity.this, "You clicked Remove",Toast.LENGTH_SHORT).show(); break; default: } return true; }
2.2.6 销毁一个活动
- 调用活动类的
finish()
方法即可
e.g.button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } });
2.3 Intent实现在活动之间切换
- 新建一个活动类及其布局文件
- 在AndroidManifest.xml中注册,由于不是主活动,故不用配置
<intent-filter>
标签里的内容 - Intent可以指明当前组件想要执行的动作,还可以在不同的组件之间传递数据
- Intent一般可被用于启动活动、启动服务以及发送广播等场景
- Intent可大致分为显式和隐式两种
2.3.1 使用显式Intent
- 在活动一新建一个Intent对象,调用构造函数
Intent(Context packageContext, Class<?> cls)
- 第一个参数Context要求提供一个启动活动的上下文
- 第二个参数Class指定想要启动的目标活动
- 调用活动类提供的
startActicity()
方法启动活动,它接收一个Intent参数,将建好的Intent对象传入即可启动目标活动
e.g.
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
});
2.3.2 使用隐式Intent
- 隐式并不指明我们想要启动哪一个活动,而是指定了一系列更抽象
action
和category
等信息,然后交给系统分析这个Intent,并帮我们找出合适的活动去启动 - 通过在AndroidManifest.xml中的
<activity>
标签下配置<intent-filter>
的内容可以指定当前活动能够响应的action
和category
e.g.<activity android:name=".SecondActivity"> <intent-filter> <!--指明当前活动可以响应com.example.activitytest.ACTION_START这个action--> <action android:name="com.example.activitytest.ACTION_START"/> <!--对action的补充,使更精确指明当前活动能响应的Intent中还可能带有的category--> <category android:name="android.intent.category.DEFAULT"/> <!--只有action和category同时匹配成功,这个活动才能响应该Intent--> </intent-filter> </activity>
- 新建一个Intent,调用构造函数
Intent(String action)
,表明我们想要启动能响应该action的活动,没有指定category是因为android.intent.category.DEFAULT
是一种默认的category,在调用startActivity()
时会自动添加到Intent中,不然用Intent中的addCategory()
方法来添category
e.g.button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.activitytest.ACTION_START"); startActivity(intent); } });
- 每个Intent中只能指定一个action,但却能指定多个category
- 可以调用Intent中的
addCategory()
方法来添加一个category,记得事先在AndroidManifest.xml中配置好,否则找不到适合的Intent会导致程序崩溃
e.g.button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.activitytest.ACTION_START"); intent.addCategory("com.example.activitytest.MY_CATEGORY"); startActivity(intent); } });
2.3.3 更多隐式Intent的用法
-
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能,比如你的应用程序需要显示一个网页,这时你没有必要去实现一个浏览器,而是只需要调用系统的浏览器来打开这个网页即可
e.g.button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //ACTION_VIEW是Android系统内置的动作,其常量值为android.intent.action.VIEW Intent intent = new Intent(Intent.ACTION_VIEW); //调用Uri.parse()方法将网址字符串解析成Uri对象 //再调用setData()方法将Uri对象传进Intent intent.setData(Uri.parse("http://www.baidu.com")); startActivity(intent); } });
-
我们还可以在
<intent-filter>
标签中再配置一个<data>
标签,用于更精确地指定当前活动能够响应什么类型地数据
e.g.<activity android:name=".ThirdActivity"> <intent-filter> <action android:name="android.intent.action.DIAL"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="tel"/> </intent-filter> </activity>
-
<data>
标签可以配置一下内容:- android:scheme 用于指定数据地协议部分,如上例中的http部分
- android:host 用于指定数据的主机名部分,如上例中的www.baidu.com部分
- android:pory 用于指定数据的端口部分,一般紧随在主机名后
- android:path 用于指定路由
- android:mimeType 用于指定可以处理地数据类型,允许通配符的方式进行指定
-
除http协议外,我们还可以指定很多协议,比如geo表示显示地理位置、tel表示拨打电话
e.g.button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //ACTION_DIAL是Android系统内置的动作,其常量值为android.intent.action.DIAL Intent intent = new Intent(Intent.ACTION_DIAL); //调用Uri.parse()方法将网址字符串解析成Uri对象 //再调用setData()方法将Uri对象传进Intent intent.setData(Uri.parse("tel:10086")); startActivity(intent); } });
2.3.4 向下一个活动传递数据
-
Intent还能在启动活动的时候传递数据
-
Intent中提供了一系列
putExtra()
方法的重载,可以把我们想要的数据暂存在Intent中,启动了另一个活动后再取出e.g.
//FirstActivity: button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String data = "HELLO SECOND ACTIVITY"; //显式Intent Intent intent = new Intent(FirstActivity.this, SecondActivity.class); //通过putExtra()传递数据 两个参数为键值对 intent.putExtra("extra_data", data); startActivity(intent); } }); //SecondActivity: button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //getIntent()获取用于启动当前活动的Intent Intent intent = getIntent(); //调用getStringExtra(),传入相应的键,即可传递数据 String data = intent.getStringExtra("extra_data"); Log.d("SecondActivity", data); } });
-
传递字符串使用
getStringExtra()
;传递整型数据用getIntExtra()
;传递布尔型数据用getBooleanExtra()
...以此类推
2.3.5 向上一个活动传递数据
- 与给下一个活动传递数据不同,返回上一个活动没有一个启动活动的Intent来传递数据
- 因此使用活动类的另一个
startActivityForResult()
方法,该方法也用于启动活动,且期望在活动销毁后能够返回一个结果给上一个活动
e.g.button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //显式Intent Intent intent = new Intent(FirstActivity.this, SecondActivity.class); //通过startActivityForResult()启动活动,参数为Intent和响应码,响应码用于之后的回调中辨别数据来源 startActivityForResult(intent, 1); } });
- 在下一个活动中构建一个Intent,调用不带参数的构造函数,仅用于传递数据
- 调用
setResult()
方法,专门用于向上一个活动传递数据
e.g.button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); String data = "HELLO FIRST ACTIVITY"; intent.putExtra("data_return", data); //setResult()是专门向上一活动传递数据的方法 //接收两个参数:要返回的处理结果(一般只使用RESULT_OK或RESULT_CANCELED)和带有数据的Intent setResult(RESULT_OK, intent); finish(); } });
- 由于使用
startActivityForResult()
方法启动下一个活动,在下一个活动销毁后会回调上一个活动的onActivityResult()
方法,因此需要在上一个活动中重写该方法来得到返回的数据
e.g./** * * @param requestCode 启动活动时传入的响应码 * @param resultCode 下一个活动返回的处理结果 * @param data 返回的数据 */ @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case 1: if (resultCode == RESULT_OK) { String returnData = data.getStringExtra("data_return"); Log.d("FirstActivity", returnData); } break; default: } }
- 若是通过BACK键返回上一活动的,则须在下一个活动类中重写
onBackPressed()
方法
e.g.@Override public void onBackPressed() { Intent intent = new Intent(); String data = "HELLO FIRST ACTIVITY"; intent.putExtra("data_return", data); //setResult()是专门向上一活动传递数据的方法 //接收两个参数:要返回的处理结果(一般只使用RESULT_OK或RESULT_CANCELED)和带有数据的Intent setResult(RESULT_OK, intent); finish(); }
2.4 活动的生命周期
2.4.1 返回栈
- Android使用任务(Task)来管理活动,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(BackStack)
- 每当我们按下BACK键或者finish()销毁一个活动时,这个处于栈顶活动就会出栈,上一个活动重新处于栈顶位置
2.4.2 活动的状态
- 每个活动在其生命周期都有四种状态:
- 运行状态
- 当一个活动位于栈顶位置,就处于运行状态
- 暂停状态
- 当一个活动不处于栈顶,但依然可见时
- 停止状态
- 当一个活动不处于栈顶,且不可见时
- 销毁状态
- 当一个活动从返回栈中移除后
- 运行状态
2.4.3 活动的生存期
-
活动类中定义了7个回调方法,覆盖了活动生命周期的每一个环节:
onCreat()
: 每个活动我们都重写了这个方法。它会在活动第一次创建的时候调用。你应该在这个方法中完成活动的初始化操作,如加载布局、绑定事件等。onStart()
: 这个方法在活动由不可见变为可见时调用。onResume()
: 这个方法在准备好和用户进行交互时调用。此时活动一定位于栈顶,且处于运行状态。onPause()
: 这个方法在系统准备去启动或恢复另一个活动时调用。onStop()
: 这个方法在活动完全不可见时调用。onDestroy()
: 这个方法在活动销毁前调用,之后活动的状态将变为销毁状态。一般完成内存释放操作。onRestart()
: 这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
-
活动的3种生存期:
- 完整生存期
- 活动在
onCreat()
方法和onDestroy()
方法之间经历的就是完整生存期。
- 活动在
- 可见生存期
- 活动在
onStart()
方法和onStop()
方法之间经历的就是可见生存期。 - 我们可以通过这两个方法合理地管理对用户可见的资源。
- 活动在
- 前台生存期
- 活动在
onResume()
方法和onPause()
方法之间经历的就是前台生存期。 - 在前台生存期,活动总是处于运行状态且活动是可以和用户进行交互的。
- 活动在
- 完整生存期
-
生命周期示意图:
2.4.4 体验生命周期
2.4.5 活动被回收了怎么办
- 会有通过一个活动打开另一个活动后,由于内存不足导致上一个处于停止状态的活动被回收的情况,此时上一个活动的临时数据将得不到保存,重新启动上个活动也不会调用
onRestart()
,而是重新调用onCreat()
- 此时活动类提供了
onSaveInstanceState()
回调方法,这个方法可以保证在活动被回收之前一定被调用 onSaveInstanceState()
方法携带一个Bundle
类型参数Bundle
提供了一系列保存数据的方法,如putString()
、putInt()
等,每个方法需要两个参数,即键值对onCreate()
也携带了一个Bundle
类型参数,一般情况下为null,但如果活动被回收前调用了onSaveInstanceState()
方法保存数据,那么这个参数就会带有之前保存的全部数据Bundle
提供了一系列提取数据的方法,如getString()
、getInt()
等,每个方法须传入之前保存数据的键- 可以结合Intent和Bundle一起传递数据,Bundle保存在Intent中。
2.5 活动的启动模式
- 在实际的项目中,我们应该根据特定的需求为每个活动指定恰当的启动模式
- 启动模式有4种,分别是standard、singleTop、singleTask和singleInstance
- 可以在
AndroidManifest.xml
中通过给<activity>
标签指定android:launchMode
属性来指定启动模式
2.5.1 standard
- standard是活动的默认启动模式
- 在standard模式下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶位置。对于使用standard模式的活动,系统不会判断这个活动是否已在返回栈中存在,每次启动都会创建该活动的一个新实例
- standard模式的原理示意图:
2.5.2 singleTop
- 当活动模式指定为singleTop,在启动活动时如果发现返回栈栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例
- singleTop模式的原理示意图:
2.5.3 singleTask
- 使用
singleTask
模式可以很好地解决重复创建栈顶活动的问题 - 当启动模式指定为singleTask,每次启动活动时系统会首先在返回栈中检查是否存在该活动的实例,如果发现已存在,则直接使用该实例,并把在这个活动之上的所有活动统统出栈;如果没有则创建一个新的活动实例
- singleTask模式的原理示意图:
2.5.4 singleInstance
- 不同于以上三种模式,指定为
singleInstance
模式的活动会启用一个新的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用这一个返回栈,从而达到多个程序共享一个活动实例的目的 - singleTask模式的原理示意图:
2.6 活动的最佳实践
2.6.1 知晓当前是在哪个活动
- 如何根据程序当前的界面就能判断出这是哪一个活动
- 创建一个类,继承AppCompatActivity,然后重写onCreate(),使用getClass().getSimpleName()获得当前实例的类名,并打印,最后让活动都继承这个类即可
e.g.public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle saveInstanceState) { super.onCreate(saveInstanceState); //打印当前实例的类名 Log.d("BaseActivity", getClass().getSimpleName()); } }
2.6.2 随时随地退出程序
如果我们的程序需要一个一键退出或注销的功能,如何实现?
- 用一个专门的集合类ActivityCollector对所有活动进行管理
- 在这个集合类中定义一个ArrayList数组存放活动实例,再定义数组添加、删除活动实例和注销全部活动的方法
e.g.public class ActivityCollector { public static ArrayList<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 acivity: activities) { if (!acivity.isFinishing()) { acivity.finish(); } } } }
- 在
BaseActivity
的onCreate()
方法中调用ActivityCollector
的addActivity()
方法,表示将当前正在创建的活动添加到活动管理器(ActivityCollector的ArrayList数组)中,同理在onDestroy()
方法中调用ActivityCollector
的removeActivity()
方法,表示将当前马上要注销的活动移出活动管理器 - 从此,不管在什么地方退出程序,只需调用
ActivityCollector.finishAll()
方法即可 - 要想保证程序完全退出还需在注销全部活动的代码后添加杀掉当前进程的代码:
//killProcess()方法用于杀掉当前程序的进程 //它接收一个进程id参数 android.os.Process.killProcess(android.os.Process.myPid())
2.6.3 启动活动的最佳写法
向下一个活动传递数据时,不知道启动这个活动需要传递哪些数据怎么办?
- 为避免重新阅读下个活动的代码的麻烦,我们可以在下一个活动中自定义一个
actionStart()
方法,把这个活动启动所需的数据通过actionStart()
方法的参数传递过来,在这个方法中构建Intent,把所需的数据存储到Intent中,最后调用startActivity()
方法启动该活动
e.g.//在活动中自定义启动方法: //把需要的数据都作为该方法的参数 public static void actionStart(Context context, String data1, String data2) { Intent intent = new Intent(context, SecondActivity.class); intent.putExtra("param1", data1); intent.putExtra("param2", data2); context.startActivity(intent); } //在其他活动中启动该活动: button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //调用要启动活动的启动放法 SecondActivity.actionStart(FirstActivity.this, "This is data1.", "This is data2."); } });
- 养成一个良好的习惯,给你编写的每个活动都添加类似的启动方法,这样不仅可以让启动活动变得非常简单,还可以节省不少同事来询问你的时间。