Odpf框架是一种终端应用混合开发模式框架,ODPF框架是一种开发混合模式移动应用(HybridApp)的基础框架,在借鉴了Cordova、AppCan等业界主流混合模式开发平台设计思想的基础上,结合Android Fragment模式开发的优点,设计了基于Fragment的混合模式开发框架。
1. 开发工程指导
工程结构
工程的res/xml文件夹中有两个配置文件
1、 config.xml:配置js层调用native层的插件配置
配置插件:
<feature name="OdpfPlugin"> <param name="android-package" value="com.zte.umap.odpf.plugin.FragmentPlugin" /> </feature> <feature name="SoftKeyBoard"> <param name="android-package" value="com.zte.umap.odpf.plugin.IonicKeyboard" /> </feature> <feature name="ImageCache"> <param name="android-package" value="com.zte.umap.odpf.plugin.ImageCachePlugin" /> </feature>
2、url.xml:配置自定义本地view与url的映射关系,内容示例:
<?xml version="1.0" encoding="UTF-8"?> <UrlMap> <url name="contact.html" value = "com.zte.umap.test. ContactFragment"/> <url name="tabFragment.html" value = "com.zte.umap.test.TabFragment"/> </UrlMap>
Web页面放置于工程的assets/www文件夹下,必须引用 cordova.js和odpf.js文件。
Loading提示效果
Web页面加载需要时间,这时可以在界面中加载loading提示,默认显示的是白底转圈的loading提示,当业务应用需要自定义特殊loading提示时,可以自定义名为布局xml文件放置与应用的layout目录下。在相应接口传递该xml 文件名,Odpf会自动读取加载该视图作为页面加载过程中的loading视图。
应用的入口
需要新建一个Activity,并且定义一个布局文件,监听back事件
package com.zte.umap.test;
import com.zte.umap.odpf.FirstPageCompleteInterface;
import com.zte.umap.odpf.ODPFragmentPool;
import com.zte.umap.odpf.util.ResUtil;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends FragmentActivity implements FirstPageCompleteInterface
{
public static String TAG = "MainActivity";
public ODPFragmentPool fragmentPool;
private FrameLayout layout;
private ImageView splashCreenImg;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
Log.i(TAG ,"onCreate" );
// 这个布局必须为FrameLayout类型
layout = (FrameLayout) findViewById(R.id.id_content);
splashCreenImg = (ImageView) findViewById(R.id.imgSplashscreen);
//初始化资源文件获取类
ResUtil.init(getApplicationContext());
//获取window池实例
fragmentPool = ODPFragmentPool.getInstance(this);
//首页加载的纯web页面,设置加载结束通知接口
fragmentPool.setFirstPageCompleteInterface(this);
//加载首页
fragmentPool.openInit( R.id.id_content,"index.html",2,4,”progress”);
}
/*
*back 键的拦截
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
/ /首先调用池的后退处理
if (fragmentPool.goBack()) {
return true;
} else {
return super.onKeyUp(keyCode, event);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
//判断池的后退处理
if(!fragmentPool.isIndex()){
return true;
}else
{
Log.i(TAG, "onkey down");
return super.onKeyDown(keyCode, event);
}
}
@Override
protected void onDestroy()
{
Log.i(TAG," main fragment destroy");
//调用池的销毁处理
fragmentPool.destroy();
super.onDestroy();
}
/*
*该接口内隐去欢迎界面,展示首页
*/
@Override
public void firstPageProgressComplete() {
// TODO Auto-generated method stub
splashCreenImg.setVisibility(View.GONE);
layout.setVisibility(View.VISIBLE);
}
}布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <FrameLayout android:id="@+id/id_content" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <ImageView android:id="@+id/imgSplashscreen" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:src="@drawable/startup_bg_16_9"/> </RelativeLayout>
2. JS SDK API
window.umap.exec
用途:调用native方法,异步调用
结构:
function exec(var json, function success, function fail);
说明:
a) 返回值:无
b) 输入参数:json:native方法与参数定义(如:{“cls”:”Test”,“fn”:”echo”,”params”:[“str”,”hello”]},其中cls表示class名称,fn表示方法名称,params表示参数列表)
c) 输入参数:success:成功时的回调函数
d) 输入参数:fail:失败时的回调函数
使用方式:
1.首先自己实现Native的Plugin
2.在res/config.xml配置plugin
3.在js中定义json格式串
var odpf_hello = {"cls":"HelloWorld","fn":"hello","params":["value"]};
4.调用window.umap.exec
window.umap.open
用途:加载新页面
结构:
function open(var json, function success, function fail);
说明:
a) 返回值:无
b) 输入参数:json,输入参数定义,如:var page1_url = {"url":"onePage.html","anim_type":"1","isOpenNewWindow":"true",”priority”:”3” "viewId":"0"};
其中url:新页面地址,http://网络新链接;file://本地html页面, 可以省略assets/www的前缀,默认就是这个目录;content://native页面,如果在配置文件中找到以url参数为key的value值,则加载value代表的页面,如果没有找到,才加载url代表的页面;
isOpenNewWindow:这个值置为true表示 目标页采用新的window,则当前页面压栈,若false,则表示是在当前页面重新打开,当前页不压栈,举个例子说明如果:A 页面跳转至B页面,B再跳转至C页面, B至C页面跳转时设定isOpenNewWindow为true,则C后退后返回至B页面,否则返回至A页面;

anim_type:动画类型,1:新页面左入,旧页面右出,2:新页面右入,旧页面左出,3:新页面上入,旧页面下出,4:新页面下入,旧页面上出,5淡入淡出,9右切入,10左切入,11上切入,12下切入,13左切出、14右切出、15上切出、16下切出,其他数值则无动画;
priority:新页面的优先级,优先级从0至9 ,数字越大优先级越高
c) 输入参数:success:成功时的回调函数
d) 输入参数:fail:失败时的回调函数
viewed:打开的新页面的视图容器id,默认情况下都为0 ,0为应用入口openInit 传递的view对应的id,当你需要切换为视图中部分加载页面时,这个id 需相应的改变,对应于openInNewView中设置的viewid, 如果不传递该参数,平台则取默认值0
window.umap.back
用途:页面回退
结构:
function back(function success, function fail);
说明:
a) 返回值:无
b) 输入参数:success:成功时的回调函数
c) 输入参数:fail:失败时的回调函数
window.umap.clearHistory
用途:清空历史记录
结构:
function clearHistory (function success, function fail);
说明:
a) 返回值:无
b) 输入参数:success:成功时的回调函数
c) 输入参数:fail:失败时的回调函数
window.umap.clearRecycle
用途:清空缓存栈
结构:
function clearRecycle (function success, function fail);
说明:
d) 返回值:无
e) 输入参数:success:成功时的回调函数
f) 输入参数:fail:失败时的回调函数
Js访问Native存储
1、本地端设置值
获取ODPFragmentPool的对象:
ODPFragmentPool fragmentPool = ODPFragmentPool.getInstance(context);
设置需要保存的值:
public void setKeyValue(String key, String value)
例如:
fragmentPool. setKeyValue(“ip”,”10.46.75.26”)
2、JS端获取本地端保存的值:(key值要与本地端的一致)
var ip = Odpf.getValue(“ip”);
Js与native互相调用
Js调native的方法即cordova插件的编写
在native层编写相应的类和方法,类继承于CordovaPlugin,实现execute方法,在配置文件中配置,并在js层封装接口
1、Native层插件类示例:
public class IonicKeyboard extends CordovaPlugin{
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
//calculate density-independent pixels (dp)
//http://developer.android.com/guide/practices/screens_support.html
DisplayMetrics dm = new DisplayMetrics();
cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
final float density = dm.density;
final CordovaWebView appView = webView;
//http://stackoverflow.com/a/4737265/1091751 detect if keyboard is showing
final View rootView = cordova.getActivity().getWindow().getDecorView().findViewById(android.R.id.content).getRootView();
OnGlobalLayoutListener list = new OnGlobalLayoutListener() {
int previousHeightDiff = 0;
@Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
rootView.getWindowVisibleDisplayFrame(r);
int heightDiff = rootView.getRootView().getHeight() - (r.bottom);
int pixelHeightDiff = (int)(heightDiff / density);
if (pixelHeightDiff > 100 && pixelHeightDiff != previousHeightDiff) { // if more than 100 pixels, its probably a keyboard...
appView.sendJavascript("cordova.plugins.Keyboard.isVisible = true");
appView.sendJavascript("cordova.fireWindowEvent('native.keyboardshow', { 'keyboardHeight':" + Integer.toString(pixelHeightDiff)+"});");
//deprecated
appView.sendJavascript("cordova.fireWindowEvent('native.showkeyboard', { 'keyboardHeight':" + Integer.toString(pixelHeightDiff)+"});");
}
else if ( pixelHeightDiff != previousHeightDiff && ( previousHeightDiff - pixelHeightDiff ) > 100 ){
appView.sendJavascript("cordova.plugins.Keyboard.isVisible = false");
appView.sendJavascript("cordova.fireWindowEvent('native.keyboardhide')");
//deprecated
appView.sendJavascript("cordova.fireWindowEvent('native.hidekeyboard')");
}
previousHeightDiff = pixelHeightDiff;
}
};
rootView.getViewTreeObserver().addOnGlobalLayoutListener(list);
}
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
Log.e("Ionickeyboard"," plugin execute");
if ("hide".equals(action)) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
//http://stackoverflow.com/a/7696791/1091751
InputMethodManager inputManager = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
View v = cordova.getActivity().getCurrentFocus();
if (v == null) {
callbackContext.error("No current focus");
} else {
Log.e("Ionickeyboard"," plugin execute hide");
inputManager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
callbackContext.success(); // Thread-safe.
}
}
});
return true;
}
if ("show".equals(action)) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
Log.e("Ionickeyboard"," plugin execute show");
((InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
callbackContext.success(); // Thread-safe.
}
});
return true;
}
return false; // Returning false results in a "MethodNotFound" error.
}2.config.xml中配置
<feature name="SoftKeyBoard"> <param name="android-package" value="com.zte.umap.odpf.plugin.IonicKeyboard" /> </feature>
3.编写相应Js文件,封装接口
skbShow:function(success, fail){
cordova.exec(
success,
fail,
"SoftKeyBoard",
"show",
[]);
},
skbHide:function(success, fail){
cordova.exec(
success,
fail,
"SoftKeyBoard",
"hide",
[]);
}Odpf中合入了IonicKeyboard插件,具体的使用方式请参见Ionic的介绍
Native调用js
首先在js层定义一个方法
document.addEventListener('reDrawView', function (event) {
alert(event. param) ;
}, false);在native层,使用WebviewFragment实例的loadUrl方法
String param = “index”;
webviewFragment.loadUrl("javascript:reDrawView('"+ param +"');");3. ODPFragmentPool类
Odpf框架的主类,可以理解为window池,一个window 对应一个不同类型的页面视图。一个页面依附于fragment,fragment又嵌套在window内。该widow视图可以是纯web页面、可以是自定义的本地view、或者是本地View与web页面的混合,利用该类接口可以实现页面之间的切换和管理。
接口说明
public static ODPFragmentPool getInstance(Context context)
功能 | 获取ODPF池对象 | |
参数 | context | 上下文 |
返回值 | ODPFragmentPool的单例对象 | |
说明 | ||
public BaseFragment openInit(FrameLayout view, String url, int priority, int capacity, String FirstProgress)
功能 | 应用首页加载接口,为应用的入口部分 | |
参数 | view | 摆放页面的布局容器view,该布局必须是FrameLayout |
url | 载入的页面,一个页面对应一个window,url需要带参数时,格式为 xxx.html#a=b 以”#”分割,后面带参数。 url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上
| |
priority | 该页面的优先级,当池存储的容量大于限额时,需要删除存储的window,优先级低的删除的概率大,介于0至9之间 | |
capacity | 池容量 | |
FirstProgress | 一级视图页面加载时的loading 布局xml 文件名例如“progress_bar” | |
返回值 | 用于显示首页的fragment,每个fragment依附于一个window之上 | |
说明 | 本地调用 | |
public BaseFragment openInNewView(FrameLayout view ,int viewId ,String url, int priority, String secdProgress)
功能 | 应用加载二级视图时接口,为应用的入口部分 | |
参数 | view | 摆放二级页面的视图容器view,该布局必须是FrameLayout |
ViewId | 用于标示该view 的id ,必须大于0 | |
url | 载入的页面,一个页面对应一个window,url需要带参数时,格式为 xxx.html#a=b 以”#”分割,后面带参数。 url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上
| |
priority | 该页面的优先级,当池存储的容量大于限额时,需要删除存储的window,优先级低的删除的概率大,介于0至9之间 | |
secdProgress | 二级视图页面加载时的loading 布局xml 文件名例如“progress_bar2” | |
返回值 | 用于显示二级视图页面的fragment,每个fragment依附于一个window之上 | |
说明 | 本地调用 | |
public void setFirstPageCompleteInterface(FirstPageCompleteInterface firstPageComplete)
功能 | 应用首页为纯web页面时,页面数据加载完成的通知接口 | |
参数 | firstPageComplete | 实现了FirstPageCompleteInterface的类对象 |
返回值 | ||
说明 | 因为一个纯web页面加载是需要时间的,尤其复杂页面会出现长时间的白屏,这时可以给应用添加一个欢迎页面,当首页加载完成后再隐藏欢迎页,show出首页。 | |
public void open( String url ,int viewId, int anim_type, Boolean isOpenNewWindow, int priority )
功能 | 应用加载新页面 | |
参数 | url | 载入的页面,一个页面对应一个window,url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上。 url需要带参数时,格式为 xxx.html#a=b 以”#”分割,后面带参数。 |
viewId | 需要打开的新视图 需要在哪个view 容器上展示,最根级的目录 id 为0 ,其余viewid 与openInNewView中传递的id 一致 | |
anim_type | 页面载入时的动画切换类型, 1表示右入, 2表示左入, 3:新页面上入,旧页面下出, 4:新页面下入,旧页面上出, 5淡入淡出, 输入其他数值则没有动画; 当该页面回退时,动画与载入相反 | |
isOpenNewWindow | 这个值置为true表示 目标页采用新的window,则当前页面压栈,若false,则表示是在当前页面重新打开,当前页不压栈,举个例子说明如果:a 页面跳转至b页面,b再跳转至C页面, b至c页面跳转时设定isOpenNewWindow为true,则c后退后返回至b页面,否则返回至a页面; | |
priority | 该页面的优先级,当池存储的容量大于限额时,需要删除存储的window,优先级低的删除的概率大,介于0至9之间,值越大优先级越高。 | |
返回值 | 用于显示首页的fragment,每个fragment依附于一个window之上 | |
说明 | 该接口封装至js层,可以在web页面直接调用进行页面跳转 | |
public Boolean isIndex()
功能 | 当前页面是不是首页,已无可以回退的历史页面 |
返回值 | true表示 无可以回退的历史页 |
说明 | 主要用于物理键back的拦截事件 |
public Boolean goBack()
功能 | 回退 |
返回值 | true表示 可以回退 |
说明 | 主要用于物理键back的拦截事件,进行后退操作。该接口封装至js层,可以进行页面的后退 |
public void clearHistory()
功能 | 清空历史记录 |
返回值 | |
说明 | 清空之前的历史页面记录。该接口封装至js层,可以清空之前的历史记录 |
public void clearRecycle ()
功能 | 清空缓存栈 |
返回值 | |
说明 | 清空之前的缓存对象。该接口封装至js层,可以清空之前的缓存对象 |
public void setKeyValue(String key, String value)
功能 | 设置属性值,用于js层获取数据 |
返回值 | |
说明 | 在js层可以通过注入的对象获取到设置的属性值, js层调用方式为Odpf.getValue(String key) |
public BaseFragment getFragmentByUrlKey(String url)
功能 | 获取池里面的fragment对象 |
参数 | Open接口中传递的url,但是不带参数,截取的是“#”之前的字段 |
返回值 | 该url 对应的池中的fragment对象 |
说明 |
FirstPageCompleteInterface接口类
public interface FirstPageCompleteInterface{
public void firstPageProgressComplete ();
}4. BaseFragement类
自定义本地页面,当需要载入纯本地页面或者一个页面中即有部分本地控件,又有web页面时就用到了BaseFragment类。
自定义的native页面是以fragment形式加入到window池中的,所有自定义的视图需继承BaseFragment类。
该类中定义了几个方法可以用于子类使用。当载入一个本地自定义页面,也需要用ODPFragmentPool的open或者openInit方法传入一个url,这个url不是www文件夹中的页面而是在url.xml中配置的,他指向了实际加载的native类。url.xml中name可以只配置url域名,不用带上参数。
接口说明
public String getUrlName()
功能 | 获取open接口传递的url参数,主要是用于获取url中的各个参数 |
返回值 | |
说明 | 清空之前的历史页面记录。该接口封装至js层,可以清空之前的历史记录 |
public void ODPCreate()
功能 | 生命周期事件之create |
返回值 | |
说明 | 在这里实现自定义本地页面的create事件,具体时机见页面生命周期说明 |
public void ODPDestroy()
功能 | 生命周期事件之Destroy |
返回值 | |
说明 | 在这里实现自定义本地页面的Destroy事件 |
public void ODPResume()
功能 | 生命周期事件之Resume |
返回值 | |
说明 | 在这里实现自定义本地页面的Resume事件 |
public void ODPPause()
功能 | 生命周期事件之Pause |
返回值 | |
说明 | 在这里实现自定义本地页面的Pause事件 |
5. WebviewFragment类
该类的使用场景为自定义native页面中需要摆放web页面。WebviewFragment类继承于fragment,布局中放置了webview控件,用于加载web页面。
接口说明
public static WebviewFragment newInstance(String url)
功能 | 创建WebviewFragment类的实例,传递需要载入的url,当fragment视图创建后,自动载入该url |
参数 | 需要载入的url,url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上 |
返回值 | WebviewFragment实例 |
说明 | 当fragment视图创建后,自动载入该url |
public void loadUrl(String url)
功能 | 在webview控件中 加载url |
参数 | 需要载入的url,url指向的是本地位于assets/www文件夹下的web文件,当有相对路径时要写上,可以加载js代码, |
返回值 | |
说明 | 相当于webView的loadurl 方法 |
public void setPageFinishedInterface(PageFinishedInterface pageFinished )
功能 | 页面加载完成的通知接口 | |
参数 | PageFinishedInterface | 实现了PageFinishedInterface的类对象 |
返回值 | 使用场景:因为web页面加载需要时间的,尤其复杂页面会出现长时间的白屏,这时可以给应用添加一个loading视图,当首页加载完成后再隐藏show出web页面。 | |
public void setPageStartedInterface(PageStartedInterface pageStarted )
功能 | 页面开始加载的通知接口 | |
参数 | pageStarted | 实现了PageStartedInterface的类对象 |
返回值 | ||
Webview 对象
public CordovaWebView appView,可以用WebviewFragement示例获取
PageFinishedInterface接口类
public interface PageFinishedInterface{
public void onPageFinished (WebviewFragment fragment,String url);
}
参数说明:
fragment:当前WebviewFragment 的实例,
url:当前WebviewFragment加载的url
PageStartedInterface接口类
public interface PageStartedInterface{
public void onPageStarted (WebviewFragment fragment,String url);
}
参数说明:
fragment:当前WebviewFragment 的实例,
url:当前WebviewFragment加载的url
FragmentHiddenInterface接口类
public interface FragmentHiddenInterface{
public void onFragmentHidden ();
}
WebviewFragment,通过onHiddenChanged接口触发的实例隐藏时,调用该接口,并且调用页面的ODPFPause事件,通知页面回到后台,从而相应的做一些处理
FragmentShownInterface接口类
public interface FragmentShownInterface{
public void onFragmentShown ();
}
WebviewFragment,通过onHiddenChanged接口触发的实例显示时,调用该接口,并且调用页面的ODPFResume事件,通知页面回到前台,从而相应的做一些处理
6. 页面生命周期事件
类似于Android的Activity,window池中的每个window也有类似的生命周期事件。有四种ODPCreate、ODPDestroy、ODPResume、ODPPause。
ODPCreate事件:
n 纯web 页面
Js接口为ODPCreate , 参数为url 截取“#”之后的参数字符串
document.addEventListener(ODPCreate, function (event) {
//add function content
alert(event.url) ;
}, false);
n 自定义本地页面
接口为
public void ODPCreate(),而url可以通过getUrlName()方法获取,这里获取的是整url
当window为非首次创建,加载视图时,调用该事件。一般用于window 在池的堆栈中复用的情况。
如图:b页面被复用的条件:
首先b页面必须曾经加载过。
b页面在栈里还有记录,b页面window实例存储于实例池中;
b页面已经被回退过,栈里已经没有记录,或者没有被压栈,但是在window实例池中因为没有超过容量限制,b页面的window实例依然在window实例池中。
当再次打开b页面的时候,b页面对应的window实例展示到屏幕中,并调用ODPCreate事件。每次打开b页面时传递的url ,可携带不同的参数,这些值可以获取到,在ODPCreate方法里做一些页面数据的调整。
ODPDestroy事件
n 纯web页面
Js接口为ODPDestroy,无参数
document.addEventListener(ODPDestroy, function (event) {
//add function content
}, false);
n 自定义本地页面
接口为
public void ODPDestroy ()
当用户按后退,离开当前window, 或者当前window不压栈而打开新window,当前window调用该事件。
如图,a页面打开b页面后,再back回a页面时,b页面调用ODPDestroy方法,进行一些必要的清理
或者 c页面打开b页面,isOpenNewWindow为false,在当前页面重新打开,当前c页不压栈,则c页面调用ODPDestroy方法,进行一些必要的清理
ODPPause事件
n 纯web页面
Js接口为ODPPause,无参数
document.addEventListener(ODPPause, function (event) {
//add function content
}, false);
n 自定义本地页面
接口为
public void ODPPause ()
当用户打开新window,离开当前window,当前window压栈,回到后台时调用该事件。
如图:a页面点击链接跳转至b页面,a页面调用ODPPause事件处理例如暂停播放器等操作,回到后台
ODPResume事件
n 纯web页面
Js接口为ODPResume,无参数
document.addEventListener(ODPResume, function (event) {
//add function content
}, false);
n 自定义本地页面
接口为
public void ODPResume ()
当用户需要后退,回到前一个window,前一个window回到前台时调用该事件。
如图:a页面点击链接打开b页面,然后在b页面点击后退,回到a页面时,a页面调用ODPResume事件开启页面展示动画等操作,回到前台。
下载Demo