2.1 活动是什么
活动(Activity),是一种可以包含用户界面的组件,主要用户和用户进行交互,类似于 iOS 的 UIViewController。
2.2 活动的基本用法
在 Activity 中使用 Toast
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
// 获取按钮对象并设置回调方法
Button btn = findViewById(R.id.button_1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "You clicked the button 1", Toast.LENGTH_SHORT).show();
}
});
}
在 Activity 中使用 Menu
首先,右击 res 中的 menu 目录 -> New -> Menu resource file , menu 文件命名为main。代码如下:
<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>
然后,在 Activity 中重写 onCreateOptionsMenu 方法,可使用 control + o 快捷键。代码如下:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(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;
}
onCreateOptionsMenu:创建 Menu 的重载方法 onOptionsItemSelected:Menu事件响应的重载方法
两者均可使用control + o ,然后输入关键字即可智能补全代码。
销毁一个 Activity
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
该效果,和点击 Back 按钮一样。
2.3 活动之间的跳转 - 使用 intent
Intent 有意图、意向、目的之意,在 Android 中意味着活动跳转技术,类似于 iOS 中的 pushViewController。
两种 Intent 方式
- 显式 Intent
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
});
- 隐式 Intent
在 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>
在 FirstActivity 中代码如下:
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 用法之
- 打开一个网页
button1.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);
}
});
- 调用系统拨号界面
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
});
向下一个 Activity 传递数据
在第一个活动中用 putExtra() 放入数据
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("extra_data", data);
startActivity(intent);
}
});
在第二个活动中用 getStringExtra() 获取数据
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");
Log.d("SecondActivity", data);
}
}
返回数给上一个 Activity
- 在第一个活动中用 startActivityForResult 表示关注返回结果
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
}
});
- 在第二个活动中用 setResult() 返回值。除了在按钮事件中返回之外,还可以在 Back 键返回时(onBackPressed())
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
});
}s
- 在第一个活动中,重写 onActivityResult() 接收数据
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity", returnedData);
}
break;
default:
}
}
2.4 活动的生命周期
Android 使用 任务(Task)管理活动,一个任务就是一组存放在栈里的活动的集合,这个栈叫做返回栈(Back Stack)。
活动状态
-
运行状态
活动位于栈顶时,处于运行状态。系统不会回收该活动
-
暂停状态
活动不再处于栈顶,但仍然可见时,处于暂停状态。系统不会轻易回收该活动,除非内存极低。
-
停止状态
活动不再处于栈顶,且完全不可见时,处于停止状态。系统会为这种活动保存相应的状态和成员,但并不完全可靠,当其它地方需要内存时,停止状态的活动可能被回收。
-
销毁状态
活动从返回栈移除后,变成了销毁状态。系统会倾向于回收该活动
活动的生存期
Activity 类中定义了 7 个回调方法,覆盖活动生命周期的每一个环节:
-
onCreate()
在活动“第一次被创建时”调用。应在该方法中完成活动的初始化操作如:加载布局、绑定事件等
-
onStart()
在活动“由不可见变为可见时”调用,类似于 iOS 控制器的 viewDidAppear
-
onResume()
在活动“准备好和用户交互时”调用。此时活动一定处于栈顶,且处于运行状态
-
onPause()
在系统“准备去启动或恢复另一个活动时”调用,等同于 viewWillDisappear。通常会在该方法中将一些消耗 CPU 的资源释放掉,及保存一些关键数据(但该方法执行速度要快,不能影响到新的栈顶活动的使用)
-
onStop()
在活动“完全不可见时”调用,等同于 viewDidDisappear。和 onPause 方法 主要区别是:如果启动的活动是一个对话框式活动,那么 onPause 方法会执行,onStop 不会执行
-
onDestory()
在“活动被销毁之前”调用,等同于 dealloc。之后活动变为销毁状态
-
onRestart()
在活动“由停止状态变为运行状态之前”调用。
以上 7 个方法出 onRestart 之外,两两相对,从而可将活动分为 3 中生存期:
-
完整生存期
onCreate 和 onDestory 之间是完整生存期。通常,一个活动在 onCreate 中完成各种初始化操作,在 onDestory 中完成释放内存操作
-
可见生存期
onStart 和 onStop 之间是可见生存期。在此期间,活动对于用户总是可见的,即便有可能无法和用户进行交互。可通过这两个方法,管理那些对用户可见的资源。比如,在 onStart 中加载资源,在 onStop 中对资源进行释放,以保证处于停止的活动不会占用过多内存
-
前台生存期
onResume 和 onPause 之间是前台生存期。 此时活动处于运行状态可以和用户交互。
案例体会活动生命周期
相关代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate");
// 1. dialog activity button config
Button startDialogActivity = findViewById(R.id.start_dialog_activity);
startDialogActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, DialogActivity.class);
startActivity(intent);
}
});
// 2. normal avtivity button config
Button startNormalActivity = findViewById(R.id.start_normal_activity);
startNormalActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, NormalActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
-
运行工程后的界面
日志打印: -
点击 Start NormalActivity
日志打印: -
点击 Back ,回到首页的日志打印:
-
点击 Start DialogActivity
日志打印: 只有 onPause 打印,是因为 dialog 没有完全遮挡 MainActivity。所以只进入了暂停状态,没有进入停止状态 -
点击 Back,回到首页
-
在首页,点击 Back
-
再次回到 App
活动被回收的相关处理
代码示例较清晰
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
...
}
2.5 活动的启动模式
先看启动模式的设置
<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>
android:launchMode 的值有四种:
-
standard:
默认的启动模式,可打开多个处于栈顶的同类活动实例
-
singleTop:
在启动活动时,如果发现返回栈的栈顶已是该活动,则认为可直接使用它,不会再创建新的活动实例。若未在栈顶,则依旧会创建多个实例。
-
singleTask:
在整个应用程序的上下文,只存在一个实例。
-
singleInstance:
该模式会启用一个新的返回栈来管理这个活动。
2.6 活动的最佳实践
一些常用的开发技巧
知晓当前在哪个活动
定义一个 BaseActivity ,让所有活动继承自该类
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
进入一个活动后,便会打印当前活动名称
随时随地退出程序
- 新增 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) {
if (!activity.isFinishing()) {
activity.finish();
}
}
activities.clear();
// 杀掉当前进程
android.os.Process.killProcess(android.os.Process.myPid());
}
}
- 改造 BaseActivity
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
- 在某一活动中,退出程序
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();
}
});
}
}
启动活动最佳实践
简言之,启动活动的代码应写在需要启动的活动中。譬如需要启动 SecondActivity:
public class SecondActivity extends BaseActivity {
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 OnClickListener() {
@Override
public void onClick(View v) {
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
}
});