模块 04:Activity 与 Fragment
目标:理解 Android 的页面模型、Activity 和 Fragment 的生命周期,以及页面间的跳转和通信。
前端对照:页面模型差异
| 前端 (SPA) | Android | 说明 |
|---|---|---|
路由页面 /home | Activity / Fragment | 一个页面 |
history.pushState() | startActivity(Intent) | 导航到新页面 |
history.back() | finish() / 返回键 | 返回上一页 |
URL 参数 ?id=123 | Intent Extras | 页面间传参 |
| 组件嵌套 | Fragment 嵌套在 Activity 中 | 页面内模块化 |
useEffect(() => cleanup, []) | onDestroy() | 组件销毁时清理 |
React Router <Outlet /> | Fragment 容器 | 动态内容区域 |
1. Activity 基础
1.1 创建 Activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 绑定布局文件(类似 React 的 render() 返回 JSX)
setContentView(R.layout.activity_main);
// 初始化视图
TextView titleText = findViewById(R.id.tv_title);
Button submitBtn = findViewById(R.id.btn_submit);
// 设置点击事件
submitBtn.setOnClickListener(v -> {
// 处理点击
titleText.setText("按钮被点击了!");
});
}
}
AppCompatActivityvsActivity:始终继承AppCompatActivity,它提供了向后兼容的 Material Design 支持。
1.2 Activity 生命周期
这是 Android 开发最核心的概念之一。类比前端的组件生命周期:
前端组件生命周期 Android Activity 生命周期
───────────── ─────────────────────
constructor() onCreate() ← 创建,初始化,绑定布局
componentDidMount() onStart() ← 可见
useEffect 中订阅 onResume() ← 获得焦点,可交互
onPause() ← 失去焦点(弹出对话框/切换应用)
onStop() ← 不可见(导航到其他 Activity)
componentWillUnmount() onDestroy() ← 销毁,清理资源完整生命周期图:
┌──────────┐
│ onCreate │ 初始化:setContentView, findViewById
└────┬─────┘
│
┌────▼─────┐
│ onStart │ 可见但可能未获焦
└────┬─────┘
│
┌──────────▼──────────┐
│ onResume │ 获取焦点,用户可交互 ← 运行状态
└──────────┬──────────┘
│
┌─────▼─────┐
│ onPause │ 失去焦点(如弹出对话框)
└─────┬─────┘
│
┌─────▼─────┐
│ onStop │ 完全不可见
└─────┬─────┘
│
┌──────▼──────┐
│ onDestroy │ 即将销毁
└─────────────┘public class MyActivity extends AppCompatActivity {
private static final String TAG = "MyActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_my);
// savedInstanceState: 从被销毁状态恢复时传入之前保存的数据
if (savedInstanceState != null) {
String savedText = savedInstanceState.getString("key_text");
}
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
// 适合:注册广播接收器、开始动画
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
// 适合:恢复相机、重新开始轮询
}
@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");
// 适合:释放所有引用、注销监听器
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 保存 UI 状态(如旋转屏幕时)
outState.putString("key_text", "保存的文本");
}
}1.3 关键生命周期场景
| 场景 | 回调顺序 |
|---|---|
| 启动 Activity | onCreate → onStart → onResume |
| 按 Home 键 | onPause → onStop |
| 从后台返回 | onRestart → onStart → onResume |
| 按返回键 | onPause → onStop → onDestroy |
| 旋转屏幕 | onPause → onStop → onDestroy → onCreate → onStart → onResume |
| A 跳到 B | A:onPause → B:onCreate → onStart → onResume → A:onStop |
| B 返回 A | B:onPause → A:onRestart → onStart → onResume → B:onStop → onDestroy |
⚠️ 屏幕旋转会销毁并重建 Activity:这是很多 Bug 的来源。解决方案:使用 ViewModel 保存数据、在
AndroidManifest.xml中配置android:configChanges="orientation|screenSize"(不推荐)、或在onSaveInstanceState中保存状态。
2. Intent:页面间导航
Intent 是 Android 中的"消息"对象,用于组件间通信。
2.1 显式 Intent(明确目标)
// 从 MainActivity 跳转到 DetailActivity
// 类似前端:router.push('/detail?id=123&name=张三')
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
// 传递参数(类似 URL 查询参数)
intent.putExtra("id", 123);
intent.putExtra("name", "张三");
intent.putExtra("is_vip", true);
// 启动 Activity
startActivity(intent);
// 如果需要返回结果(类似 await router.push())
// 新方式:ActivityResultLauncher(推荐)
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
String data = result.getData().getStringExtra("result_key");
}
}
);
launcher.launch(intent);老项目写法:startActivityForResult(已过时但老项目大量存在)
// ⚠️ 老项目中大量使用这种方式,新代码推荐用上面的 ActivityResultLauncher
// 发起请求(requestCode = 0)
Intent intent = new Intent(this, ActResponseActivity.class);
Bundle bundle = new Bundle(); // 创建包裹(类似 JS 的打包对象)
bundle.putString("request_time", DateUtil.getNowTime()); // 往包裹存入数据
bundle.putString("request_content", "你吃饭了吗?");
intent.putExtras(bundle); // 把包裹塞给意图
startActivityForResult(intent, 0); // 第二个参数为请求代码
// 接收返回数据(类似 await router.push() 的返回值)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (intent != null && requestCode == 0 && resultCode == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras(); // 从返回的意图中获取包裹
String responseTime = bundle.getString("response_time");
String responseContent = bundle.getString("response_content");
}
}Bundle 类似前端的序列化对象,支持
putString/putInt/putBoolean/putSerializable等方法。在 Activity、Fragment、Service 之间传递数据时经常使用。
2.2 在目标 Activity 中接收参数
public class DetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
// 获取传入的参数
Intent intent = getIntent();
int id = intent.getIntExtra("id", 0); // 第二个参数是默认值
String name = intent.getStringExtra("name");
boolean isVip = intent.getBooleanExtra("is_vip", false);
// 使用参数
TextView tv = findViewById(R.id.tv_info);
tv.setText("ID: " + id + ", Name: " + name);
}
}2.3 隐式 Intent(通过动作匹配)
// 打开网页(类似 window.open)
Intent webIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.example.com"));
startActivity(webIntent);
// 拨打电话
Intent callIntent = new Intent(Intent.ACTION_DIAL,
Uri.parse("tel:10086"));
startActivity(callIntent);
// 分享内容
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, "来看看这个应用!");
startActivity(Intent.createChooser(shareIntent, "分享到"));
// 发送邮件
Intent emailIntent = new Intent(Intent.ACTION_SENDTO,
Uri.parse("mailto:someone@example.com"));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "主题");
emailIntent.putExtra(Intent.EXTRA_TEXT, "正文内容");
startActivity(emailIntent);2.4 常见 Intent 操作
// 返回上一页
finish();
// 返回并传递结果
Intent result = new Intent();
result.putExtra("result_key", "操作成功");
setResult(RESULT_OK, result);
finish();
// 清空栈中所有 Activity 并跳转到新页面
// 类似:router.replace('/login') 然后清空历史
Intent loginIntent = new Intent(this, LoginActivity.class);
loginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(loginIntent);
// 跳转到某个页面,但保持当前页面在栈中
// 类似:router.push('/settings')
startActivity(new Intent(this, SettingsActivity.class));2.5 Activity 启动模式(launchMode)
在 AndroidManifest.xml 中配置:
<activity
android:name=".MainActivity"
android:launchMode="standard" /> <!-- 默认:每次都创建新实例 -->
<activity
android:name=".HomeActivity"
android:launchMode="singleTop" /> <!-- 如果已在栈顶,不创建新实例 -->
<activity
android:name=".LoginActivity"
android:launchMode="singleTask" /> <!-- 全栈唯一实例 -->
<activity
android:name=".SplashActivity"
android:launchMode="singleInstance" /> <!-- 独占一个任务栈 -->老项目中:
singleTask常见于首页和登录页,避免重复创建。
3. Fragment
Fragment 是 Activity 内的"子页面",可以复用和组合。类似前端中的路由嵌套或组件嵌套。
3.1 创建 Fragment
public class HomeFragment extends Fragment {
// 绑定布局
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 类似 Activity 的 setContentView
return inflater.inflate(R.layout.fragment_home, container, false);
}
// 视图已创建,可以 findViewById
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView title = view.findViewById(R.id.tv_title);
Button btn = view.findViewById(R.id.btn_action);
btn.setOnClickListener(v -> {
title.setText("Fragment 中的按钮被点击了");
});
}
}3.2 Fragment 生命周期
onAttach() ← Fragment 绑定到 Activity
onCreate() ← 初始化(无 UI)
onCreateView() ← 创建 UI(inflate 布局)
onViewCreated() ← UI 已创建,可以操作视图 ★ 推荐在这里初始化
onStart() ← 可见
onResume() ← 可交互
onPause() ← 失去焦点
onStop() ← 不可见
onDestroyView() ← UI 被销毁(Fragment 可能仍在回退栈中)
onDestroy() ← Fragment 被销毁
onDetach() ← 从 Activity 解绑关键差异:Fragment 有
onDestroyView()但 Activity 没有。当 Fragment 进入回退栈时,View 会被销毁但 Fragment 实例还在。下次回来时会重新调用onCreateView()。
3.3 Fragment 管理
// 添加 Fragment 到 Activity
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new HomeFragment())
.commit();
// 添加到回退栈(类似 router.push)
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new DetailFragment())
.addToBackStack("detail") // 加入回退栈
.commit();
// 传递参数给 Fragment
DetailFragment fragment = new DetailFragment();
Bundle args = new Bundle();
args.putInt("id", 123);
args.putString("name", "张三");
fragment.setArguments(args);
// 在 Fragment 中获取参数
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
int id = args.getInt("id");
String name = args.getString("name");
}
}
// 回退到上一个 Fragment(类似 router.back())
getSupportFragmentManager().popBackStack();
// 清空回退栈
getSupportFragmentManager().popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);3.4 Fragment 与 Activity 通信
// 方式 1:通过接口回调(老项目常见)
public class HomeFragment extends Fragment {
public interface OnItemSelectedListener {
void onItemSelected(String item);
}
private OnItemSelectedListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnItemSelectedListener) {
listener = (OnItemSelectedListener) context;
}
}
// 触发回调
private void onItemClick(String item) {
if (listener != null) {
listener.onItemSelected(item);
}
}
}
// Activity 实现接口
public class MainActivity extends AppCompatActivity
implements HomeFragment.OnItemSelectedListener {
@Override
public void onItemSelected(String item) {
// 处理选择事件
}
}// 方式 2:通过 ViewModel 共享(推荐,Jetpack 方式)
// 在 Activity 和 Fragment 中使用同一个 ViewModel 实例
public class SharedViewModel extends ViewModel {
private final MutableLiveData<String> selectedItem = new MutableLiveData<>();
public void select(String item) {
selectedItem.setValue(item);
}
public LiveData<String> getSelectedItem() {
return selectedItem;
}
}
// 在 Fragment 中
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelectedItem().observe(getViewLifecycleOwner(), item -> {
// 响应选择
});
// 在 Activity 中
SharedViewModel model = new ViewModelProvider(this).get(SharedViewModel.class);
model.select("某个选项");4. 四大组件速览
除了 Activity,Android 还有三个重要组件:
4.1 Service(后台服务)
// 类似前端的 Web Worker / Service Worker
public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String url = intent.getStringExtra("download_url");
// 在后台执行长时间运行的任务
new Thread(() -> {
// 下载逻辑
downloadFile(url);
stopSelf(); // 完成后停止服务
}).start();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
// 启动服务
Intent serviceIntent = new Intent(this, DownloadService.class);
serviceIntent.putExtra("download_url", "https://example.com/file.zip");
startService(serviceIntent);4.2 BroadcastReceiver(广播接收器)
// 类似 window.addEventListener('online', handler)
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
boolean isConnected = checkNetwork(context);
// 处理网络变化
}
}
}
// 动态注册(在 Activity/Fragment 中)
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
NetworkReceiver receiver = new NetworkReceiver();
registerReceiver(receiver, filter);
// 记得在 onDestroy 中注销
unregisterReceiver(receiver);4.3 ContentProvider(内容提供者)
// 用于跨应用共享数据,老项目中使用较少
// 常见场景:读取联系人、相册等系统数据
Cursor cursor = getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI,
null, null, null, null
);5. Context 概念
Context 是 Android 中最重要但也最令人困惑的概念。你可以把它理解为应用环境的引用,几乎所有操作都需要它。
// Activity 本身就是 Context
Activity activity = this; // this 就是 Context
// Fragment 中获取 Context
Context context = requireContext(); // 通用 Context
Activity activity = requireActivity(); // 宿主 Activity
// Application Context(全局生命周期)
Context appContext = getApplicationContext();
// 常见需要 Context 的场景
Toast.makeText(this, "提示消息", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(this, OtherActivity.class);
SharedPreferences prefs = this.getSharedPreferences("prefs", MODE_PRIVATE);
Resources res = this.getResources();何时使用哪种 Context:
| 场景 | 使用 |
|---|---|
| UI 操作(Toast、Dialog) | Activity Context (this) |
| 加载资源 | Activity Context |
| 长期持有引用(单例、缓存) | Application Context |
| Service 中 | Service Context (this) |
| Fragment 中 | requireContext() |
常见 Bug:在非 UI 线程或非 Activity 环境中使用 Activity Context 创建 Dialog,会导致崩溃。
6. Handler 延迟任务与页面跳转
Handler 是 Android 中处理延迟和线程间通信的核心机制,老项目中大量使用。
// 场景:启动页 3 秒后自动跳转到主页
// 前端类比:setTimeout(() => router.push('/home'), 3000)
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
TextView tv = findViewById(R.id.tv_hint);
tv.setText("3秒后进入主页...");
// 延迟 3 秒后启动跳转任务
new Handler(Looper.myLooper()).postDelayed(mGoMain, 3000);
}
private Runnable mGoMain = new Runnable() {
@Override
public void run() {
// 从 SplashActivity 跳到 MainActivity
startActivity(new Intent(SplashActivity.this, MainActivity.class));
finish(); // 关闭启动页
}
};
}// 场景:生命周期日志记录(调试常用)
// 参考自实际教程项目 ActLifeActivity.java
public class LifeCycleActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "LifeCycleActivity";
private TextView tvLife; // 声明文本视图对象
private String mStr = ""; // 日志拼接字符串
private void refreshLife(String desc) {
Log.d(TAG, desc); // 打印到 Logcat
mStr = String.format("%s%s %s\n", mStr, DateUtil.getNowTimeDetail(), desc);
tvLife.setText(mStr); // 显示在屏幕上
}
@Override protected void onCreate(Bundle s) { super.onCreate(s); refreshLife("onCreate"); }
@Override protected void onStart() { super.onStart(); refreshLife("onStart"); }
@Override protected void onResume() { super.onResume(); refreshLife("onResume"); }
@Override protected void onPause() { super.onPause(); refreshLife("onPause"); }
@Override protected void onStop() { super.onStop(); refreshLife("onStop"); }
@Override protected void onRestart() { super.onRestart(); refreshLife("onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); refreshLife("onDestroy"); }
}
implements View.OnClickListener是老项目中非常常见的模式:Activity 自身实现点击接口,通过v.getId()区分不同按钮。前端类比:class MyPage extends React.Component implements ClickHandler。
7. 线程模型
Android 有严格的线程规则:
// ⚠️ 主线程(UI 线程)规则:
// 1. 所有 UI 操作必须在主线程
// 2. 耗时操作(网络、数据库、文件)不能在主线程
// 3. 违反规则会导致 ANR(Application Not Responding)或崩溃
// 在后台线程执行耗时操作
new Thread(() -> {
// 网络请求、数据库操作等
String result = fetchDataFromNetwork();
// ⚠️ 不能在后台线程更新 UI!
// textView.setText(result); // ❌ 崩溃!
// 切回主线程更新 UI
runOnUiThread(() -> {
textView.setText(result); // ✅ 正确
});
}).start();
// 使用 Handler(老项目常见)
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
textView.setText(result);
});
// 使用 AsyncTask(已废弃,但老项目中大量存在)
// ⚠️ 了解即可,新代码不要用
class MyAsyncTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
return fetchData(params[0]); // 后台线程
}
@Override
protected void onPostExecute(String result) {
textView.setText(result); // 主线程
}
}8. 状态保存与恢复
public class FormActivity extends AppCompatActivity {
private EditText editText;
private boolean isChecked;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_form);
editText = findViewById(R.id.et_input);
// 从保存的状态恢复
if (savedInstanceState != null) {
editText.setText(savedInstanceState.getString("input_text"));
isChecked = savedInstanceState.getBoolean("checkbox_state");
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("input_text", editText.getText().toString());
outState.putBoolean("checkbox_state", isChecked);
}
}ViewModel 可以替代 onSaveInstanceState:ViewModel 在配置变更(如旋转屏幕)时不会被销毁,是更现代的状态管理方式(见模块 08)。
前端开发者备忘
| 前端概念 | Android 对应 | 备注 |
|---|---|---|
| React 组件 | Activity / Fragment | 页面/模块 |
useEffect(() => {}, []) | onCreate() | 初始化 |
useEffect(cleanup, []) | onDestroy() | 清理 |
| 组件可见时执行 | onResume() | 每次可见 |
| 组件不可见时暂停 | onPause() | 每次不可见 |
router.push(path, params) | startActivity(intent) | 导航 |
router.back() | finish() / popBackStack() | 返回 |
| URL params / query | Intent.putExtra() | 页面传参 |
useParams() | getIntent().getXxxExtra() | 接收参数 |
| Context (React) | Context (Android) | 概念不同但名称相同 |
| Web Worker | Service | 后台任务 |
addEventListener | BroadcastReceiver | 事件监听 |
window.location.hash | Activity 回退栈 | 导航历史 |
下一步
理解了 Activity 和 Fragment 后,接下来学习 模块 05:UI 布局与控件,了解如何用 XML 构建界面。