在Android Webview的assets目录下开发Hybrid App的一些坑…

crying_android

好久没写blog了,忙起来真是没法.. 在上一篇文章《基于Android Webview的Hybrid App开发的前端优化》的最后一条“以上都不是”里提到了“其实Hybrid App的最佳实践,还是应该把所有的html css js和主要的图片资源离线存储在Android的asset文件夹下,然后由Android实现从服务器端到手机的这个www主文件夹的更新机制,这样才不用凡事从server端下载..”没想到这么快就应用到新的项目中了。

当然,还只是实现了将webapp放在Android app的assets下,通过json api与server交互这步,理想中的通过app文件操作的更新机制还没有做,但是已经是梦想照进现实的节奏。感谢Android开发的同事包容了我这边不厌其烦反反复复的修改调试,才让这个相对Cutting Edge的方法得以应用到产品中。原本使用Hybrid App的初衷是为了分担Android开发同事的一些工作,但由于实践经验不足,而且Android webview确实还存在一些bug和兼容问题,最后评估下来反而多耗了不少时间精力。因此我们对于Hybrid App方式的使用暂时告一段落,web侧工作重心回归数据和后台开发,但是这轮开发的经验和教训还是很宝贵的,也给了我们更多信心,相信再过一小段时间,HTML5在移动设备的大规模应用就会到来。

好的,梦醒了还是总结下在Android Webview的assets目录下开发Hybrid App的一些坑,和各种跳过或者踩过坑的方案吧。

【坑 #1】 部分Android 4.0设备(HTC、海尔等)LoadUrl不能识别?参数或#hashtag

项目开始的时候,web部分是单独开发的,为了以后加入更多模块的扩展考虑,这个项目采用了AngularJs MVVM前端框架和ui-router做页面路由,其实就是单页应用(SPA)。当然,web程序无论采用什么技术,传参数只有两种方式

(1) ?参数形式,如top.html?id=1

(2) #hashtag形式,如index.html#topic/1

如果把程序部署在server端,或者Android的assets目录下,使用Webview访问的方式为

mWebView =(WebView)findViewById(R.id.mb_webview);
// String url_server = "http://www.awebird.com/demo_project/index.html#demo/123";
String url_assets = "file:///android_asset/www/demo_project/index.html#demo/123";
mWebView.loadUrl(url_assets);

我们之前的项目已经大量使用了第一种url_server的方式进行开发,webview loadUrl远程链接也从没有出过问题,所以就顺理成章的开发着,但是等web端做的差不多了,部署到Android工程的assets目录下测试时,遇到一个非常棘手的问题,有一台HTC Android 4.0.3的测试机总是无法打开网页(Page not found)

1

由于Android开发侧的同事不懂Web,我这边对Android的了解也是汗毛级别,所以遇到这种问题的纠结与困惑就不赘述,直接给出“坑#1_desc”

Android的issue 17535 (Issue 17535: WebView – URL mechanism is broken – passing parameters does not work) https://code.google.com/p/android/issues/detail?id=17535

打开这个页面,一阵惊叹,反正我是第一次在google groups或者stackoverflow上看到这么多老外对一个bug如此大面积的愤怒情绪爆发,这个密集程度快赶上国内门户网站中国足球新闻下的评论了Orz..

具体bug不细描述,其实就是Android 3.0以后的webview在访问assets目录下的本地html资源时,带?参数和#hashtag的URL机制不能被识别,上面链接(#148楼)里2012年6月29号有google的开发人员回帖说这个问题已经在Jelly Bean被修复,当然这个只是Android代码的修复,所以现在市面上还是有很多手机的Android版本依然存在这个问题,就像我们不能强迫用户都改用Chrome,不能强迫国行Android用户root手机安装GMS服务包一样,我们也没办法让“找不到网页”的用户换手机或者升级系统,所以问题还是要被解决,下面提供三种方案,我们使用了最后一种

(1)从Android侧扩展webview类进行处理

相关的方案和jar包已经在下面的链接列出 Step1~6即可解决。由于要对项目java部分进行改动,所以我们这边app侧没有采纳次方案,但应该是可行的。多提一句,我这边测试过,Phonegap的开源版本Apache Cordova就已经修复了这个问题

http://bricolsoftconsulting.com/fixing-the-broken-honeycomb-and-ics-webview/

https://github.com/bricolsoftconsulting/WebViewIssue17535Fix

(2)loadUrl(‘index.html’)后,再loadUrl一句js跳转语句的方法

这个方法,是得到V2EX的Archangel_SDY童鞋提醒,就是先只载入不带参数的页面(可以显示空白页或者loading效果),在页面载入后(onPageFinished),在loadUrl一句javascript用于跳转页面,下面的locationTo是在index.html写好的js函数,具体内容可以自己控制,只要接收到参数就ok

Android侧 (onKeyDown和java interface是用于处理返回事件问题的)

mWebView = (WebView)findViewById(R.id.mb_webview);
String base_url = file:///android_asset/www/demo_project/index.html;
mWebView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        mWebView.loadUrl("javascript:locationTo(#demo/"+"123"+"')");
        super.onPageFinished(view, url);
    }
}
mWebView.loadUrl(base_url);

@Override
public boolean onKeyDown(int keyCode, KeyEvent event){
        if(keyCode == KeyEvent.KEYCODE_BACK) {
            //点击返回键时
            if(mWebView.canGoBack()){
                   mWebView.goBack();// 返回前一个页面
            }else{
                    finish();
            }
            return true;
        }
    return super.onKeyDown(keyCode, event);
}

//Javascript Interface
public class demo_java_interface{
        /**
         * 关闭当前页面
         */
        public void closeCurrectActivity(){
            finish();
        }
}

Web侧index.html中js (sessionStorge和javascript Interface调用代码是处理手机返回键事件用的)

<script>
function locationTo(data_path){
  if(!sessionStorage.getItem("once")){
    sessionStorage.setItem("once", "1");
      location.href=data_path;
    }else{
      //call demo_java_interface.goback()..
      if(window.demo_java_interface){
        window.demo_java_interface.closeCurrectActivity();
    }else{
        console.log('call java to closeCurrentActivity');
    }
  }
}
</script>

当然,由于我手上没有这个问题的测试机(Android开发在不同城市Orzzzz),其实也不能确认这个方法是否真正有效,因为插入了一个中间页面,所以带来了返回和跳转问题,我们很快放弃了这个方案,继续寻找其它方法

(3)重写onReceivedError()方法 #最终采纳并测试有效版本#

参见

http://stackoverflow.com/questions/6542702/basic-internal-links-dont-work-in-honeycomb-app/7297536#7297536

代码(只支持#hashtag)

mWebView = (WebView)findViewById(R.id.mb_webview);

String url = file:///android_asset/www/demo_project/index.html#demo/123;

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
{
    if (failingUrl.contains("#")) {
      String[] temp;
      temp = failingUrl.split("#");
      mWebView.loadUrl(temp[0]);
       try {
        Thread.sleep(400);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
       mWebView.loadUrl(failingUrl);
     }
}

mWebView.loadUrl(url);

【坑 #2】所有Android Webview不能捕获#hashtag变化

Hybrid App中Android侧经常需要捕获webview中的url变化来做一些响应(比如改变header title的文字,处理终端的返回键等等)

但是无论是Server端的Url还是assets中的Url,webview都无法捕获到#hashtag的变化,也就是说index.html#step1 –> index.html#step2 –> index.html#step3 … 这些页面跳转是不能被Android监控到的。

这里提供两个解决方案

(1)在webapp里面使用javascript window.onhashchange里面调用Android的Javascript Interface方法,通知Android hash变化

http://stackoverflow.com/questions/15176519/android-webview-is-it-possible-to-detect-url-hash-change

(2)自己在页面跳转逻辑里,根据需要调用Android的Javascript Interface方法实现需要的功能

比如我这边用Angularjs,就在每个url对应的controller里面,调用Android侧的Javascript Interface方法,后来极端一点,由于不同页面功能点差别较大,直接废弃了web侧的ui-router功能,所有的跳转都交给java处理,也就是每个web页面(包括SPA中的一个状态)都交给Android的一个单独Activity,也就是做Activity的条状,Web侧的router白白浪费了T-T

其实上面的(1)和(2)是同一种方法,Android的Java和Webview里的web交互只有两种办法,一个是url捕获,一个是Javascript Interface,这里在#hashtag不能被url捕获到的情况下,就只能使用Javascript Interface了。

这样处理的一个结果就是Hybrid app里的web部分加入的java接口调用,如果脱离了App环境,要单独处理,或者通过检测Javascript Interface对象是否存在,来决定是采用web方法还是app方法跳转

$rootScope.Java_go2step2 = function){
     if(window.demo_java_interface_obj){
        //如果java interface对象存在,调用Java方法跳转Activity
        window.demo_java_interface_obj.go2step2);
    }else{
        //否则,使用web跳转
        $state.go('step2');
     }
 }

总之,不完美…

 

【坑 #3】Android 2.X Webview不能Scroll滑动

发现有HTML5的页面在Android 4.x下一切正常,在Android 2.X下不能滑动(Scroll)的问题

这个问题无疑是Android的 bug

目前有两个解决方案

(1)Html中去掉meta viewport tag (未经验证)

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" />

http://stackoverflow.com/questions/10552702/cant-scroll-webview-in-android

由于viewport对于移动端的页面适配很重要,前端的同学说不能去掉,试过去掉后样式乱掉了,所以没有采用此方案

(2)Android中的layout布局,将webview布局在ScrollView中

<ScrollView
            android:id="@+id/scrollView1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/tab"
            android:layout_alignParentTop="true"
            android:background="#00000000"
            android:scrollbars="none" >
            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:background="#00000000"
                android:orientation="vertical" >
                <WebView
                    android:id="@+id/mb_webview"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content" />
            </LinearLayout>
</ScrollView>

这是Android侧的方案,上面的布局xml只是一个简单demo,web童鞋遇到了可以跟app开发的童鞋沟通解决。我们采用的是此方案

【—–Scroll问题后续——】

后续开发过程中,Android的同事说不能使用上面的ScrollView布局,因为了整体的布局架构有冲突,所以这个问题再次无解,下面是后ScrollView时代的血泪史

(1)采用iScroll

angulrjs可以使用ng-iscroll

iscroll可以让webapp乍一看很有原生的感觉,但是我个人认为在webview存在不少问题,而且体验也不够顺畅,除非下拉刷新获取更多数据这种场景,应该尽量不用

使用iscroll后测试确实可以在Android 2.3的手机上Scroll,但是有个问题,是因为我们的页面是先载入,在动态$http.post获取数据,更新页面内容的,所以Scroll的范围特别是高度总是会出现可以滑动到最底部,但是又弹回上面的问题..

后来分析是isroll的区域判断了初始页面的位置,所以在$http.post的数据获取后,$timeout延时refresh myscroll

上面的方案貌似解决了,但是在webview下又出现了页面闪烁的现象,而且scroll的体验很不好

(2)最后痛定思痛,还是删除了所有的iscroll部分,重新寻找原因,重要发现元凶

overflow:hidden

是他,是他,就是他!

[原因] Android 2.X不支持这句css

[解决方案] 去掉,加上 height:auto

[参考] Scrollable DIV on mobile phones

【还在坑里没爬出来的总结…】

通过这个Hybrid App项目的实践,我们遇到了不少麻烦,HTML5在移动端的应用已经基本成熟,但是还有一些问题,让我们再耐心一点,做好准备,等Android特别是webview再成熟一点,等手机终端再快一点,等大家发了压岁钱、年终奖都换了新手机… 很多问题就自愈了(同时一大波新问题随之到来)LOL

本文地址:http://awebird.com/blog/art/190/

在Android Webview的assets目录下开发Hybrid App的一些坑…》上有3条评论

  1. Jess

    Hi,最近我也在尝试android的hybrid,也是把html、JS、CSS都 放在APP的 assets目录下,这种方式,JS里不能直接ajax请求服务器端数据,请问你们有遇到么,还是说获取数据只能交给 java 中实现,然后调用JS接口传给HTML页面呢?

    谢谢

    回复
  2. Pingback引用通告: html5 app 简单实例 - 移动开发 - 阿里欧歌

发表评论

电子邮件地址不会被公开。 必填项已用*标注