活动Activity
活动Activity是一种可以包含用户界面的组件,主要用于和用户交互。
活动的基本用法
创建和加载布局
Android程序设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局layout,布局就是用来显示界面内容的。
<?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>
通过XML文件的方式来编辑布局。
如果在XML中引用一个id,就使用@id/id_name
如果在XML中定义一个id,就使用@+id/id_name
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
}
}
setContentView()
作用:
当前活动加载一个布局。
参数:
1. 布局文件的id
在AndroidManifest文件中注册
所有活动都需要在AndroidManifest.xml中进行注册才能生效。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cunxie.helloworld">
<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/AppTheme">
<activity
android:name=".MainActivity"
android:label="This is MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
活动的注册声明要放在<application>标签内,通过<activity>标签来对活动进行注册。
在<activity>标签中使用android:name来指定具体注册哪一个活动。由于最外层的<manifest>标签已经通过package属性指定了程序的包名是com.cunxie.helloworld,因此在注册活动时可以省略前缀而直接使用.MainActivity。
配置主活动的方法就是在<acitivity>标签内部加入<intent-filter>标签,并在其中添加<action android:name="android.intent.action.MAIN" />和<category android:name="android.intent.category.LAUNCHER" />这两句声明即可。
android:lable指定活动中标题栏的内容。
Toast
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button_1 = (Button) findViewById(R.id.button_1);
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "点击了Button_1", Toast.LENGTH_SHORT).show();
}
});
}
findViewById()
作用:
获取布局文件中定义的元素
参数:
android:id属性指定的值
返回:
一个View对象,在这里需要向下转型成Button对象
setOnClickListener()
作用:
为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法
Toast.makeTest()
作用:
创建出一个Toast对象
参数:
1. Context,也就是Toast要求的上下文
2. Toast显示的文本内容
3. Toast显示的时常
Menu
<?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>
<item>标签用来创建具体的某一个菜单项,然后通过android:id给这个菜单项指定一个唯一的标识符,通过android:title给这个菜单项指定一个名称。
重写onCreateOptionsMenu()方法,重写方法使用control+O(win系统为Ctrl+O)。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
getMenuInflater()
作用:
得到MenuInflater对象
getMenuInflater().inflate()
作用:
给当前活动创建菜单
参数:
1. 指定通过哪一个资源文件来创建菜单
2. 指定菜单项将添加到拿一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的menu参数
然后onCreateOptionsMenu()中返回true,表示允许创建的菜单显示出来;返回false,表示创建的菜单无法显示。
重写onOptionsItemSelected()方法是为了定义菜单响应事件,重写方法使用control+O(win系统为Ctrl+O)。
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "You click Add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "You click Remove", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
通过调用item.getItemId()来判断点击的是哪一个菜单项,然后给每一个菜单项加入自己的逻辑处理。
销毁活动
Activity类提供了一个finish()方法。
使用Intent在活动间穿梭
Intent是Android程序中各组件之间进行交互的一种重要方法,不仅可以指明当前组件想要执行的操作,还可以在不同组件之间传递数据。Intent一般被用于启动活动、启动服务以及发送广播等场景。其中,活动Activity对象本身是一个Context对象。
Intent分为显示Intent和隐式Intent。
显式Intent
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
startActivity(intent);
}
});
Intent()
作用:
构造函数
参数:
1. Context,启动活动的上下文
2. Class,指定想要启动的目标活动
通过startActivity()方法来执行这个Intent。
隐式Intent
隐式Intent并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并找出合适的可以响应隐式Intent的活动去启动。
<activity
android:name=".FirstActivity"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="com.cunxie.helloworld.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.cunxie.helloworld.MY_CATEGORY" />
</intent-filter>
</activity>
在<action>标签中指明当前活动可以响应com.cunxie.helloworld.ACTION_START这个action
在<category>标签中包含了一些附加信息,更精确地指明了当前活动能够响应地Intent中还可能带有的category。
只有<action><category>同时匹配上Intent中指定的action和category时,这个活动才能响应该Intent。
每个Intent中只能指定一个action,但能指定多个category。
此处添加了两个<category>标签的声明,分别为android.intent.category.DEFAULT(默认的category)和com.cunxie.helloworld.MY_CATEGORY(自定义的category)。
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.cunxie.helloworld.ACTION_START");
intent.addCategory("com.cunxie.helloworld.MY_CATEGORY");
startActivity(intent);
}
});
可以调用Intent中的addCategory()方法来添加一个category,如果不添加则为默认的category。
隐式Intent不仅可以启动自己程序内的活动,还可以启动其他程序的活动。
button_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});
首先指定了Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW。
然后通过Uri.parse()方法,将一个网址字符串解析成Uri对象。
再调用Intent的setData()方法将这个Uri对象传递进去。
button_3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData((Uri.parse("tel:10010")));
startActivity(intent);
}
});
首先指定了Intent的action是Intent.ACTION_DIAL,这是一个Android系统内置的动作。
geo表示显示地理位置,tel表示拨打电话。
我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指定当前活动能够响应声明类型的数据。
android:scheme:用于指定数据的协议部分,如http
android:host:用于指定数据的主机名部分,如www.baidu.com
android:port:用于指定数据的端口部分
android:path:用于指定主机名和端口之后的部分
android:mimeType:用于指定可以处理的数据类型
只有<data>标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。
向下一个活动传递数据
Intent中提供了一些列putExtra()方法的重载,可以把想传递的数据暂存再Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出即可。
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data = new String("abc123");
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
intent.putExtra("extra_data", data);
startActivity(intent);
}
});
putExtra()
作用:
传递数据
参数:
1. 键,用于后面从Intent中取值
2. 真正要传递的数据
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("FirstActivity", data);
首先通过getIntent()方法获取到用于启动的Intent,然后调用getStringExtra()方法, 传入对应的键值,就可以取得传递的数据。
传递字符串数据,使用getStringExtra()
传递整型数据,使用getIntExtra()
传递布尔型数据,使用getBooleanExtra()
返回数据给上一个活动
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, FirstActivity.class);
startActivityForResult(intent, 1);
}
});
startActivityForResult()
作用:
启动活动,并期望在活动销毁的时候能够返回一个结果给上一个活动
参数:
1. Intent
2. 请求码,用于在之后的回调中判断数据的来源,只要是一个唯一值即可
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello MainActivity");
setResult(RESULT_OK, intent);
finish();
}
重写了onBackPressed(),在按下Back键时返回数据。
这个Intent仅仅用于传递数据。把传递的数据存放在这个Intent中,然后调用setResult()方法。
setResult()
作用:
专门用于向上一个活动返回数据
参数:
1. 用于向上一个活动返回处理结果,一般使用RESULT_OK或RESULT_CANCELED
2. Intent,把带有数据的Intent传递回去
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnnedData = data.getStringExtra("data_return");
Log.d("MainActivity", returnnedData);
}
break;
default:
}
}
在第二个活动被销毁之后会回调上一个活动的onActivityResult()方法,因此需要在第一个activity中重写onActivityResult()。
onActivityResult()
作用:
上一个活动被销毁后会回调该方法, 用于得到返回的数据
参数:
1. requestCode,即在启动活动时传入的请求码
2. resutlCode,即在返回数据时传入的处理结果
3. data,即携带着返回数据的Intent
由于在一个活动中有可能调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult()这个方法中。因此首先需要检查requestCode的值来判断数据来源,再通过requestCode的值来判断处理结果是否成功,最后从data中取值。
活动的生命周期
返回栈
Android中的活动是可以层叠的。每启动一个新的活动,就会覆盖在原活动上,然后点击Back键就会销毁最上面的活动,下面的一个活动就会重新显示出来。
Android使用任务(Task)来管理活动,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。
活动状态
1. 运行状态
当一个活动位于返回栈的栈顶时,此时活动就处于运行状态。系统最不愿意回收。
2. 暂停状态
当一个活动不再处于栈顶位置,但任然可见时,此时活动就进入暂停状态(对话框形式的活动只会占用屏幕中间部分的区域)。处于暂停状态的活动仍然是活着的。系统也不愿意回收,只有在内存极低的情况下,系统才会考虑回收。
3. 停止状态
当一个活动不再处于栈顶位置,并且完全不可见时,此时活动进入了停止状态。系统会为这种活动保存相应的状态和成员变量,但并不可靠,当其他地方需要内存时,处于停止状态的活动可能会被系统回收。
4. 销毁状态
当一个活动从返回栈中移除后就变成销毁状态。系统最倾向于回收这种状态的活动。
活动的生存期
Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节。
- onCreate():会在活动第一次被创建时调用,应该在此完成活动的初始化操作,比如加载布局、绑定事件等
- onStart():在活动由不可见变为可见的时候调用
- onResume():在活动准备好和用户进行交互的时候调用,此时活动一定位于返回栈的栈顶,并且处于运行状态
- onPause():在系统准备去启动或者恢复另一个活动的时候调用,通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据
- onStop():在活动完全不可见的时候调用,和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法不会执行
- onDestroy():在活动被销毁之前调用,之后活动的状态会变为销毁状态
- onRestart():在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了
完整生存期
onCreate() -- onDestroy()。一个活动一般会在onCreate()完成各种初始化操作,在onDestroy()完成释放内存操作。
可见生存期
onStart() -- onStop()。活动对于用户总是可见的,即便可能无法和用户交互。可以通过这两个方法,合理的管理那些对用户可见的资源。比如在onStart()对资源进行加载,在onStop()对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
前台生存期
onResume() -- onPause()。活动总是处于运行状态,可以和用户进行交互。
活动被回收了怎么办
Activity提供了一个onSaveInstanceState()回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此可以通过该方法来解决活动被回收时临时数据得不到保存的问题。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "temp data you need to save";
outState.putString("data_key", tempData);
}
onSaveInstanceState()方法携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,等等。
每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。
一直使用的onCreate()方法有一个Bundle类型的参数,可以用于数据恢复。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d("MainActivity", tempData);
}
}
Intent可以结合Bundle一起用于传递数据。首先可以把需要传递的数据保存在Bundle对象中,然后再将Bundle对象存放在Intent里。到了目标活动之后先从Intent中取出Bundle,再从Bundle中一一取出数据。
活动的启动模式
启动模式一共有4种,分别为standard、singleTop、singleTask和singleInstance。
可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式。
standard
每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop
在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
singleTask
让某个活动在整个应用程序的上下文中只存在一个实例。每次启动活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
singleInstance
指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动。在这种模式下会有一个单独的返回栈来管理活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
活动的最佳实践
知晓当前是在哪一个活动
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
接下来让BaseActivity成为项目中所有活动的父类。
现在每当进入一个活动的界面,该活动的类名就会被打印出来,就可以时时刻刻知晓当前界面对应的是哪一个活动了。
随时随地退出程序
只需要用一个专门的集合类对所有的活动进行管理即可。
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();
}
}
}
}
在活动管理器中,通过一个List来暂存活动,然后提供一个addActivity()方法用于向List中添加一个活动,提供一个removeActivity()方法用于从List中移除活动,最后提供一个finishAll()方法将List中存储的活动全部销毁掉。
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()方法,表明将当前正在创建的活动添加到活动管理器中;重写onDestroy()方法并调用ActivityCollector的removeActivity()方法,表明将一个马上要销毁的活动从活动管理器里移除。
在任何地方想退出程序,只需要调用ActivityCollector.finish()即可。