App 唤起场景

游戏开发中,经常存在 App 间相互跳转的场景:

  1. 从游戏跳转到第三方应用,完成指定功能后,再返回游戏 App。如 QQ/WeChat 登录、分享等。
  2. 从第三方应用拉起游戏 App。如 QQ/WeChat 游戏中心直接拉起游戏。
  3. 从 Web 页面拉起游戏 App。

App 唤起方案

指定 Activity 名拉起

1
2
3
4
5
6
7
8
9
10
// 方式1:
Intent intent = new Intent(Intent.ACTION_VIEW);
ComponentName componentName = new ComponentName("com.tencent.morris.activity", "com.tencent.morris.activity.MainProxyActivity");
intent.setComponent(componentName);
this.startActivity(intent);

// 方式2:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName("com.tencent.morris.activity", "com.tencent.morris.activity.MainProxyActivity");
this.startActivity(intent);

指定 Package 名拉起

1
2
Intent intent = getPackageManager().getLaunchIntentForPackage("com.tencent.morris.activity");
this.startActivity(intent);

通过 Scheme 拉起

1
2
3
4
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.parse("mytest://hello.world:1024/test?name=zhangsan&age=27");
intent.setData(data);
this.startActivity(intent);
1
<a href="mytest://hello.world:1024/test?name=zhangsan&age=27">打开APP页面</a>

被拉起的 App AndroidManifest 中需要新增 intent-filter 配置。

1
2
3
4
5
6
7
8
9
10
11
12
<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:host="hello.world" android:port="1024", android:path="/test" android:scheme="mytest" />
</intent-filter>

参数传递与处理

参数发送

一般将数据封装到 Bundle 中,然后通过 Intent 把 Bundle 对象传递过去。

1
2
3
4
5
6
7
// 创建一个 Bundle 对象封装 kv 数据
Bundle data = new Bundle();
data.putInt("age", 18);
data.putString("name", "Jack");

// 通过 intent 传递参数
intent.putExtra("data", data);

参数接收

被拉起的 App 需要在 Activity 激活时,处理传递数据。

  1. 拉起的Activity 未启动,此时会触发 onCreate,可以在此生命周期函数中处理数据。
  2. 拉起的Activity 已启动,此时会触发 onNewIntent,需要在此声明周期中接收并更新 intent,然后进行处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);

// 获取 intent 并处理数据
Intent intent = this.getIntent();
}

@Override
protected void onNewIntent(Intent intent) {
Log.i(TAG, "onNewIntent");
super.onNewIntent(intent);

// 保存更新 intent
setIntent(intent);

// 获取 intent 并处理数据
Intent intent = this.getIntent();
}

参数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// URI 解析
Uri uri = getIntent().getData();
if (uri != null) {
String url = uri.toString();
Log.e(TAG, "Url : " + url);

String scheme = uri.getScheme();
Log.e(TAG, "Scheme : " + scheme);

String host = uri.getHost();
Log.e(TAG, "Host : " + host);

int port = uri.getPort();
Log.e(TAG, "Port : " + port);

String path = uri.getPath();
Log.e(TAG, "Path : " + path);

String query = uri.getQuery();
Log.e(TAG, "Query : " + query);

String authority = uri.getAuthority();
Log.e(TAG, "Authority : " + authority);

String token = uri.getQueryParameter("token");
Log.e(TAG, "Token : " + token);

List<String> segments = uri.getPathSegments();
Log.e(TAG, "Segments : " + segments);

Set<String> names = uri.getQueryParameterNames();
Log.e(TAG, "QueryParameterNames : " + names);

String schemeSpecificPart = uri.getSchemeSpecificPart();
Log.e(TAG, "SchemeSpecificPart : " + schemeSpecificPart);
}

// Bundle kv 数据解析
Bundle data = getIntent().getBundleExtra("data");
if (data != null) {
for (String key : data.keySet()) {
Log.i(TAG, "[Bundle]: " + key + " = " + data.get(key));
}
}

Android Intent

Intent 是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由 Intent 来协助完成 Android 各个组件之间的通讯。

Intent 用途

  1. 启动Activity,通过Context.startActvity() / Activity.startActivityForResult()启动一个Activity;
  2. 启动Service,通过Context.startService()启动一个服务,或者通过Context.bindService()和后台服务交互;
  3. 发送Broadcast,通过广播方法Context.sendBroadcasts() / Context.sendOrderedBroadcast() / Context.sendStickyBroadcast()发给 Broadcast Receivers。

Intent 种类

  1. 显式Intent,通过组件名指定启动的目标组件。
  2. 隐式Intent,不指定组件名,而是设置Action、Data、Category,让系统来根据所有的 intent-filter 来筛选满足属性的组件,当不止一个满足时,会弹出一个让我们选择启动哪个的对话框。

Intent 属性

Intent对象大致包括7大属性:Action(动作)、Data(数据)、Category(类别)、Type(数据类型)、Component(组件)、Extra(扩展信息)、Flag(标志位)。其中最常用的是Action、Data、Extra属性。

属性 用途 备注
Action 字符串,用来指定Intent要执行的动作类别 一个 Intent Filter 可以包含多个 Action
Category 字符串,表示哪种类型的组件来处理这个Intent 一个 Intent Filter 可以包含多个 Category
Data URI 格式的数据
Type 指定数据类型 一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。
Component 指定Intent的目标组件名称 当指定了这个属性后,系统将跳过匹配其他属性,而直接匹配这个属性来启动对应的组件。
Extra key-value 扩展数据 通过调用putExtra()方法设置数据,也可以通过创建 Bundle 对象来存储所有数据,然后通过调用putExtras()方法来设置数据。
Flag 用来指示系统如何启动一个Activity 通过setFlags()或者addFlags()设置 Intent 标签。

Action

ACTION 类型 作用
ACTION_MAIN 表示程序入口
ACTION_VIEW 自动以最合适的方式显示Data
ACTION_EDIT 提供可以编辑的
ACTION_PICK 选择一个一条Data,并且返回它
ACTION_DAIL 显示Data指向的号码在拨号界面Dailer上
ACTION_CALL 拨打Data指向的号码
ACTION_SEND 发送Data到指定的地方
ACTION_SENDTO 发送多组Data到指定的地方
ACTION_RUN 运行Data,不管Data是什么
ACTION_SEARCH 执行搜索
ACTION_WEB_SEARCH 执行网上搜索
ACRION_SYNC 执同步一个Data
ACTION_INSERT 添加一个空的项到容器中

Broadcast Actions

ACTION 类型 作用
ACTION_TIME_TICK 当前时间改变,并即时发送时间,只能通过系统发送。
ACTION_TIME_CHENGED 设置时间。

Category

ACTION 类型 作用
CATEGORY_DEFAULT 把一个组件Component设为可被implicit启动的
CATEGORY_LAUNCHER 把一个action设置为在顶级执行。
CATEGORY_BROWSABLE 当Intent指向网络相关时,必须要添加这个类别
CATEGORY_HOME 使Intent指向Home界面
CATEGORY_PREFERENCE 定义的Activity是一个 Preference Panel

Flag

FLAG 类型 作用
FLAG_ACTIVITY_CLEAR_TOP 相当于SingleTask
FLAGE_ACTIVITY_SINGLE_TOP 相当于SingleTop
FLAG_ACTIVITY_NEW_TASK 类似于SingleInstance
FLAG_ACTIVITY_NO_HISTORY 当离开该Activity后,该Activity将被从任务栈中移除

Activity有四种启动模式:standard、singleTop、singleTask、singleInstance。可以在AndroidManifest.xml中activity标签的属性android:launchMode中设置该activity的加载模式。

  • standard模式:默认的模式,以这种模式加载时,每当启动一个新的活动,必定会构造一个新的Activity实例放到返回栈(目标task)的栈顶,不管这个Activity是否已经存在于返回栈中;
  • singleTop模式:如果一个以singleTop模式启动的activity的实例已经存在于返回桟的桟顶,那么再启动这个Activity时,不会创建新的实例,而是重用位于栈顶的那个实例,并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中;

注:如果以singleTop模式启动的activity的一个实例已经存在于返回桟中,但是不在桟顶,那么它的行为和standard模式相同,也会创建多个实例;

  • singleTask模式:这种模式下,每次启动一个activity时,系统首先会在返回栈中检查是否存在该活动的实例,如果存在,则直接使用该实例,并把这个活动之上的所有活动统统清除;如果没有发现就会创建一个新的活动实例;
  • singleInstance模式:总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他activity会自动运行于另一个任务中。当再次启动该activity的实例时,会重新调用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。(singleInstance即单实例)

注:前面三种模式中,每个应用程序都有自己的返回栈,同一个活动在不同的返回栈中入栈时,必然是创建了新的实例。而使用singleInstance模式可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪一个应用程序来访问这个活动,都公用同一个返回栈,也就解决了共享活动实例的问题。(此时可以实现任务之间的切换,而不是单独某个栈中的实例切换)

Android URL Scheme

Scheme://Host:Port/Path?Query

  1. 协议:Scheme
  2. 授权:Authority(Host:Port)
  3. 请求:Query(key1=value1&key2=value2)

参考文档

https://www.runoob.com/w3cnote/android-tutorial-intent-pass-data.html