活动
- 定义:一种可以包含用户界面的组件,主要用于和用户进行界面交互。
- 一个程序可以包含零个或多个活动,但是不包含任何活动的程序很少见。
活动的基本用法
手动创建活动
-
右击app/src/main/java/com.example.activitytest包->New->Activity->Empty
Activity(不勾选Generate Layout File和Launcher Activity)勾选Generate Layout File表示会自动为FirstActivity创建一个对应的布局文件 勾选Launcher Activity表示会自动生成将FirstActivity设置成当前项目的主活动
-
勾选Backwards Compatibility
勾选Backwards Compatibility表示会为项目启用向下兼容的模式
-
点击Finish完成创建
创建,编辑以及加载布局
创建布局
- 右击app/src/main/res->New->Directory,创建名为layout的目录
- 对着layout目录右键->Layout resource file
- 在新弹出的新建布局资源文件的窗口上将该布局文件命名为first_layout,根元素默认选择为LinearLayout
- 点击ok完成创建
编辑布局
Android Studio提供的编辑器
右上角Design是当前的可视化布局编辑器,可以预览当前的布局以及通过拖放的方式编辑布局
右上角Code通过XML文件的方式编辑布局
为布局添加按钮
点击Code,修改以下代码
<?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">
</LinearLayout>
修改后,添加了一个Button元素且在元素内部添加了几个属性
<?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:text="Button 1"
android:layout_height="wrap_content" />
</LinearLayout>
Button内部添加的属性
-
android:id 给当前的元素定义一个唯一标识符,之后可以在代码中对这个元素进行操作
在XML中引用id,使用语法@id/id_name 在XML中定义id,使用语法@+id/id_name
-
android:layout_width 指定了当前元素的宽度,match_parent表示与父元素一样宽
-
android:layout_height 指定了当前元素的高度,wwrap_content表示高度刚好包含内容
-
android:text 指定了元素中显示的文字内容
加载布局
在FirstActivity的onCreate()方法中加入以下代码
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);//新加入的
}
}
调用setContentView()方法给当前活动加载一个布局文件id,刚才创建的first_layout.xml布局的id已经添加在R文件中了
在AndroidManifest文件中注册
注册活动
实际上FirstActivity已经被AndroidStudio注册过了
代码如下
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.activicitytest">
<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.ActivicityTest">
<activity android:name=".FirstActivity"></activity>
</application>
</manifest>
- 活动的声明应该放在
<application>
标签内,这里是通过<activity>
标签来进行注册的- .Firstivity其实是com.example.activicitytest.Firstivity的缩写,但是外层< mainfest >标签已经通过package属性指定了程序的包名,所以不用再写
为活动配置主活动
实际上光是注册号活动后的程序也是不能运行的,因为还没有位程序配置主活动,程序运行时不知道要先运行哪个,其实只需要在<activity>
标签的内部加入<intent -filter>
标签,并在这个标签中加入<action android:name="android.intent.action.MAIN"/>
和<category android:name="android.intent.category.LAUNCHER"/>
这两句声明即可
制定活动中标题栏中的内容
使用android:label指定活动中标题栏的内容
给主活动指定的label不仅会成为标题栏中的内容,还会称为启动器中应用程序显示的名称。
在活动中使用Toast
一种很好的提醒方式,在程序中可以使用并将一些短小的信息通知给用户而且不会占用屏幕空间
Toast的使用
- 定义一个弹出Toast的触发点,设置点击Button 1时弹出一个Toast,在onCreate()方法中添加如下代码
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1=(Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this,"You cliced Button 1",Toast.LENGTH_SHORT).show();
}
});
}
- 通过findViewById()方法获取在布局文件中定义的元素,传入R.id.button_1来得到按钮的实例(这个值是刚才在first_layout.xml中通过android:id属性指定的)
- findViewById()方法返回的是一个View对象,将它向下转型成Button对象,得到按钮的实例后,通过调用setOnClickListener()方法为按钮注册一个监视器,点击按钮就会执行监视器中的onClick()方法。所以弹出Toast的方法需要写在onClick()方法中
- Toast的用法:通过静态方法makeText()创建一个Toast对象,然后调用show()将Toast显示出来
注:
makeText()需要传入三个参数
1.Context即Toast要求的上下文,活动本身就是一个Context对象,所以直接传入FirstActivity.this即可
2.Toast显示的文本内容
3.Toast显示的时常,有两个内置常量可以选择Toast.LENGTH_SHORT和Toast.LENGTH_LONG
在活动中使用Menu
不占用任何屏幕空间并且让菜单得到展示的步骤
- 右击res目录->New->Directory,输入文件名,点击OK
- 右击menu文件夹->New->Menu resource file,文件名输入main,点击OK完成创建
- 在main.xml中添加以下代码
<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>
<item>
标签用来创建具体的某一个菜单项,通过android:id给菜单项指定唯一的标识符,通过android:title给这个菜单项指定一个名称
- 重新回到FirstActivity中重写onCreateOptionsMenu()方法,重写方法可以使用Ctrl+O快捷键,然后在onCreateOptionsMenu()方法中添加如下代码
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return super.onCreateOptionsMenu(menu);
}
- 通过getMenuInflater()方法可以得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了
- inflate()方法接收两个参数,第一个用于指定通过哪个资源文件来创建菜单,这里传入R.menu.main,第二个用来指定菜单项将添加到哪一个Menu对象中,这里直接采用传入的menu参数,然后给该方法返回true,表示允许创建的菜单可以显示出来,否则将无法显示。
- 定义菜单响应事件,在FirstActivity中重写onOptionsItemSelected()方法
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()){
case R.id.add_item:
Toast.makeText(this,"You clicked Add",Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this,"You clicked Remove",Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
通过调用item.getItemId()来判断点击的是哪一个菜单项,后给每个菜单项加入自己的逻辑处理
- 最后运行程序即可看到生成的菜单项
销毁一个活动
法一
按一下Back键
法二,通过代码
修改按钮监听器中的代码,如下
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Toast.makeText(FirstActivity.this,"You cliced Button 1",Toast.LENGTH_SHORT).show();
finish();
}
});
重新运行程序。此时点击一下按钮,当前活动就可以被销毁
使用Intent在活动之间穿梭
创建第二个活动
- 右击com.example.activitytese包->New->Activity->Empty Activity,将活动命名为SecondActivity
- 勾选Generate Layou File,将文件命名为second_layout,不勾选Launcher Activity
- Finish完成创建
- 自动生成SecendActivity.java和second_layout.xml之后修改second_layout.xml(定义一个新的按钮,具体代码见创建布局part)
- 此时该活动已经被Android Studio在Androidmanifest.xml中注册完毕,由于此活动不是主活动,所以不用配置
<intent_filter>
标签中的内容
启动第二个活动
1. 显式intent
Intent有很多构造函数的重载,其中Intent(Context packageContext,Class<?>cls)构造函数需要接收两个参数,第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent的“意图”
用法
使用Activity类提供的startActivity()方法,该方法专门用于启动活动,接受一个Intent参数,将我们构造好的Intent传入startActivity()方法即可,修改FirstActivity中按钮的点击事件,代码如下:
public void onClick(View v) {
//Toast.makeText(FirstActivity.this,"You cliced Button 1",Toast.LENGTH_SHORT).show();
//finish();
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
**解析:**我们构建出了一个Intent传入FirstActivity.this作为下文,传入Second-Activity.class作为目标活动,这样我们的意图就是,在FirstActivity这个活动的基础上打开SecondActivity这个活动,然后通过startActivity()方法来执行这个Intent,因为意图非常明显,所以叫做显式Intent
2. 隐式Intent
并不指出我们想要启动的活动,而是指定一系列更为抽象的action和category等信息,然后叫系统分析这个intent,并找出合适的活动启动,通常在<activity>
标签下配置<intent-filter>
的内容,可以指定当前活动能够响应的action和category,打开AndroidManifest.xml修改成如下
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<action>
标签指明了当前活动可以响应com.example.activitytest.ACTION_START这个action<category>
标签包含了一些附加信息,更精确的指明了当前的活动能够响应的Intent中还可能带有的category,只有<action><category>
中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent
修改FirstActivity中按钮的点击事件,代码如下
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
Intent intent=new Intent("com.example.activitytest.ACTION_START");
startActivity(intent);
}
});
这里使用了Intent的另一个构造函数,直接将action的字符串传了进去,表明我们想要启动能够响应com.example.activitytest.ACTION_START这个action的活动,虽然前面说只有
<action><category>
都匹配上才能响应,但是android.intent.category是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中去
3.更多隐式Intent的用法(打开浏览器)
使用隐式除了启动自己程序内的活动,而且可以启动其他程序的活动,例如应用程序中需要展示一个网页,这时候就没必要自己去实现浏览器,只需要调用系统的浏览器来打开这个网页就行了,修改FirstActivity中按钮点击事件的代码,如下所示
public void onClick(View v) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//Intent intent=new Intent("com.example.activitytest.ACTION_START");
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));//接收一个Uri对象,主要用于指定当前Intent正在操作的数据,这些数据都是以字符串的形式传入Uri.parse()方法中解析产生的
startActivity(intent);
}
- 这里指定的Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值是android.intent.action.VIEW
- 通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去
- 此时在FirstActivity界面点击按钮就可以看到打开了系统浏览器
与此对应,我们可以在<intent-filter>
标签中在配置一个<data>
标签来更精确的指定当前活动能够响应什么类型的数据。<data>
可以配置的内容如下:
- android:scheme 用来指定数据的协议部分,如上述的http部分
- android:host 用来指定数据的主机部分,如上述的ww.baidu.com部分
- android:port 用来指定主机名和端口之后的部分,一般紧随在主机之后
- android:mimeType 用于指定可以处理的数据类型,允许使用通配符的方式进行指定
只有<dtata>
标签中指定的内容和Intent携带的Data完全一致时,当前活动才能够响应该Intent,不过一般在<data>
标签中都不会指定过多的内容,如上方浏览器实例中,其实只需要指定android:scheme为http,就可以响应所有的http协议的Intent了
通过在<intent-filter>
标签中配置一个<data>
标签来打开浏览器
- 新建活动,编辑third_layout.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=".ThirdActivity">
<Button
android:id="@+id/button_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 3"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- 修改AndroidManifest.xml文件如下
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http"/>
</intent-filter>
</activity>
此时点击Button 1时就会弹出选择响应哪个程序
通过在<intent-filter>
标签中配置一个<data>
标签来打开拨号界面
3. 修改FirstActivity中的onClick()方法如下
public void onClick(View v) {
//Toast.makeText(FirstActivity.this,"You clicked Button 1",Toast.LENGTH_SHORT).show();
//Intent intent=new Intent("com.example.activitytest.ACTION_START");
//Intent intent=new Intent(Intent.ACTION_VIEW);
Intent intent=new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
指定的Intent的action时Intent.ACTION_DIAL这又是一个Android系统的内置动作,data部分指定了协议时tel,号码是10086
向下一个活动传递数据
- 概念:使用Intent在启动活动时传递数据:
Intent中提供了一系列putExtra()方法的重载,可以把想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就好了。 - 例子:比如说FirstActivity中有一个字符串,现在把这个字符串传递到SecondActivity中,代码如下:
public void onClick(View v) {
String data="Hello SecondActivity";
//显示Intent方式来启动活动
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
}
putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传输的数据,在SecondActivity中将传递的数据取出,并打印,代码如下:
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Intent intent=getIntent();
String data=intent.getStringExtra("extra_data");//获取传递的数据,如果是整型就用getIntExtra()方法就可以了
Log.d("SecondActivity",data);
}
}
- 通过getIntent()方法获取到用于启动SecondActivity的Intent
- 调用getString Extra()方法,传出响应的键值,就可以得到传递的数据了
- 点击Button 1就会跳转到SecondActivity,查看logcat打印信息
返回数据给上一个活动
Activity中还有一个startActivityForResult()方法也是用于启动活动的,但该方法期望在活动销毁的时候能够返回一个活动给上一个活动。
- startActivityForResult()方法接收两个参数,第一个还是Intent,第二个是请求码,用于在之后的回调中判断数据的来源,修改FirstActivity中按钮的点击事件,代码如下
public void onClick(View v) {
//显示Intent方式来启动活动
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
}
});
这里使用startActivityForResult()方法来启动SecondActivity,请求码只要是一个唯一的值就可以了,这里传入了1,接下来在SecondActivity中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑,代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Button button2=(Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Intent intent=new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}
});
这里构建的Intent只是用于传递数据,没有指定任何的意图,然后把要传递的数据存放在Intent中,然后调用了setResult()方法。该方法用于向上一个活动传递数据,接受两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把有数据的Intent传递回去,然后调用了finish()方法来销毁当前活动
由于使用了startActivityForResult()方法来启动SecondActivity,在该活动被销毁后会回调上一个活动的onActivityResult()方法,所有我们需要在FirstActivity中重写这个方法来得到返回的数据,如下
@Override
public void onActivityResult(int requestCode,int resultCode,Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("Firstivity", returnedData);
}
break;
default:
}
}
onActivityResult(0方法带有三个参数,第一个参数requestCode,是我们启动活动时传入的请求码,第二个参数resultCode,即我们在返回数据时传入的处理结果,第三个参数data,即携带着返回数据的Intent,由于在一个活动中有可能调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回都会回调到onActivityResult()这个方法中,因此我们首先要做的就是通过检查requestCode的值来判断数据来源。确定数据是从SeconActivity返回的之后,我们再通过resultCode的值来判断处理结果是否成功。最后从data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。
可以通过在SecondActivity中重写onBackPressed()方法来解决用户需要用Back键来回到FirstActivity且数据无法返回的问题,代码如下:
public void onBackPressed(){
Intent intent=new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}
活动的生命周期
返回栈
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也叫做返回栈,,当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,前一个入栈的活动就会处于栈顶的位置,系统总会显示处于栈顶的活动给用户。
活动状态
- 运行状态
当一个活动位于返回栈的栈顶时,活动就处于运行状态,系统最不愿意回收的就是处于运行状态的活动,因为体验感极差。 - 暂停状态
当一个活动不在处于栈顶位置,但是仍然可见,这是活动就处于暂停状态,系统也不愿意回收这种活动,,只有在内存奇低的情况下,系统才会考虑回收这种活动。 - 停止状态
当一个活动不在处于栈顶位置且完全不可见的时候,就进入了暂停状态,系统仍然会为这种活动保存相应的的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存的时候,处于停止状态的活动有可能被系统回收 - 销毁状态
活动从返回栈中移除后就变成了销毁状态,系统最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
活动的生存期
覆盖了活动生命周期的每一个环节的Activity类中定义的7个回调方法
- onCreate()会在活动第一次被创建的时候调用,我们应该在这个方法中进行活动的初始化操作
- onStart()在活动由不常见变为可见的时候调用
- onResume()在活动准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶,并且处于运行状态
- onPause()在系统准备去启动或恢复另一个系统的时候调用,通常在这个方法中消耗CPU的资源释放掉以保存一些关键的数据,但这个方法的执行必须要快,否则会影响到新的栈顶活动的使用
- onStop()在活动完全不可见的时候调用,和onPause()的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法就会得到执行,而onStop()不会执行。
- onDestory()这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态
- onRestart()这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了
以上七个方法除了onRestart()方法,其他的都是两两相对的,从而可以将活动分为3种生存期
- 完整生存期:活动在onCreate()和onDestory()之间所经历的为完整生存期,一般,一个活动会在conCreate()方法中完成各种初始化操作,在onDestory()中完成释放内存的操作
- 可见生存期:活动在onStart()和onStop()之间所经历的,在可见生存期,活动对于用户是可见的,即便有时可能无法和用户进行交互,我们可通过这两种方法合理的管理那些对用户可见的资源。例如:在onStart()方法中对资源进行加载,在onStop()对资源进行释放,从而保证处于停止状态的活动不会占用过多内存
- 前台生存期:活动在onResume()和onPause()之间所经历的,在这个期间,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我们接触最多的也是这个状态下的活动
体验活动的生命周期
- 新建名为ActivityLifeCycleTest项目并勾选Launcher Activity来将创建的活动设置为主活动
- 创建两个子活动–NormalActivity和DiaglogActivity,布局名分别取为normal_layout和diaglog_layout,并编辑normal_layout.xml和diaglog_layout.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=".NormalActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is a normal activity"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?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=".NormalActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is a normal activity"/>
</androidx.constraintlayout.widget.ConstraintLayout>
两个活动的代码几乎一模一样,接下来将活动设置成对话框模式,修改AndroidManifest.xml的<activity>
标签的配置,如下
<activity android:name=".DialogActivity" android:theme="@android:style/Theme.Dialog"></activity>
<activity android:name=".NormalActivity" />
这里是两个活动的注册代码,但是DialogActivity的代码我们给他使用了一个android:theme属性用于给当前的活动指定主题,这里@android:style/Theme.Dialog是让DialogActivity使用对话框式的主题
接下来修改activity_main.xml,代码如下
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start_normal_activity"
android:text="Start NormalActivity"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start_dialog_activity"
android:text="Start DialogActivity"/>
加入的两个按钮分别用于启动NormalActivity和DialogActivity,最后修改MainActivity中的代码如下
public class MainActivity extends AppCompatActivity {
public static final String TAG="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startNormalActivity=(Button)findViewById(R.id.start_normal_activity);
Button startDialogActivity=(Button)findViewById(R.id.start_dialog_activity);
startNormalActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,NormalActivity.class);
startActivity(intent);
}
});
startDialogActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,DialogActivity.class);
startActivity(intent);
}
});
}
protected void onStart() {
super.onStart();
Log.d(TAG,"onStart");
}
protected void onResume() {
super.onResume();
Log.d(TAG,"onResume");
}
protected void onPause() {
super.onPause();
Log.d(TAG,"onPause");
}
protected void onStop() {
super.onStop();
Log.d(TAG,"onStop");
}
protected void onDestory() {
super.onDestroy();
Log.d(TAG,"onDestory");
}
protected void onRestart() {
super.onRestart();
Log.d(TAG,"onRestart");
}
}
在onCreate()方法中,我们分别为两个按钮注册了点击事件,点击第一个按钮会启动NormalActivity,点击第二个会启动DialogActivity,然后再Activity的7个回调方法中分别打印了一句话,这样就可以通过观察日志来理解活动的生命周期
- 当MainActivity第一次被创建会依次执行onCrreate(),onStart() ,onResume()。
- 点击第一个按钮启动NormalActivity后,打印“onPause,onStop”,由于NormalActivity已经把MainActivity完全遮住,所以onPause()和onStop()都会得到执行
- 按下Back返回MainActivity会打印“onRestart onStart onResume”
- 由于之前MainActivity已经进入了停止状态,所以onRestart()会执行,之后会依次执行onStart()和onResume()方法,此时oonCreate()不会执行,因为没有重新创建MainActivity
- 然后点击第二个按钮,启动DialogActivity,打印“onPause”
- 可以看到只有onPause方法得到了执行,onStop()没有执行,因为DialogActivity并没有完全遮挡住MainActivity,此时MainActivity只是进入了暂停状态,并没有停止,所以按下Back键返回MainActivity也应该只有onResumem()执行,最后在MainActivity按下Back键退出程序,打印“onPause onStop onDestroy”
活动被回收了怎么办
当活动进入停止状态的时候是有可能被系统回收。例如:应用中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了停止状态,这个时候由于内存不足,将活动A回收掉了,然后用户按下Back键返回活动A,还是会显示活动A的,只是不会执行onReatart()方法,而是会执行活动A的onCreate()方法,因为这个情况下活动A会重新创建一次,,可活动A中可能存在临时数据和状态,若MainActivity中有一个文本输入框,现在输入了一段文字,然后启动了NormalActivity,这时刚才输入的文字全部都没了,因为MainActivity被重新创建了。
1. 保存数据
Activity中提供了一个onSaveInstanceState()回调方法,这个方法可以保证活动被回收之前一定会被调用,可以通过这个方法解决活动被回收临时数据得不到保存的问题。
onSaveInstanceState()会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据
- 可以使用putString()方法保存字符串
- putInt()保存整型数
每个保存方法需要传入两个参数,第一个是键,用于后面从Bundle中取;第二个参数是真正你要保存的内容
在MainActivity中添加如下代码就可以把临时数据保存
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData="Something you just typed";
outState.putString("data_key",tempData);
}
2. 恢复数据
我们一直使用的onCreate()方法也有一个Bundle参数,一般情况下都是null,但是在活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要通过相应的取值方法再取出数据即可。
修改MainActivity的onCreate()方法,如下:
活动的启动模式
standard、singleTop、singleTask和singleInstance,可以在AndroidManiifest.xml中通过给标签指定android:launchMode属性来选择启动模式
1. standard
活动默认的启动模式,在不指定显式的情况下所有活动都会自动使用这种启动模式。Android时使用返回栈来管理活动的,在standard模式(默认)下每启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动活动都会创建该活动的一个新的实例
打开ActivityTest项目,修改FirstActivity中onCreate()方法中的代码如下
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity",this.toString());
setContentView(R.layout.first_layout);
Button button1=(Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//显示Intent方式来启动活动
Intent intent=new Intent(FirstActivity.this,FirstActivity.class);
startActivityForResult(intent,1);
}
});
}
在FirstActivity的基础上启动FirstActivity逻辑上没什么意义,不用在意这段代码的用途,重新运行程序,连续点击两次按钮可以看到locat中打印的信息
每点击一次就会创建一个新的FirstActivity实例,需要连按三次Back才能退出程序
2. singleTop
当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例
修改AndroidManiFest.xml中Activity的启动模式如下:
<activity
android:name=".FirstActivity"
android:launchMode="singleTop"//新加的
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
此时运行就会发现已经创建了一个FirstActivity,不论你点击几下按钮都不会有新的实例出现,每次想要再启动一个FirstActivity时都会直接使用栈顶的活动,因此FirstActivity也只会有一个实例,仅按一次Back键就可以退出程序
但是如果FirstActivity不在栈顶的位置时,这时候启动FirstActivity还是会创建新的实例的,修改FirstActivity中onCreate()方法的代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FirstActivity",this.toString());
setContentView(R.layout.first_layout);
Button button1=(Button)findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//显示Intent方式来启动活动
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
}
});
}
这次点击按钮启动的是SecondActivity,然后修改SecondActivity中onCreate()方法的代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity",this.toString());
setContentView(R.layout.second_layout);
Button button2=(Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Intent intent=new Intent(SecondActivity.this,FirstActivity.class);
startActivity(intent);
}
});
}
我们在SecondActivity中的按钮点击事件里又加入了启动FirstActivity的代码重新运行程序,再FirstActivity界面点击按钮进入到SecondActivity,然后再SecondActivity界面点击按钮又会重新回到FirstActivity,查看打印信息可以看到系统创建了两个不同的FirstActivity实例,这是因为在SecondActivity中再次启动FirstActivity时,栈顶活动已经变成了SecondActivity,所以会再创建一个新的FirstActivity实例。按下Back键会返回到SecondActivity,再按一下就会回到FirstActivity了,再按一下就可以退出程序。
3. singleTask
使用该方法可以很好地解决如果活动没有在栈顶就会被重复创建的问题,就可以让某个活动在整个应用程序的上下文只存在一个实例。当活动的启动模式指定为singleTask,每次启动活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有就会创建一个新的活动实例,修改AndroidManifest.xml中FirstActivity的启动模式,代码如下:
<activity
android:name=".FirstActivity"
android:launchMode="singleTask"//新加的
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
然后在FirstActivity中添加onRestart()方法,并打印日志
protected void onRestart(){
super.onRestart();
Log.d("FirstActvity","onRestart");
}
在SecondActivity中添加onDestroy()方法,打印日志
protected void onDestroy(){
super.onDestroy();
Log.d("SecondActvity","onDestroy");
}
在FirstActivity界面点击按钮进入到SecondActivity,然后再SecondActivity界面点击按钮又会重新回到FirstActivity,查看打印信息可以发现,在SecondActivity中启动FirstActivity时会发现返回栈中已经存在一个FirstActivity的实例,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为了栈顶活动,因此FristActivity的onRestart()和SecondActivity的onDestroy()会得到执行,现在返回栈中应该只剩下 一个FirstActivity的实例了,按一下Back键就可以退出程序
4.singleInstance
指定为singleInstance模式的活动会启动一个新的返回栈来管理这个活动,使用这个模式可以解决不同程序共享同一个活动的问题,这个情况下,会有一个新的栈来管理这个需要被共享的活动,不管是哪个应用程序来访问这个活动,都公用的同一个返回栈,也就解决了共享活动的实例的问题
修改AndroidMmanifest.xml中SecondActivity的启动模式如下
<activity android:name=".SecondActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
修改SecondActivity中onCreate()方法的打印函数的代码如下:
Log.d("FirstActivity","Task id is"+getTaskId());
onCreate()中打印了当前返回栈的id,然后修改SecondActivity中onCreate()代码如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("SecondActivity","Task id is"+getTaskId());
setContentView(R.layout.second_layout);
Button button2=(Button)findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
Intent intent=new Intent(SecondActivity.this,ThirdActivity.class);
startActivity(intent);
}
});
}
同样修改了打印信息然后又修改了按钮点击事件的代码来用于启动第三个活动最后修改ThirdActivity中onCreate()方法的代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ThirdActivity","Task id is"+getTaskId());
setContentView(R.layout.third_layout);
}
重新运行程序,在FirstActivity界面点击进入SecondActivity,然后在SecondActivity界面中点击按钮进入到ThirdActivity,查看打印信息可以看到SecondActivity的Task id不同于其他的都两个,说明SecondActivity在一个单独的返回栈里,且这个栈里只有SecondActivity这一个活动,按下Back键返回会发现ThirdActivity直接返回到了FirstActivity,再按下就会返回到SecondActivity,再按下Back才会退出程序,是因为,FirstActivity和ThirdActivity是放在一个栈里的,当在ThirdActivity的界面按下Back键,ThirdActivity会出栈,那么FirstActivity就成了栈顶的活动显示在界面上然后再按下Back,栈已经空了,于是就显示了另一个栈的栈顶活动,即SecondActivity,最后按下Back所有返回栈都是空的,就自然退出了程序
活动的最佳实践
知晓现在是哪一个活动
在ActivityTest项目上新建一个类,命名为BaseActivity,这里和活动的创建并不一样,所以只需要新建一个Java类就好了,让BaseActivity继承AppCompatActivity,并重写onCreate()方法,代码如下:
public class BaseActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
我们在此方法中得到了当前的实例的名字,并通过Log打印了出来,接下来需要让BaseActivity称为ActivityTest项目中所有活动的父亲。修改FirstActivity,SecondActivity.ThirdActivity的继承结构,让他们不在继承AppCompatActivity而是继承BaseActivity,重新运行程序并观察打印信息可知,我们进入的是哪一个程序了。
随时随地退出程序
想要退出程序必须连续点击Back键,这就需要一个专门的集合类对所有的活动进行管理,新建一个ActivityCollector类作为活动管理器,代码如下
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)
activity.finish();
}
}
活动管理器中我们通过一个List来暂存活动,提供了添加活动,删除活动,删除所有活动的方法,接下来修改BaseActivity中的代码,如下:
public class BaseActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
public void Destroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
在BaseActivity的onCreate()方法中调用了ActivituCollector的addActivity()方法,表明把当前正在创建的活动添加到任务管理器里面;在BaseActivity的onDestroy()方法中调用了ActivituCollector的removeActivity()方法表明了一个马上要销毁的活动从任务管理器中移除,从此以后,如果想要退出程序,直接调用ActivituCollector.finishAll()方法即可。例如,想要在ThirdActivity界面通过点击按钮直接退出程序,只需要把代码改成如下所示:
public class ThirdActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ThirdActivity","Task id is"+getTaskId());
setContentView(R.layout.third_layout);
Button button3=(Button)findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCollector.finishAll();
}
});
}
}
也可以在销毁所有活动的代码后再加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程的代码如下所示:
android.os.Process.killProcess(android.os.Process.myPid());
killProcess用于杀掉第一个进程,,他只能杀掉当前的进程,我们不能使用这个来杀死其他的程序
启动活动的最佳写法
首先通过Intent构建出当前的意图,然后调用startActivity()或startActivityForResult()将活动启动起来,有数据需要传递也可以借助Intent来完成
假设SecondActivity中需要用到两个非常重要的字符串,在启动SecondActivity时必须传输过来,那可以这样写:
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("Param1","data1");
intent.putExtra("Param2","data2");
startActivity(intent);
这样虽然正确,但是如果我们负责的部分需要启动SecondActivity但是我们却不清楚这个活动需要传递哪些数据,换一种写法就可以解决这个问题:修改SeconActivity的代码,如下
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);
}
这个新方法完成了Intent的构建,所有SecondActivity需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到intent后,调用startActivity()启动程序,这样不仅SecondActivity所需要的数据在方法参数中就都体现出来了,这样还简化了启动活动的代码,现在只需要一行代码就可以启动SecondActivity,如下: