分享

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

 橘子悦读 2013-12-09

前言

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



项目介绍

这是 一个百科类的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

package com.souapp.baike.yuer.js;

import android.app.Activity;
import android.os.Handler;
import android.util.Log;
import android.webkit.WebView;

import com.souapp.baike.yuer.db.DBFavoriteOpeator;
import com.souapp.baike.yuer.exception.DataIsExistException;
import com.souapp.baike.yuer.favorite.FavoriteBean;
import com.souapp.common.tools.DateTools;

/****
 * 处理JS主动调用自己的方法
 * @author Tester
 *
 */
public class ActionHelper {
  private String TAG="ActionHelper";
  private Activity cxt;
  private WebView webview;
  private String callbackFunction;//条码扫描结果,给JS的回调函数
  private ActionHelper(){}
  private static  ActionHelper instance;
  private Handler handler;
  private String result="添加收藏成功.";
  
  public static ActionHelper getInstance(){
    if (instance==null){
      instance=new ActionHelper();
    }
    return instance;
  }
  public void setValue(Activity context,WebView wv,Handler ha){
    cxt=context;
    webview=wv;
    handler=ha;
  }
  
  
  /***
   * 把扫码结果回调给JS
   * @param data
   */
  public void saveBarcodeScanData(String data){
    try{
      webview.loadUrl("javascript:"+callbackFunction+"('"+data+"')");
    }catch(Exception e){
      Log.e(TAG, "",e);
    }
  }
  /***
   * 设置页面标题
   * @param name
   */
  public void setBaikeTitle(final String name){
    try{

          handler.post(new Runnable() {  
                
              public void run() {  
                  //调用客户端setContactInfo方法  
              	webview.loadUrl("javascript:setBaikeTitle('"+name+"')");
              }  
          }); 
          
      
    }catch(Exception e){
      Log.e(TAG, "",e);
    }
  }
  
  /***
   * 搜索页面里调用查看本地详细文章
   * @param savepath
   */
  public void viewDetail(final String savepath){
    try{
      
      Log.d(TAG, "=========viewDetail=========");

    }catch(Exception e){
      Log.e(TAG, "",e);
    }finally{
        handler.post(new Runnable() {  
                
              public void run() {  
                  //调用JS,显示操作状态
              	webview.loadUrl("file:///android_asset/static_mobile_baike"+savepath);
              }  
          });
    }
  }
  
  
  /***
   * 把文章加入收藏夹里
   * @param name
   */
  public void addFavorite(final String title,final String category,final String tag,final String savepath){
    
    try{
      
      Log.d(TAG, "=========addFavorite=========");
      String savedate=DateTools.formatLong2Str(System.currentTimeMillis());
      FavoriteBean bean=new FavoriteBean();
      bean.setTitle(title);
      bean.setCategory(category);
      bean.setTag(tag);
      bean.setSavepath(savepath.substring(1));
      bean.setOrder(0);
      bean.setSavedate(savedate);
      
      DBFavoriteOpeator.saveFavorite(cxt,bean);
      
      result="成功添加到收藏夹";
    }catch(DataIsExistException e1){
      Log.e(TAG, "",e1);
      result="提示:"+e1.getMessage();
          
      
    }catch(Exception e){
      Log.e(TAG, "",e);
      result="错误:"+e.getMessage();
    }finally{
        handler.post(new Runnable() {  
                
              public void run() {  
                  //调用JS,显示操作状态
              	webview.loadUrl("javascript:callBack_echoMsg('"+result+"')");
              }  
          });
    }
  }
  
  /****
   * JS调用关闭Activity
   * @param mother
   */
  public  void exitActivity(){
    try{
      if (cxt==null) return;
      cxt.finish();
    }catch(Exception e){
      e.printStackTrace();
    }
  }

  /****
   * 调用条码扫描界面
   */
  public void startBarCodeScanUI(String url,String invokeFunction){
/*		try{
      Log.e(TAG, " start url:"+url);
      callbackFunction=invokeFunction;
      
      Intent it=new Intent();
      it.setClass(cxt, ZXingScannerActivity.class);
      it.putExtra("queryUrl", url);//把请求的URl传给扫码的页面
      cxt.startActivity(it);
    }catch(Exception e){
      e.printStackTrace();
    }*/
  }
}


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


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

我们看代码如下:

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


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

<script type="text/javascript">


  function setBaikeTitle(name){
    console.log("setTitle:"+name);
    //$("#baike_title").html(name);
    document.getElementById("baike_title").innerHTML=name;

  }
</script>


关闭Activity的JS代码
<script type="text/javascript">


  //给按钮,绑定一个事件监听器
  $(document).bind('pageinit', function() {


      $('#goback').bind('tap', function(e) {
          
        //调用手机关闭主页面

        if(window.es){
          window.es.exitActivity();
          console.log("关闭当前百科页面");
          return;
        }
        

      });
  });



</script>



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

    WebView wv = (WebView) this.findViewById(R.id.webview);
    WebSettings settings = wv.getSettings();
    settings.setJavaScriptEnabled(true);
    settings.setAllowFileAccess(true);
    wv.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);


    SOUAPP_URL="file:///android_asset/static_mobile_baike/";
    
    //先来判断是目录还是收藏的文章吧
    String baike_article=getIntent().getExtras().getString("baike_article");
    if(baike_article!=null){
      //直接打开页面
      SOUAPP_URL+=baike_article+"?pageid=1";
      Log.d(TAG, "页面:"+SOUAPP_URL);
    }else{
      //走目录模式
      String baike_type=this.getIntent().getExtras().getString("baike_type");
      SOUAPP_URL+=baike_type+".html";
      
      baike_name=this.getIntent().getExtras().getString("baike_name");
    }
    
    
    /****把JS调用自己的方法注册***/
    ah=ActionHelper.getInstance();
    ah.setValue(this,wv,new Handler());
    wv.addJavascriptInterface(ah, "es");
    wv.loadUrl(SOUAPP_URL);
    wv.setBackgroundColor(0); 
    wv.setBackgroundResource(R.drawable.loadpage);
这里呢,我省略了一些代码,例如:网页加载进度的显示的;

看一下html5页面的代码:

<!-- Home -->
<div data-role="page" id="main_page" data-dom-cache="true">




    <div data-theme="a" data-role="header"   data-position="fixed" >
      <a  id="goback" data-rel="back"   data-role="button"  data-icon="back">返回主页面</a>
        <h3>
            <div id="baike_title">怀孕百科</div>
        </h3>
    </div>
id为goback就是返回按钮,id为baike_title,其innerHTML内容会被替换。


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


收藏夹浏览

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


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

file:///android_asset/static_mobile_baike/

目录下的文章页面。

百科搜索

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

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


最后说说jquery mobile的坑吧


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

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

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

<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下调试不是很方便。


最后大家若有交流,可以加我的新浪微博: http://weibo.com/changeself

立波育儿百科 下载地址: http://


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多