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事件开启页面展示动画等操作,回到前台。