Android原始应用与web app集成:内嵌jquery mobile为html5框架

前言

   很长时间一直思考什么时候需要原生应用于html5开发的web app结合去开发一个App;终于做了一个《立波育儿百科》的应用,才尝试了这种方式做一个App;

本文作者:心有灵犀鬼才心,blog地址:http://blog.csdn.net/changemyself   ,微博地址:http://weibo.com/changeself

如要转载本文,请加上本文地址和作者名称,不得删除原文链接;


项目介绍

这是 一个百科类的App,主要包含了育儿、养生、怀孕、饮食、母婴用品等方面的资料,这些资料有个特点,都是html格式,采用CSS将其分段存储在数据库里;

考虑到要做一个Android版本和Ios版本,所以尽量让网页内容得以在2个系统上共享,维护一份网页内容,使其在2个手机平台上使用;

在App的操作使用方面,主界面就是百科分类,然后用户进入分类就显示该分类下的标签项,用户通过标签项来进一步找到自己要看的百科文章;当然还有“文章收藏”和“文章搜索”的功能。

如图所示



在文章页面展示上,采用了jquery mobile的UI风格:页面标题栏+内容栏,用户点击页面,会使页面标题栏会自动隐藏/显示,如果页面展示超过一屏,我在页面最右下角加了一个“回到顶部”的快捷方式;当然页面标题栏还有“返回”和“收藏”的2个按钮,分别帮助用户能够返回上一级的页面,收藏当前喜欢关注的文章。

如图所示



原生和html5的交互界面介绍

这个是本文的重点,

准备工作:首先我是用python和django template技术奖数据库的内容按照jquery ui做成的模板批量生成了静态html页面,大概20多M吧,我都直接扔到/asset目录下,好让webview加载使用。


代码设计:加载html5网页的webview会单独嵌入在一个Activity,我们称之为WebPageActivity;

我们还要增加一个类ActionHelper专门负责原生程序和JS直接的通信,包含以下功能:添加收藏,设置页面标题,关闭当前Activity

[java]  view plain copy print ?
  1. package com.souapp.baike.yuer.js;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Handler;  
  5. import android.util.Log;  
  6. import android.webkit.WebView;  
  7.   
  8. import com.souapp.baike.yuer.db.DBFavoriteOpeator;  
  9. import com.souapp.baike.yuer.exception.DataIsExistException;  
  10. import com.souapp.baike.yuer.favorite.FavoriteBean;  
  11. import com.souapp.common.tools.DateTools;  
  12.   
  13. /**** 
  14.  * 处理JS主动调用自己的方法 
  15.  * @author Tester 
  16.  * 
  17.  */  
  18. public class ActionHelper {  
  19.     private String TAG="ActionHelper";  
  20.     private Activity cxt;  
  21.     private WebView webview;  
  22.     private String callbackFunction;//条码扫描结果,给JS的回调函数  
  23.     private ActionHelper(){}  
  24.     private static  ActionHelper instance;  
  25.     private Handler handler;  
  26.     private String result="添加收藏成功.";  
  27.       
  28.     public static ActionHelper getInstance(){  
  29.         if (instance==null){  
  30.             instance=new ActionHelper();  
  31.         }  
  32.         return instance;  
  33.     }  
  34.     public void setValue(Activity context,WebView wv,Handler ha){  
  35.         cxt=context;  
  36.         webview=wv;  
  37.         handler=ha;  
  38.     }  
  39.       
  40.       
  41.     /*** 
  42.      * 把扫码结果回调给JS 
  43.      * @param data 
  44.      */  
  45.     public void saveBarcodeScanData(String data){  
  46.         try{  
  47.             webview.loadUrl("javascript:"+callbackFunction+"('"+data+"')");  
  48.         }catch(Exception e){  
  49.             Log.e(TAG, "",e);  
  50.         }  
  51.     }  
  52.     /*** 
  53.      * 设置页面标题 
  54.      * @param name 
  55.      */  
  56.     public void setBaikeTitle(final String name){  
  57.         try{  
  58.   
  59.             handler.post(new Runnable() {    
  60.                     
  61.                 public void run() {    
  62.                     //调用客户端setContactInfo方法    
  63.                     webview.loadUrl("javascript:setBaikeTitle('"+name+"')");  
  64.                 }    
  65.             });   
  66.               
  67.               
  68.         }catch(Exception e){  
  69.             Log.e(TAG, "",e);  
  70.         }  
  71.     }  
  72.       
  73.     /*** 
  74.      * 搜索页面里调用查看本地详细文章 
  75.      * @param savepath 
  76.      */  
  77.     public void viewDetail(final String savepath){  
  78.         try{  
  79.               
  80.             Log.d(TAG, "=========viewDetail=========");  
  81.   
  82.         }catch(Exception e){  
  83.             Log.e(TAG, "",e);  
  84.         }finally{  
  85.                 handler.post(new Runnable() {    
  86.                     
  87.                 public void run() {    
  88.                     //调用JS,显示操作状态  
  89.                     webview.loadUrl("file:///android_asset/static_mobile_baike"+savepath);  
  90.                 }    
  91.             });  
  92.         }  
  93.     }  
  94.       
  95.       
  96.     /*** 
  97.      * 把文章加入收藏夹里 
  98.      * @param name 
  99.      */  
  100.     public void addFavorite(final String title,final String category,final String tag,final String savepath){  
  101.           
  102.         try{  
  103.               
  104.             Log.d(TAG, "=========addFavorite=========");  
  105.             String savedate=DateTools.formatLong2Str(System.currentTimeMillis());  
  106.             FavoriteBean bean=new FavoriteBean();  
  107.             bean.setTitle(title);  
  108.             bean.setCategory(category);  
  109.             bean.setTag(tag);  
  110.             bean.setSavepath(savepath.substring(1));  
  111.             bean.setOrder(0);  
  112.             bean.setSavedate(savedate);  
  113.               
  114.             DBFavoriteOpeator.saveFavorite(cxt,bean);  
  115.               
  116.             result="成功添加到收藏夹";  
  117.         }catch(DataIsExistException e1){  
  118.             Log.e(TAG, "",e1);  
  119.             result="提示:"+e1.getMessage();  
  120.               
  121.               
  122.         }catch(Exception e){  
  123.             Log.e(TAG, "",e);  
  124.             result="错误:"+e.getMessage();  
  125.         }finally{  
  126.                 handler.post(new Runnable() {    
  127.                     
  128.                 public void run() {    
  129.                     //调用JS,显示操作状态  
  130.                     webview.loadUrl("javascript:callBack_echoMsg('"+result+"')");  
  131.                 }    
  132.             });  
  133.         }  
  134.     }  
  135.       
  136.     /**** 
  137.      * JS调用关闭Activity 
  138.      * @param mother 
  139.      */  
  140.     public  void exitActivity(){  
  141.         try{  
  142.             if (cxt==nullreturn;  
  143.             cxt.finish();  
  144.         }catch(Exception e){  
  145.             e.printStackTrace();  
  146.         }  
  147.     }  
  148.   
  149.     /**** 
  150.      * 调用条码扫描界面 
  151.      */  
  152.     public void startBarCodeScanUI(String url,String invokeFunction){  
  153. /*      try{ 
  154.             Log.e(TAG, " start url:"+url); 
  155.             callbackFunction=invokeFunction; 
  156.              
  157.             Intent it=new Intent(); 
  158.             it.setClass(cxt, ZXingScannerActivity.class); 
  159.             it.putExtra("queryUrl", url);//把请求的URl传给扫码的页面 
  160.             cxt.startActivity(it); 
  161.         }catch(Exception e){ 
  162.             e.printStackTrace(); 
  163.         }*/  
  164.     }  
  165. }  



目录浏览:当用户点击主页面的某个分类(例如:育儿百科),我会通过webview去加载一个分类展示页面,然后用户在分类展示页面再点击操作,那就是通过html相对链接的方式在跳转了;在分类展示页面会有一个“返回主页面”的按钮,这个按钮是通过JS调用原生程序来控制关闭webview的Activity


这个“返回主页面”是JS调用原生代码,那么还有一个操作是原生调用JS代码的地方:页面的标题,是根据主界面的分类传到WebPageActivtiy里然后显示在html5的页面上的。

我们看代码如下:

原生代码看上面ActionHelper.java里的setBaikeTitle方法,然后我们去看JS的代码如下:


设置百科文章标题的JS代码:

[javascript]  view plain copy print ?
  1. <script type="text/javascript">  
  2.   
  3.   
  4.   function setBaikeTitle(name){  
  5.     console.log("setTitle:"+name);  
  6.     //$("#baike_title").html(name);  
  7.     document.getElementById("baike_title").innerHTML=name;  
  8.   
  9.   }  
  10. </script>  


关闭Activity的JS代码
[javascript]  view plain copy print ?
  1. <script type="text/javascript">  
  2.   
  3.   
  4.   //给按钮,绑定一个事件监听器  
  5.   $(document).bind('pageinit'function() {  
  6.   
  7.   
  8.       $('#goback').bind('tap'function(e) {  
  9.             
  10.         //调用手机关闭主页面  
  11.   
  12.         if(window.es){  
  13.           window.es.exitActivity();  
  14.           console.log("关闭当前百科页面");  
  15.           return;  
  16.         }  
  17.           
  18.   
  19.       });  
  20.   });  
  21.   
  22.   
  23.   
  24. </script>   



这里的es是ActionHelper暴露给webview可以调用的对象看我在WebPageActivity里webview的定义代码:

[java]  view plain copy print ?
  1.   
[java]  view plain copy print ?
  1. <span style="white-space:pre">      </span>WebView wv = (WebView) this.findViewById(R.id.webview);  
[java]  view plain copy print ?
  1. <span style="white-space:pre">      </span>WebSettings settings = wv.getSettings();  
  2.         settings.setJavaScriptEnabled(true);  
  3.         settings.setAllowFileAccess(true);  
  4.         wv.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);  
  5.   
  6.   
  7.         SOUAPP_URL="file:///android_asset/static_mobile_baike/";  
  8.           
  9.         //先来判断是目录还是收藏的文章吧  
  10.         String baike_article=getIntent().getExtras().getString("baike_article");  
  11.         if(baike_article!=null){  
  12.             //直接打开页面  
  13.             SOUAPP_URL+=baike_article+"?pageid=1";  
  14.             Log.d(TAG, "页面:"+SOUAPP_URL);  
  15.         }else{  
  16.             //走目录模式  
  17.             String baike_type=this.getIntent().getExtras().getString("baike_type");  
  18.             SOUAPP_URL+=baike_type+".html";  
  19.               
  20.             baike_name=this.getIntent().getExtras().getString("baike_name");  
  21.         }  
  22.           
  23.           
  24.         /****把JS调用自己的方法注册***/  
  25.         ah=ActionHelper.getInstance();  
  26.         ah.setValue(this,wv,new Handler());  
  27.         wv.addJavascriptInterface(ah, "es");  
  28.         wv.loadUrl(S<span style="font-family: Arial, Helvetica, sans-serif;">OUAPP_URL</span>);  
  29.         wv.setBackgroundColor(0);   
  30.         wv.setBackgroundResource(R.drawable.loadpage);  
这里呢,我省略了一些代码,例如:网页加载进度的显示的;

看一下html5页面的代码:

[html]  view plain copy print ?
  1. <!-- Home -->  
  2. <div data-role="page" id="main_page" data-dom-cache="true">  
  3.   
  4.   
  5.   
  6.   
  7.     <div data-theme="a" data-role="header"   data-position="fixed" >  
  8.       <a  id="goback" data-rel="back"   data-role="button"  data-icon="back">返回主页面</a>  
  9.         <h3>  
  10.             <div id="baike_title">怀孕百科</div>  
  11.         </h3>  
  12.     </div>  
id为goback就是返回按钮,id为baike_title,其innerHTML内容会被替换。


这里不讲android webview如何与js进行通信,原理很简单大家如果不懂的话可以去查文章后再看本文。


收藏夹浏览

这里面显示的都是用户收藏的百科文章,我使用sqllite数据库存在手机里;


界面上“删除”操作是原生代码操作,点击每行就是查看文章页面操作,这个其实还是调用WebPageActivity去load在

[java]  view plain copy print ?
  1. file:///android_asset/static_mobile_baike/    

目录下的文章页面。

百科搜索

在20多M的数据里进行全文搜索,只能放在服务器上了;如何中文分词、生成索引这些东西在这里不讲,原理就是用户输入关键字,会通过服务器返回一个html5页面;

这时候用户可以查看详细页面,直接调用手机里的静态页面展现。


最后说说jquery mobile的坑吧


说是坑也不过分,Android4以下的手机使用,感觉速度慢,Android4以上的手机速度一般,iphone手机用起来还可以接受,体验还行;

对于 data-ajax="false"  这种使用呢可以解决闪屏的问题,但是无法记录用户上次打开的页面状态,我是被闪屏搞得太无语就使用了就把ajax给禁止了,有人会问啥叫闪屏?

就是你点一个按钮或者链接,它闪页面1~2次,才开始切换。

[javascript]  view plain copy print ?
  1. <a  data-ajax="false" href="./baike_type_4__category_4.html" data-cache="true" data-role="button"  data-theme="b">孕前</a>  
使用jquery moblie先把html页面再浏览器下调试没错误了,再嵌入webview 里,以免被折腾死,webview下调试不是很方便。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值