Intent 与页面跳转
掌握显式与隐式 Intent、IntentFilter、参数传递、ActivityResult API 与深链接 App Links,构建可互操作的页面跳转体系
学习目标
- 区分显式 Intent 与隐式 Intent 的使用场景与构造方式
- 熟练编写 IntentFilter 让 Activity 响应系统/其他 App 的跳转请求
- 用 ActivityResult API 替代过时的 startActivityForResult,处理回传结果
- 理解深链接 deeplink 与已验证 App Links 的差异并完成接入
学习目标
Intent 是 Android 中“启动 / 通信”的核心信使,几乎所有页面跳转、组件调用、跨 App 协作都依赖它。本章目标:
- 在显式与隐式 Intent 间正确取舍;
- 用 IntentFilter 让你的 Activity 响应特定动作;
- 用现代
ActivityResultAPI 替代startActivityForResult处理“启动并等结果”的场景; - 接入 deeplink 与 App Links,让其他 App 或网页能跳转到你的具体页面。
显式 Intent vs 隐式 Intent
1. 显式 Intent:明确指定目标
public class MainActivity extends AppCompatActivity {
private void openDetail(String id) {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("extra_id", id);
intent.putExtra("extra_show_image", true);
startActivity(intent);
}
}
显式 Intent 内部使用 ComponentName,仅会在本 App 内查找目标,常用于内部页面跳转。
2. 隐式 Intent:声明动作让系统匹配
private void openUrl(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
}
private void shareText(String text) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, text);
startActivity(Intent.createChooser(intent, "分享到..."));
}
隐式 Intent 通过 action + category + data(URI + mimeType)匹配系统所有符合条件的组件,常用于跨 App 协作(打开网页、发邮件、分享)。
注意:自 Android 11(API 30)起,应用默认看不到其他 App 导出的组件。需要在
AndroidManifest.xml中添加<queries>声明,否则隐式 Intent 可能匹配不到目标。
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<!-- 允许查询浏览器 -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<!-- 允许查询分享文本的应用 -->
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain" />
</intent>
</queries>
<application ...>
</application>
</manifest>
IntentFilter 与清单声明
1. 在 Manifest 中声明过滤器
<activity
android:name=".DetailActivity"
android:exported="false">
<!-- 隐式 Intent 过滤器 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp"
android:host="detail"
android:pathPrefix="/item" />
</intent-filter>
</activity>
2. 三大要素
action:要执行的动作,如VIEW/SEND/EDIT;category:附加分类,隐式 Intent 至少需要包含DEFAULT;data:URL 与 mimeType 约束。
3. 启动 Activity 的隐式判断
private void openWebSafe(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(this, "未找到可处理该链接的应用", Toast.LENGTH_SHORT).show();
}
}
putExtra 传参与 Parcelable
1. 基本类型传参
intent.putExtra("extra_id", "1001");
intent.putExtra("extra_count", 42);
intent.putExtra("extra_flag", true);
intent.putExtra("extra_list", new ArrayList<>(Arrays.asList("a", "b")));
接收方:
public class DetailActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
Intent intent = getIntent();
String id = intent.getStringExtra("extra_id");
int count = intent.getIntExtra("extra_count", 0);
boolean flag = intent.getBooleanExtra("extra_flag", false);
ArrayList<String> list = intent.getStringArrayListExtra("extra_list");
}
}
2. 复杂对象:Parcelable
public class Article implements Parcelable {
private String id;
private String title;
private long updateTime;
public Article(String id, String title, long updateTime) {
this.id = id;
this.title = title;
this.updateTime = updateTime;
}
protected Article(Parcel in) {
id = in.readString();
title = in.readString();
updateTime = in.readLong();
}
public String getId() { return id; }
public String getTitle() { return title; }
public long getUpdateTime() { return updateTime; }
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(title);
dest.writeLong(updateTime);
}
@Override
public int describeContents() { return 0; }
public static final Creator<Article> CREATOR = new Creator<Article>() {
@Override
public Article createFromParcel(Parcel in) { return new Article(in); }
@Override
public Article[] newArray(int size) { return new Article[size]; }
};
}
// 启动时
intent.putExtra("extra_article", new Article("1001", "标题", System.currentTimeMillis()));
// 接收时
Article article = getIntent().getParcelableExtra("extra_article");
提示:传递大型 Parcelable 对象仍然受 Binder 事务大小限制(约 1MB),超过会抛
TransactionTooLargeException。建议传递 ID,目标页加载。
ActivityResult API:现代化回传结果
1. 旧 API 的痛点
startActivityForResult(intent, REQUEST_CODE) + onActivityResult(requestCode, resultCode, data) 存在以下问题:
- 回调与启动代码分离,难维护;
- requestCode 需要手动管理;
- 在多 Fragment 时易冲突。
2. 新 API:ActivityResultLauncher
public class MainActivity extends AppCompatActivity {
// 注册一个 launcher,回调内联到启动处
private final ActivityResultLauncher<Intent> mDetailLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
String returned = result.getData().getStringExtra("extra_returned");
// 处理回传结果
}
});
private void openDetailForResult(String id) {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("extra_id", id);
mDetailLauncher.launch(intent);
}
}
3. 目标页回传结果
public class DetailActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
findViewById(R.id.btn_confirm).setOnClickListener(v -> {
Intent data = new Intent();
data.putExtra("extra_returned", "OK from detail");
setResult(RESULT_OK, data);
finish();
});
findViewById(R.id.btn_cancel).setOnClickListener(v -> {
setResult(RESULT_CANCELED);
finish();
});
}
}
4. 预置契约:简化常用场景
// 选图
private final ActivityResultLauncher<String> mPickImage = registerForActivityResult(
new ActivityResultContracts.GetContent(),
uri -> {
if (uri != null) {
ImageView iv = findViewById(R.id.iv_preview);
iv.setImageURI(uri);
}
});
// 拍照
private final ActivityResultLauncher<Uri> mTakePhoto = registerForActivityResult(
new ActivityResultContracts.TakePicture(),
success -> {
if (success) {
// 图片已保存到指定 Uri
}
});
// 申请权限
private final ActivityResultLauncher<String> mRequestPermission = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
granted -> {
if (granted) {
// ...
}
});
// 调用:mPickImage.launch("image/*");
深链接 Deeplink
1. 自定义 Scheme
<activity
android:name=".DetailActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="detail" />
</intent-filter>
</activity>
外部网页可以通过 <a href="myapp://detail?id=1001"> 触发跳转。在 DetailActivity 中解析参数:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
Uri data = getIntent().getData();
if (data != null) {
String id = data.getQueryParameter("id");
// 渲染对应内容
}
}
自定义 scheme 简单但有缺陷:任意 App 都可声明同样的 scheme,存在被劫持风险。
2. App Links(已验证链接,Android 6+)
App Links 通过 HTTPS + 域名验证,确保链接只能由你的 App 打开,避免劫持。
步骤 1:声明 intent-filter
<activity
android:name=".DetailActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.myapp.com" />
</intent-filter>
</activity>
步骤 2:上传 assetlinks.json
在 https://www.myapp.com/.well-known/assetlinks.json 上传如下文件(基于你的签名 SHA256):
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": ["AB:CD:EF:..."]
}
}
]
步骤 3:测试
// 调试时手动触发验证
try {
boolean verified = getPackageManager()
.getPackageInfo(getPackageName(), PackageManager.GET_INTENT_FILTERS)
.toString() != null; // 简化示例
} catch (Exception ignored) { }
// Android 12+ 可监听 VerificationOutcome
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// ...
}
};
调试时可用
adb shell pm verify-app-links --package com.example.myapp,并用adb shell pm get-app-links com.example.myapp查看验证状态。
常见坑与最佳实践
- 隐式 Intent 找不到目标却未判断
resolveActivity:会抛ActivityNotFoundException。在 API 30+ 上即使有 App 能处理,没有<queries>声明也可能找不到。 - 传递大对象:Binder 事务上限约 1MB。传 ID 让目标页加载是更稳的做法。
registerForActivityResult必须在STARTED状态之前调用:通常放在字段初始化或onCreate中,不能在按钮点击回调里再注册。setResult后忘了finish():用户按返回键也会触发RESULT_CANCELED,不要在按下“确认”时还没 setResult 又 finish。Intent.createChooser与直接startActivity:直接启动只让用户选择“默认应用”,chooser 会强制弹出选择面板,更适合分享类场景。- App Links 验证失败:检查
assetlinks.json是否 HTTPS 可访问、SHA256 是否为发布签名的指纹、是否有autoVerify="true"。 exported属性:Android 12+ 强制要求所有含intent-filter的组件显式声明android:exported,否则无法安装。FLAG_ACTIVITY_*滥用:除非有明确需求,不要叠加FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK,可能引发回退栈错乱。
章节小结
- 显式 Intent 用于 App 内跳转,隐式 Intent 用于跨 App 协作;
- IntentFilter 的
action/category/data共同决定匹配范围,Android 11+ 别忘了加<queries>; - 现代
ActivityResultLauncher取代startActivityForResult,回调内联、类型安全; - 自定义 scheme 简单但有劫持风险,App Links 通过 HTTPS + 域名验证确保链接专属。
下一章预告
第 14 章将进入 权限管理:normal 与 dangerous 权限的分类、清单声明、运行时申请(requestPermissions 与 ActivityResultContracts.RequestPermission),以及权限被拒后如何引导用户到系统设置。