通过JavaScript实现在Android WebView中点击查看图片,长按识别二维码

序言

最近的项目中,客户需要在WebView中实现长按识别二维码的功能。但是原有的图片已经有点击查看图片功能。要不破坏原有的功能,还能添加长按事件。这是第一次遇到这种需求。最后我还是完成了这个功能。但是在完成的过程中也遇到一些坑。在此记录一下,先看一下我实现的效果。

1.原有的点击查看图片功能

1.gif

2.长按识别二维码

2.gif

3识别失败给出提示

3.gif

实现

1.长按事件的监测

背景知识

要实现对长按事件的检测主要通过HTML5 中的touchstart ,touchmove,touchend 事件。来实现。一开始触摸事件touchstart、touchmove和touchend是iOS版Safari浏览器为了向开发人员传达一些信息新添加的事件。因为ios设备既没有鼠标也没有键盘,所以在为移动Safari浏览器开发交互性网页的时候,PC端的鼠标和键盘事件是不够用的。

在iPhone 3Gs发布的时候,其自带的移动Safari浏览器就提供了一些与触摸(touch)操作相关的新事件。随后,Android上的浏览器也实现了相同的事件。触摸事件(touch)会在用户手指放在屏幕上面的时候、在屏幕上滑动的时候或者是从屏幕上移开的时候出发。下面具体说明:

touchstart事件:当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。
touchmove事件:当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用preventDefault()事件可以阻止滚动。
touchend事件:当手指从屏幕上离开的时候触发。
touchcancel事件:当系统停止跟踪触摸的时候触发。关于这个事件的确切出发时间,文档中并没有具体说明,咱们只能去猜测了。

具体的思路

1.在touchstart 事件中通过setTimeout 方法延迟500毫秒触发长按事件,并保存返回的事件id
2.在touchmove时将事件id重置为0,并取消长按事件。(防止在手机滑动图片的时候误触发事件)
3.在touchend的时候判断是事件id是否为0,(在长按事件触发时和手机滑动时 事件id会置为0),如果不为0.则表示长按事件还没有发生,也不是误差。表示500毫秒内手指抬起了,触发单击事件。

具体的代码如下

var objs = document.getElementsByTagName("img");
var timeOutEvent=0;
var imageUrl="";
for(var i=0; i<objs.length; i++) {
    var img=objs[i];
    mobile.addImageUrl(img.src);//收集网页中图片的url
     //清除之前设置的,防止重复设置
     img.removeEventListener('touchstart',touchstart)
     img.removeEventListener('touchmove',touchmove)
     img.removeEventListener('touchend',touchend)

     //注册事件
    img.addEventListener('touchstart',touchstart)
    img.addEventListener('touchmove',touchmove)
    img.addEventListener('touchend',touchend)
}
function touchstart(e){
      timeOutEvent = setTimeout("longPress()",500);
     imageUrl=this.src;
}

function touchmove(e){
    clearTimeout(timeOutEvent);
     timeOutEvent = 0;
}

function touchend(e){
   clearTimeout(timeOutEvent);
          if(timeOutEvent!=0){
               //展示图片
               mobile.showImage(imageUrl);
           }
            return false;
}

function longPress(){
    timeOutEvent = 0;
   if(typeof mobile!="undefined"){
        mobile.scanCode(imageUrl);
    }
}

二维码识别

思路如下

1.向webview注入js对象mobile。再通过js在长按时,向mobile的scanCode方法传入图片的url
2.拿到url以后使用Glide 下载相应的图片,获得bitmap
3.通过zxing实现解码,回调ScanListener的onScanSuccess()方法,返回结果。

问题总结

1.Glide的into方法必须在主线程中启动。而JavaScript调用本地代码的线程不是主线程。在其他线程调用into方法时会出现既加载不出图片,也不会报错的现象。

2.二维码扫描使用的是android-zxingLibrary库。比较方便
地址是android-zxingLibrary

3.关于调试。由于涉及到JavaScript和本地代码的联调。而最困难的是JavaScript的调试。建议大家在每次修改完JavaScript代码以后使用chrome的开发者模式,将JavaScript代码输入通过console输入进去检查有没有语法错误。


a.png

如果出现语法错误可以快速定位


b.png

当我们在Android中调试WebView中的JavaScript代码时,也可以通过chrome来远程调试。首先设置webview

webView.setWebContentsDebuggingEnabled(true);

然后再chrome浏览器的地址栏中输入

chrome://inspect/#devices

然后点击inspect

c.png

理论效果是这样的

d.png

但是实际上,部分手机加载不出来这个界面。我的手机就加载不出来。所以最好的方式还是使用

console.log("this is a log");

在Android Studio 的LogCat中过滤chromium 就可以看到log。可以在console.log中打印变量值。如果没有输出的话。回到第一步,将代码拷贝到chrom浏览器中检查是否有语法错误。

e.png

4.WebView不执行JavaScript代码。最开始我在onPageFinished中。通过下面的代码执行js。

webview.loadUrl("javascript:"+initJS);

但是效果是。js始终未执行。后来查阅资料发现这种方式执行js。有最大长度的限制。后面使用

  view.evaluateJavascript("javascript:" + initJs, null);

该方法是Android4.4以后添加,是异步执行。

f.png

具体的代码如下

package com.zgh.scandodedemo.util;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.uuzuche.lib_zxing.activity.CodeUtils.AnalyzeCallback;
import com.zgh.scandodedemo.activity.ImageBrowserActivity;
import com.zgh.scandodedemo.util.CodeUtils;

import java.util.ArrayList;


/**
 * Created by zhuguohui on 2018/10/9.
 */

public class Mobile {
    Context context;
    Handler handler = new Handler(Looper.getMainLooper());
    ArrayList<String> imageURLList = new ArrayList<>();

    public Mobile(Context context) {
        this.context = context;
    }

    @JavascriptInterface
    public void scanCode(final String imageUrl) {
        if (scanListener != null) {
            scanListener.onScanStart();
        }
        if (TextUtils.isEmpty(imageUrl)) {
            if (scanListener != null) {
                scanListener.onScanFailed("图片地址为空");
            }
        } else {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    getImage(imageUrl);
                }
            });


        }
    }

    @JavascriptInterface
    public void addImageUrl(String url) {
        imageURLList.add(url);
    }

    @JavascriptInterface
    public void showImage(String url) {
        Intent intent = new Intent(context, ImageBrowserActivity.class);
        intent.putStringArrayListExtra(ImageBrowserActivity.IMAGE_BROWSER_LIST, imageURLList);
        intent.putExtra(ImageBrowserActivity.IMAGE_BROWSER_INIT_SRC, url);
        context.startActivity(intent);
    }

    private void getImage(String imageUrl) {
        Glide.with(context.getApplicationContext()).load(imageUrl)
                .asBitmap()
                .into(new SimpleTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {

                        CodeUtils.analyzeBitmap(resource, new AnalyzeCallback() {
                            @Override
                            public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
                                if (scanListener != null) {
                                    scanListener.onScanSuccess(result);
                                }
                            }

                            @Override
                            public void onAnalyzeFailed() {
                                if (scanListener != null) {
                                    scanListener.onScanFailed("未识别到二维码");
                                }
                            }
                        });

                    }

                    @Override
                    public void onLoadFailed(Exception e, Drawable errorDrawable) {
                        super.onLoadFailed(e, errorDrawable);
                        if (scanListener != null) {
                            scanListener.onScanFailed("加载图片失败[" + e.getMessage() + "]");
                        }
                    }
                });
    }

    public interface ScanListener {
        void onScanStart();

        void onScanFailed(String info);

        void onScanSuccess(String result);
    }

    private ScanListener scanListener;

    public void setScanListener(ScanListener scanListener) {
        this.scanListener = scanListener;
    }
}

源码下载

ScanDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,464评论 1 11
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,960评论 3 119
  • mobileHack 这里收集了许多移动端上遇到的各种坑与相对解决方案 工具类网站 HTML5 与 CSS3 技术...
    安石0阅读 1,870评论 0 5
  • 苹果创始人史蒂夫·乔布斯在斯坦福的毕业典礼给现在23000名毕业生如下的建议: 你需要找到你所爱的东西……成就大事...
    周天将阅读 386评论 0 4
  • 最近读了3本日本日本作者写的关于阅读的书,总体而言,给这些书评价7分吧。这几本书都是根据自己的经验,列举了一个又一...
    萧水浮萍阅读 679评论 0 0