现在的互联网App经常遇到以下几种需求:
- 推送收到不同的推送,点击推送会往不同页面跳且带有不同参数,当然还有推送是无通知的方式,即:收到推送做特定的事情;
- H5项目之间跳转:因为项目里容纳里多个离线H5的资源包,并以加载网页资源的方式加载模块,这类项目之间本来是完全没有关联的,需要建立桥梁帮他们互相唤起,当然也少不了各种参数;
- 外部浏览器跳转至App的固定页面:很多时候公司产品会嵌入广告在其他App里做推广,要求点击那些广告要求带指定参数跳转App指定页面;
- 扫码方式跳转至App的固定页面:很多地下推广会以二维码方式呈现在海报上,如果用App扫码后跳指定App页面且带参数;
- 短信内容有链接,点击链接带参数跳App某个页面;
就以上这些场景是不可能每个场景都定义解析规则的,毕竟维护工作会太多,所以只要定义一套解析规则即可,scheme的解析是最特殊的,本着求同存异的思想,因此我们所有的工作以scheme开展开来。这里我给它起了一个名字:AppLink,scheme主要组成为:[scheme]://[host][:port]/[path]?[query]
,这非常类似http url的定义,我们举个例子:cbdbusbutler://Bus/OrderDetail?orderId=43423
,显而易见这是带着订单号打开汽车票订单详情页的AppLink定义。
关于用scheme用来做跳转的文章其实网上已经有很多,原理大家都很懂,但是我们需要的是真实运用,所以有必要对它进行设计,让业务接入更加容易,因此我写了一个Library,名叫AppLink,我的设计思想是将每种跳转都以一个类来描述,简单说每种跳转都单独定义一个appLink类,并且按模块划分,并且appLink的path就是对应appLink类的package,即: appLink所在packageName + moduleName + className = appLink的classpath
。总之,是通过路径反射找到对应的appLink并解析的,其实思路很easy,如何组织代码才是关键。
下面来看看我们的Library如何接入的:
1. 定义schema:
<activity android:name="com.feizhang.applink.AppLinkActivity">
<intent-filter>
<data android:scheme="my-scheme" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
schema本是用于在
AndroidManifest.xml
里定义的,但是苦于暂时没法从代码层面获取AndroidManifest.xml
里配置的scheme,因此需要代码中也要初始化。
public class MyApplication extends Application {
private static final String APP_LINK_PACKAGE = "com.feizhang.applink.sample.applink";
private static final String SCHEME = "my-scheme";
/**
* account id is usually dynamic, it maybe a phone number or a uuid generated by server according to your projects。
*/
public static String accountId = "account_123";
@Override
public void onCreate() {
super.onCreate();
AppLinkUtils.setup(APP_LINK_PACKAGE, R.drawable.nofication_small_ic, SCHEME);
// 这是对于想通过推送透传来自己控制显示通知的情况而言的,对于直接采用推送sdk来显示推送通知的朋友们可以不用注册它
// 关于PushNotificationReceiver的实现,其实利用了order broadcast的Priority特性,这里注册的Priority比较低,
// 另外一个`PushContentReceiver(下面会讲)`的Priority比较高,意味着`PushContentReceiver`没有收到message就会被
// `PushNotificationReceiver`收到了,然后它负责弹Notification;
PushNotificationReceiver.register(this, new PushNotificationReceiver() {
@Override
public String getAccount(@NonNull Context context) {
return accountId;
}
@Override
public int getSmallIcon(@NonNull Context context) {
return R.drawable.nofication_small_ic;
}
});
}
}
因为暂时无法从代码层面获取
AndroidManifest.xml
里的scheme定义,所以在代码层面也要定义下scheme(切记要一样),
2. 如何非常方便的定义解析规则:
创建package,为每个AppLink创建单独的类,并通过反射获取此类解析并触发跳转。
由上可见,每个package对应每个module,每个package下放有很多class,这些class是按功能命名的,比如:AppHome(打开app)、OrderDetail(打开订单详情页)、NewMsgAlert(通知有新消息)
下面我们以打开订单详情页的AppLink为例:
public class OrderDetail extends AppLink {
/**
* 构建收到appLink打开页面的Intent
*/
@Override
public Intent onStartActivity(@NonNull Context context) {
String orderId = params.get("orderId");
Intent intent = new Intent(context, OrderDetailActivity.class);
intent.putExtra("orderId", orderId);
return intent;
// 如果参数复杂,也可以取json转对象再取值
XXXObject object = new Gson().fromJson("json", XXXObject.class);
Intent intent = new Intent(context, OrderDetailActivity.class);
intent.putExtra("orderId", object.orderId);
return intent;
}
/**
* 和{@link #onStartActivity(Context)}一并会执行,当收到appLink时候可以做一些额外的工作
*/
@Override
public void onExecute(@NonNull Context context) {
super.onExecute(context);
Toast.makeText(context, "您的订单有更新", Toast.LENGTH_SHORT).show();
}
/**
* 是否特定账户的信息,对于特定账户的订单推送是需要账户隔离的
*/
@Override
public boolean isPrivate() {
return true;
}
/**
* 是否需要用DB存储,以DB存储是为了下次启动查询到未读状态
*/
@Override
public boolean shouldSave() {
return false;
}
}
3. AppLink的目标页面(Activity或Fragment)已经打开如何接收appLink?
这个功能场景我们非常熟悉,类似滴滴打车等待附近车辆接单等待推送,或者打开微信好友的聊天页面收到对方发来消息。
public class OrderDetailActivity extends AppCompatActivity {
private PushContentReceiver mReceiver = new PushContentReceiver() {
/**
* 告诉receiver当前可以接收处理哪些appLink,
* 在微信聊天场景就好比可以接收文字消息、表情消息、定位消息等
*/
@Override
public List<String> getAppLinks() {
return Collections.singletonList("my-scheme://product/OrderDetail");
}
@Override
public String getAccount(@NonNull Context context) {
// 账户ID按具体项目自己方式获取
return MyApplication.accountId;
}
/**
* 一般这里我们会再次调用订单接口并刷新当前页面
* @return true: 终止消息传递,即其他receiver收不到此message
*/
@SuppressLint("SetTextI18n")
@Override
public boolean onReceive(@NonNull Context context, @NonNull AppLink appLink) {
Toast.makeText(context, "订单已刷新", Toast.LENGTH_SHORT).show();
return true;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order_detail);
// 注册页页面级appLink广播接收器(内部onDestroy()会自动解除注册)
PushContentReceiver.register(this, mReceiver, true);
}
}
3. 让h5模块之间可以相互跳转:
public class DefaultWebClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView paramWebView, @NonNull final String url) {
if (url.startsWith(MyApplication.SCHEME)) {
AppLinkUtils.redirect(paramWebView.getContext, url);
return true;
}
// other case
return true;
}
}
拦截跳转url,如果发现是自定义appLink则以AppLinkUtils发起跳转。
4. 推送服务使用appLink解析:
@Override
public void onNotifactionClickedResult(Context context, XGPushClickedResult xgPushClickedResult) {
String appLink = xgPushClickedResult.getCustomContent();
AppLinkUtils.redirect(context, pushMsg.appLink);
}
以上以信鸽推送作为案例,其实对其他推送sdk都一样类似,仅仅取出appLink并redirect()而已。
至此,已经介绍完AppLink的使用方式,关于实现代码可以参考这里, 下一篇文章我在介绍下项目中各种红点提醒是如何实现的,现在想说的是它依赖appLink。