Android Dynamic Action,简称DA,是一种简便、可变Action的实现方案。DA框架的初衷是为了取代Context.startActivity的调用方式,使用建造者模式(Builder Pattern)构建交互参数,使程序更优美。DA框架能够对任何一个已经存在的Action修改,动态改变原有的跳转逻辑。值得一提的是,DA框架不仅友好地实现了与H5间的跳转交互,也解决了Activity在插件化项目的交互问题。

项目地址:https://github.com/benniaobuguai/android-dynamic-action

DA的URI基本结构

在DA框架下,Activity是一个有趣的概念实体,每一个Activity都可视作DA框架下的一种资源。对于一个客户端而言,每个Activity都是全局唯一可访问的资源,因此每个Activity都有统一资源标识符(URI)。

URI的基本结构:

scheme://com.example.project:8888/path/etc?id=1024
\-----/ \------------------/\--/ \------/\-------/
scheme host port path query parameter
\---------------------/
authority

DA框架基于标准的URI,定制了更符合Android Activity交互的URI结构。

定制后的URI基本结构:

scheme://packageId$ActionName?data={"id":"1024"}
\-----/ \------------------/\-----------------/
scheme host query parameter

基于以上协议,定义属于自己的scheme,每个Activity将具有一个可被访问的URI,你就能够像访问网页一样访问Activity啦!!!

DA的配置文件

DA框架的“动态可变性”体现在配置文件上,DA框架遵循“约定优于配置”的原则,使用更少的配置达到目的。配置文件非常简单,仅包含scheme以及包名的映射关系。

配置文件示例:

<?xml version="1.0" encoding="UTF-8"?>
<DynamicAction xmlns:opencdk="http://www.opencdk.com/dynamicaction"
opencdk:version="1.0.0" > <constant name="DA.devMode" value="true" />
<constant name="DA.scheme" value="opencdk" />
<constant name="DA.appscheme" value="opencdkexample" /> <package id="0" name="com.opencdk.da.ui" >
</package> <package id="1" name="com.opencdk.da.ui.user" >
</package> <package id="2" name="com.opencdk.da.ui.video" >
<!-- 用H5登录界面来修复有BUG的原生登录 -->
<action name="Login" from="home_login_click" to="0$Browser?title=H5登录&amp;url=http://www.opencdk.com/login.html" />
</package> </DynamicAction>

参数说明:

  • DA.devMode:开发模式,开关打开后,后台将可看到更多日志
  • DA.scheme:DA框架内部使用的scheme,建议根据自己的业务定义唯一的scheme
  • DA.appscheme:非DA框架直接使用,是App提供给第三方App的scheme
  • package:包名的映射关系,文章最后有完整配置
  • package>id:包id,与包名一一对应
  • package>name:包名
  • action:动态修复的Action
  • action>name:Action的名称
  • action>from:事件源,触发此事件的源头
  • action>to:目标地址(DA框架标准的URI数据结构)
  • action>to>title:Activity标题
  • action>to>url:网址

DA的代码实现

假设在com.opencdk.da.ui包下有LoginActivity,访问的scheme可表示为:

opencdk://1$Login

使用DA框架后,启动LoginActivity则变得非常容易:

new DA.Builder(Context)
.setHost("1$Login")
.go();

或:

new DA.Builder(Context)
.setUriString("opencdk://1$Login")
.go();

或:

new DA.Builder(Context)
.setPackageId("1")
.setActionName("Login")
.go();

非常简单的实现方式,为动态运营奠定了基础。基于DA框架,开发同学可以轻松构建项目规范,编写更优雅的代码;测试同学编写测试用例也变得更容易了。

DA的数据交互

DA框架不但支持原生Activity间的数据交互,而且也支持Activity与H5间的数据交互。保证数据协议的一致性,DA框架统一使用JSON进行数据交互(推荐使用fastjson)。

URI表示如下:

opencdk://1$Login?data={"username":"benniaobuguai"}

代码调用:

new DA.Builder(Context)
.setHost("1$Login")
.setData("{\"username\":\"benniaobuguai\"}")
.go();

DA支持Activity回调(Context.startActivityForResult)

URI表示如下:

opencdk://1$Login?data={"username":"benniaobuguai"}&requestCode=10000

代码调用:

new DA.Builder(Context)
.setHost("1$Login")
.setData("{\"username\":\"benniaobuguai\"}")
.setRequestCode(10000)
.go();

PS: 建议在Activity间交互时使用事件总线,如:EventBusotto等。

DA动态修改默认跳转

  • 用一个Activity替换另外一个Activity
<package id="2" name="com.opencdk.da.ui.video" >
<!--- 随机推荐视频列表界面修改成免费视频列表界面 -->
<action name="VideoRandomList" from="home_video_random_click" to="2$VideoFreeList" />
</package>
  • 用一个H5界面替换一个Activity
<package id="2" name="com.opencdk.da.ui.video" >
<!-- 随机推荐视频列表界面修改成H5的免费视频推荐界面 -->
<action name="VideoRandomList" from="home_video_random_click" to="0$Browser?title=精彩免费视频推荐&amp;url=http%3a%2f%2fwww.iqiyi.com%2fdianying%2ffree.html" />
</package>

注意:

  1. xml文件不支持直接使用&、<、>等特殊字符,应该使用其对应的转义字符。具体可参考:XML和HTML常用转义字符
  2. url地址需要进行URL编码,避免特殊字符对URI的解析造成影响。在线URL编码

DA启用拦截器功能

往往有些时候,我们需要对某些界面进行访问控制。

已经发布的版本,任何用户都可访问VIP视频列表。临时需求变更,只有登录的用户才能访问VIP视频列表界面,可修改配置下发:

<interceptors>
<interceptor
name="LoginInterceptor"
class="com.opencdk.da.interceptor.LoginInterceptor" >
</interceptor>
<interceptor
name="TestInterceptor"
class="com.opencdk.da.interceptor.TestInterceptor" >
</interceptor> <actionInterceptor>
<accept name="2$VideoVIPList" >
<interceptor-ref>LoginInterceptor</interceptor-ref>
</accept>
</actionInterceptor> </interceptors>

H5与原生程序的交互

引入DA框架后,在H5界面也可前往任意的原生界面,代码也非常简单。

H5代码片断:

<p><a href="opencdk://1$Login">Login</a></p>
<p><a href="opencdk://2$VideoPlay">Play Video</a></p>
<p><a href="opencdk://0$Browser?url=http://www.opencdk.com">http://www.opencdk.com</a></p>

重写WebView WebViewClient的方法shouldOverrideUrlLoading(WebView view, String url),支持自定义的scheme。

mWebView.setWebViewClient(new WebViewClient() {

  @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url != null && url.startsWith(DALoader.getScheme())) {
new DA.Builder(mContext)
.setUriString(url)
.go(); return true;
} return super.shouldOverrideUrlLoading(view, url);
}
});

DA框架解决的核心问题

  • 一个地址可达任意Activity
  • 任意一个Activity可被动态修改为另一个Activity
  • DA框架数据交互扁平化,使用JSON便于与H5交互
  • 任意Activity可替换成H5,快速修复原生突发BUG
  • 插件化项目中也能满足以上需求

DA框架所遵循的一些约定

DA框架默认遵循以下规则:

  • 配置优先级>Java代码优先级,保证通过配置可修复代码编码的缺陷。
  • 查找动态配置时,先根据name+from进行精确查找,其次根据name去查找。

示例说明

为了更好地体现动态Action,示例中在assets目录下放了多个配置文件,加载不同的配置文件就相当于网络下发了新的配置文件。

  • dynamic_action.cfg,默认的配置文件,仅包含包结构映射关系
  • dynamic_action_transfer.cfg,配置一个动态Action
  • dynamic_action_interceptor.cfg,配置一个拦截器

进阶与思考

  • 客户端与客户端交互

    客户端与客户端之间的交互是不安全的,对于暴露给第三方的入口都需要进行校验。就DA框架而言,主要是为了解决内部跳转的统一协议而确定的scheme(opencdk://),提供外部使用的scheme(opencdkexample://)。opencdk://仅供内部使用,认为是可信任的、安全的。opencdkexample://是外部协议,必须经过校验方可拉起我们的客户端。

需要提供外部入口,必须要在AndroidManifest.xml里面定义,

<activity
android:name="com.opencdk.da.ui.SplashActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/DA.Theme.NoTitleBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- 定义给外部调用 的scheme -->
<data android:scheme="opencdkexample" />
</intent-filter>
</activity>

新建一个项目,执行外部调用代码片断:

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
Uri data = Uri.parse("opencdkexample://0$Browser?url=http://v.qq.com/cover/r/rm3tmmat4li8uul/w0019k37ecc.html");
intent.setData(data);
startActivity(intent);
  • iOS对本协议的支持

    iOS并无包名概念,如果希望使用同一个URI来跳转至同一个界面,iOS在对URI的处理时,应当直接过滤包名后再使用。

    登录配置如下:
opencdk://1$Login?data={"username":"benniaobuguai"}

解析【host】协议时,直接取【ActionName】,忽略前面的 "1$" 即可。

  • 对于简单的项目,Android把所有Activity放在一个包名下(不建议这么做),也可与iOS保持URI同一处理逻辑。

    URI表示如下:
opencdk://Login?data={"username":"benniaobuguai"}

PS: DA框架当前不支持无包名的实现方式。

注意点

  • 避免循环对Action进行中转
  • 通过反射访问目标Activity,ActionName的大小写敏感
  • 避免过多的包名映射,Activity所在的包不宜过多,包越多维护成本越大。
  • Activity不能被混淆

其他

  • 运营的灵活性,增强运营配置的自由度
  • 界面可替换性,任意Activity可替换成H5,提供快速使用H5修复BUG的能力
  • 插件访问简单化,宿主程序是无法直接获取插件的Activity对象,DA框架的作用尤其明显。
  • DA框架最大的问题是全局可访问任意Activity,如何保证被访问者的安全,业务不受到影响就显示尤为重要了
  • DA框架与传统的Context.startActivity最大的区别在于:交互协议标准化、灵活、运营能力强,动态修复能力强

Q&A:

1.为什么需要制定两个scheme?

答:隔离

2.配置from to过多时, 性能如何?

答:配置from和to时,属于修复BUG的行为,原则上并不会太多。通过HashMap查找,速度很快。

3.配置文件过大时, 性能如何?

答:未验证。

完整的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<DynamicAction xmlns:opencdk="http://www.opencdk.com/dynamicaction"
opencdk:version="1.0.0" > <constant name="DA.devMode" value="true" />
<constant name="DA.scheme" value="opencdk" />
<constant name="DA.appscheme" value="opencdkexample" /> <package id="0" name="com.opencdk.da.ui" >
</package> <package id="1" name="com.opencdk.da.ui.user" >
</package> <package id="2" name="com.opencdk.da.ui.video" >
<action name="VideoRandomList" from="home_video_random_click" to="2$VideoFreeList" />
</package> <interceptors>
<interceptor
name="LoginInterceptor"
class="com.opencdk.da.interceptor.LoginInterceptor" >
</interceptor>
<interceptor
name="TestInterceptor"
class="com.opencdk.da.interceptor.TestInterceptor" >
</interceptor> <actionInterceptor>
<accept name="2$VideoVIPList" >
<interceptor-ref>LoginInterceptor</interceptor-ref>
</accept>
</actionInterceptor> </interceptors> </DynamicAction>

Android Dynamic Action(动态Action)—像访问网页一样地访问Activity的更多相关文章

  1. Android 自定义title 之Action Bar

    Android 自定义title 之Action Bar 2014-06-29  飞鹰飞龙...  摘自 博客园  阅 10519  转 25 转藏到我的图书馆   微信分享:   Action Ba ...

  2. Android网络:HTTP之利用HttpURLConnection访问网页、获取网络图片实例 (附源码)

    http://blog.csdn.net/yanzi1225627/article/details/22222735 如前文所示的TCP局域网传送东西,除了对传输层的TCP/UDP支持良好外,Andr ...

  3. Android 外部启动activity,自定义action,action常量大全

    从任意app,启动另外一个app的activity: 1.   Intent i = new Intent();           ComponentName cn = new ComponentN ...

  4. Struts2的动态Action和全局跳转视图以及配置各项默认值

    1:Struts2的默认访问后缀是.action(特别需要注意的是改了配置文件web.xml或者struts.xml需要重启服务器) 2:Struts2中常用的常量介绍:<!-- 一:全局配置 ...

  5. web10 动态action的应用

    电影网站:www.aikan66.com 项目网站:www.aikan66.com游戏网站:www.aikan66.com图片网站:www.aikan66.com书籍网站:www.aikan66.co ...

  6. struts2 动态Action

    1.java 2.struts.xml struts2.5,默认关闭动态Action,着色的是开启和使用动态action 3.JSP 小结:访问时,用!后跟方法名的方法,方法返回值----->r ...

  7. Struts2 Action 动态传参数

    Struts2的两个Action之间传参的问题. 需求功能是这样:Action1 获取数据库配置内容,得到相应Model的 动态URL ,这里的URL 有的是Action有的是JSP页面. 1.使用r ...

  8. Android开发之Intent.Action

    1 Intent.ACTION_MAIN String: android.intent.action.MAIN 标识Activity为一个程序的開始.比較经常使用. Input:nothing Out ...

  9. Android开发之Intent.Action 各种Action的常见作用

    1 Intent.ACTION_MAIN String: android.intent.action.MAIN 标识Activity为一个程序的开始.比较常用. Input:nothing Outpu ...

随机推荐

  1. go 和make的用法 区别

    Doand Make are two verbs which frequently confuse students of English. Learn the Difference between ...

  2. Python标准库之csv(1)

    Python标准库之csv(1) 1.Python处理csv文件之csv.writer() import csv def csv_write(path,data): with open(path,'w ...

  3. Python中你不知道的特性

    内置函数print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False) 本函数是实现对象以字符串表示的方式格式化输出到流文件对象fil ...

  4. 【LeetCode】Maximize Sum Of Array After K Negations(K 次取反后最大化的数组和)

    这道题是LeetCode里的第1005道题. 题目描述: 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次. ...

  5. POJ-3352 Road Construction,tarjan缩点求边双连通!

    Road Construction 本来不想做这个题,下午总结的时候发现自己花了一周的时间学连通图却连什么是边双连通不清楚,于是百度了一下相关内容,原来就是一个点到另一个至少有两条不同的路. 题意:给 ...

  6. 在LoadRunner中执行命令行程序之:popen()取代system()

    我想大家应该都知道在LoadRunner可以使用函数system()来调用系统指令,结果同在批处理里执行一样. 但是system()有个缺陷:无法获取命令的返回结果. 也许你可以用`echo comm ...

  7. redis介绍和安装和主从介绍(二)

    redis正式安装过程 安装依赖,下载解压,编译安装 yum install gcc-c++ tcl wget http://download.redis.io/releases/redis-4.0. ...

  8. [luoguP3413] SAC#1 - 萌数(数位DP)

    传送门 gtm的数位dp! 看到好多题解,都是记忆化搜索,好像非常方便啊,但是我还是用递推好了,毕竟还是有些类似数位dp的题用递推的思路,记忆化做不了,现在多培养一下思路 首先这道题, 只看长度大于等 ...

  9. BZOJ3473 字符串 【广义后缀自动机】

    题目 给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串? 输入格式 第一行两个整数n,k. 接下来n行每行一个字符串. 输出格式 一行n个整数,第i个整数表 ...

  10. 【2017.12.18】Dijkstra专题

    先友情提示一下,作者很早就会这个算法了,只不过这么久以来没怎么写过博客.现在正在学习它的拓展,干脆就把这个算法相关的内容整个敲一遍吧!本章把迪杰斯特拉从基础到拓展全都说一遍咯. 下面是优先队列(堆)优 ...