第二章 探究活动
2.2 活动的基本用法
手工建立Activity
新建项目时选择No Activity,在res文件夹下新建layout.在新建Activity选择Empty Activity.不要勾选Generate layout file,后续使用手工关联layout
- 建立Resource Layout
- 建立Activity类(Empty Activity),重写onCreate方法,并调用Layout
- 在AndroidManifest中注册Activity
AndroidManifest中内容如下:
<activity android:name=".FirstActivity"
android:label="This is the FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"></action>
<category android:name="android.intent.category.LAUNCHER"></category>
</intent-filter>
</activity>
FirstActivity.java如下
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
}
}
布局略
Toast
Toast是Android系统中的提醒方式,程序可以将短小的消息通知给用户并在一段时间后自动消失.弹出Toast的方式如下,修改FirstActivity.java
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1 = findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(FirstActivity.this,"You click Button 1",Toast.LENGTH_SHORT).show();
}
});
}
}
如上所示,findViewById()返回button对象,通过setOnClickListener()注册一个监听器,并执行onClick方法.
Toast通过makeText方法创建Toast对象,并调用show()显示出来.makeText方法的三个参数分别为:当前的Context;消息内容;显示时间的长短.
Menu
在res下新建Android Resource Directory,选择menu,并其名称为menu.在menu下新建Menu Resource File,起名为main.xml.在main.xml中定义Menu的Item.如下所示:
<?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>
<item
android:id="@+id/remove_item" android:title="Remove"></item>
</menu>
在FirstActivity.java中重写onCreateOptionsMenu()方法,
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
// return super.onCreateOptionsMenu(menu);
return true;
}
通过getMenuInfater()方法可以获取MenuInflater对象,通过inflate方法给当前资源创建菜单.inflate方法中第一个参数是获取资源文件中的menu定义,第二个参数指定菜单项目将添加到哪个menu对象中.返回true标识允许菜单被显示出来.
初始化菜单后,需要为每个菜单定义响应事件,此时需要重写onOptionsItemSelected()方法,在FirstActivity中重写此方法,代码如下:
@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;
default:
}
// return super.onOptionsItemSelected(item);
return true;
}
此时重新编译后,点击右上角菜单即可看到响应结果.完整的FirstActivity.java代码如下:
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1 = findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(FirstActivity.this,"You click Button 1",Toast.LENGTH_SHORT).show();
}
});
}
@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;
default:
}
// return super.onOptionsItemSelected(item);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
// return super.onCreateOptionsMenu(menu);
return true;
}
}
销毁活动
在终端可以使用Back键来销毁当前活动,也可以使用Activity类中的finish()方法来销毁活动.只需要在监听事件或其他需要的地方调用finish()方法即可.在FirstActivity.java中添加一个按钮并监听click,调用finish().在布局中增加一个Button,id为button1Finish.
在onCreate中增加代码如下:
Button button1Finish = findViewById(R.id.button1Finish);
button1Finish.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
Log.d("FirstActivity","Finish--");
}
});
2.3 使用Intent在活动间穿梭
显式Intent
Intent 是一个消息传递对象,您可以用来从其他应用组件请求操作.尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:
- 启动 Activity
- 启动服务
- 广播传递
Intent大致可分两类:显示Intent和隐式Intent.
按照之前的FirstActivity方式建立SecondActivity.java及响应的布局文件,布局文件起名为second_layout.注意不要勾选Launcher Activity选项.替换second_layout.xml文件内容如下:
<?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/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BUTTON 2"/>
</LinearLayout>
此时androidManifest文件中会自动注册SecondActivity,修改FirstActivity中的button1的监听,如下
@Override
public void onClick(View view) {
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
这里首先构建出了一个Intent,传入FirstActivity.this作为上下文,传入SecondActivity.class作为活动目标,通过startActivity来执行Intent.
隐式Intent
隐式需要在androidManifest中对应的activity定义intent-filter标签内指定响应的action与category,首先在SecondActivity的activity标签中声明一个action和category,如下所示:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.secondeActivity.ACTION_START1"></action>
<category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
</activity>
修改FirstActivity中Button的监听,如下
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent1 = new Intent("com.example.secondeActivity.ACTION_START1");
startActivity(intent1);
}
});
在使用隐式Intent时,必须action与category同时匹配上才会响应,由于android.intent.category.DEFAULT时一种默认的category,所以上面例子中在调用时没有指定category依然能够运行.
修改FirstActivity中Button的监听,如下
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent1 = new Intent("com.example.secondeActivity.ACTION_START1");
intent1.addCategory("com.example.activityIntentCategory.Category1");
startActivity(intent1);
}
});
此时因为在androidManifest中SecondActivity的activity没有指定com.example.activityIntentCategory.Category1,所以此时代码运行会报错,提示No Activity found to handle Intent.修改SecondActivity的activity中的intent-filter如下:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.secondeActivity.ACTION_START1"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="com.example.activityIntentCategory.Category1"></category>
</intent-filter>
</activity>
此时在进行调用就一切正常了
更多隐式Intent
在Intent中启动系统浏览器.在SecondActivity.java的Button中增加监听,调用系统的浏览器,代码如下:
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://baidu.com"));
startActivity(intent);
}
});
这里的Intent.ACTION_VIEW是安卓的内置动作,对应的常量如下
public static final String ACTION_VIEW = "android.intent.action.VIEW";
Uri.parse("http://baidu.com")将网址的字符串解析成Uri对象,通过setData方法传递.也可以在intent-filter标签中配置一个data标签,用来更精确的指定当前活动能够响应什么类型数据.data标签主要配置的内容如下:
- android:scheme .用来指定数据的协议
- android:host .用来指定数据的主机部分,如www.xxx.xx
- android:prod .用来指定数据的端口
- android:path .用来指定主机名和端口之后的内容
- android:mimeType .用来指定处理的数据类型,可以使用通配符
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
需要注意的是向 Intent 过滤器添加数据规范.该规范可以是只有数据类型(mimeType 属性),可以是只有 URI,也可以是既有数据类型又有 URI.URI 由其各个部分的单独属性指定:
<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]
用于指定网址格式的以下属性是可选的,但也相互依赖:
- 如果没有为 Intent 过滤器指定 scheme,则系统会忽略其他所有 URI 属性.
- 如果没有为过滤器指定 host,则系统会忽略 port 属性以及所有路径属性.
比如,如果需要对网址的port过滤,则必须有sheme与host属性的配置.
建立一个ThirdActivity.java来测试指定http类型是数据响应.新建Empty Activity起名为ThirdActivity,对应生成的布局起名为third_layout.其中布局内容如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BUTTON 3" />
</LinearLayout>
AndroidManifest.xml中修改ThirdActivity的注册信息,内容如下:
<activity android:name=".ThridActivity">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<data android:scheme="http"/>
</intent-filter>
</activity>
这里配置了intent-filter,指定的Intent能够响应的action为Intent.ACTION_VIEW,并指定默认的category.在配置此处是,开发工具(Android Studio提示需要增加tools:ignore="AppLinkUrlError")
此时重新运行程序,系统自动弹出列表,显示能够响应这个Intent(Intent.ACTION_VIEW)的所有程序.所以.如果选择使用浏览器打开,则此时会默认启动系统内置浏览器.如果选择使用ThirdActivity来响应Intent,虽然可以响应这个活动,但并不会加载任何网页.在上述配置中,可以将data标签中android:scheme的值就改为ftp,此时ThirdActivity则不会响应这个Intent.
测试网址的过滤,将AndroidManifest.xml中修改ThirdActivity的注册信息,内容如下:
<activity android:name=".ThridActivity">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<data android:scheme="http" android:host="www.baidu.com" android:port="9999"/>
</intent-filter>
</activity>
在SecondActivity.java中将button修改如下:
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://baidu.com:80"));
startActivity(intent);
}
});
此时,ThirdActivity不会对此Intent进行响应.
调用拨号的Intent示例,按钮监听部分如下:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
});
通用常见的Intent在官网文档中有具体的描述
向下一个活动传递数据
在Intent中提供了一些列的putExtra()方法的重载,可以将传递的数据放到Intent中.修改FirstActivity中Button的监听方法,使用Intent显示的方式传递数据.代码如下:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String data = "Hello";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
}
});
在SecondActivity中使用getIntent()方法来获取Intent并将传递的值取出来,代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity",data);
}
返回数据给上一个活动
在Activity中使用startActivityForResult()方法可以在活动销毁时返回一个结果给上一个活动,此方法接受两个参数,第一个参数是Intent,第二个参数是请求码(requestCode),修改FirstActivity中的监听,使用startActivityForResult方法来启动SecondActivity,代码如下:
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 下一个活动返回数据
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
}
});
在SecondActivity中,修改监听方法,创建一个新的Intent并设置返回值,代码如下:
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent1 = new Intent();
intent1.putExtra("data_return","Hello FirstActivity This is Second Activity");
setResult(RESULT_OK,intent1);
finish();
}
});
上面代码中,先创建了一个Intent来传递数据,然后调用setResult方法来返回处理结果,setResult()方法的第一个参数用于向上返回处理结果,一般使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数是将Intent传递回去,最后调用finish方法销毁活动.
在FirstActivity中,需要重写onActivityResult方法来获取返回的数据,代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, 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:
}
}
onActivityResult中的三个参数,其中requestCode为startActivityForResult()方法带过来的唯一键,resultCode为SecondActivity中setResult方法设置的返回值,Intent为SecondActivity中返回的Intent.此时如果用户使用返回按钮来结束活动时,需要在SecondActivity中重写onBackPassed()方法来解决此问题,并通过onBackPassed()方法中来返回数据,修改SecondActivity中代码,增加onBackPassed方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, 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:
}
}
2.4 活动的生命周期
2.4.1 返回栈
Android使用任务Task来管理活动,一个任务是一组存放在栈里的活动集合,这个集合叫做返回栈.档一个活动入栈后,调用finish方法后最先出栈.
2.4.2 活动状态
- 运行状态:此时活动处于栈顶
- 暂停状态:活动不处于栈顶,当内存极低情况下,系统才会考虑回收
- 停滞状态:活动不处于栈顶,完全不可见,进入停止状态,可能被系统回收
- 销毁状态:活动出栈,系统回收
2.4.3 活动的生命周期
- onCreate() 活动第一次被创建时候调用,所有的初始化操作在此方法内完成
- onStart() 活动由不可见到可见时被调用
- onResume() 方法在活动准备好和用户进行交互时候调用,此时活动一定处于返回栈的栈顶,并处于运行状态
- onPause() 方法在系统准备去启动或者恢复另一个活动时候调用.通常在这个方法中将一些消耗CPU的资源释放掉并保留一些关键数据,但此方法执行速度要快,不然会影响新的栈顶的活动.
- onStop() 方法在活动完全不可见时候调用,和onPause()方法的区别是如果启动的新活动是一个对话框式活动,那么onPause()方法会被调用,而onStop()方法不会被执行
- onDestroy() 方法在活动被销毁前调用,之后活动变为销毁状态
- onRestart() 方法在活动由停止变为运行之前调用,也就是活动被重新启用了
以上7个方法中,除了onRestart()方法,其他的都是两两相对的,可分为3种生存期: - 完全生存期.活动在onCreate()和onDestroy()之间经历的
- 可见生存期.活动在onStart()和onStop()之间经历的
- 前台生存期.活动在onResume()和onPause()之间经历的
2.4.5 活动被回收
onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供一列的方法保存数据,比如在MainActivity中重写此方法,将参数通过Bundle的putString()方法将值保存.MainActivity中代码如下
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Temp Data";
outState.putString("temp_key",tempData);
}
在onCreate时获取如下:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState!=null){
String temp = savedInstanceState.getString("temp_key");
Log.d(TAG,temp);
}
}
2.6 活动的最佳实践
参考书中例子,完成BaseActivity类及ActivityCollector类,实现获取当前活动及销毁所有活动.
BaseActivity.java
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);
}
}
ActivityCollector.java
public class ActivityCollector {
public static List<Activity> activityList = new ArrayList<>();
public static void addActivity(Activity activity) {
activityList.add(activity);
}
public static void removeActivity(Activity activity) {
activityList.remove(activity);
}
public static void finishAll() {
for (Activity activity : activityList) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
个人测试的Demo地址均放在了github上地址链接