通過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庫。比較方便
地址是 ofollow,noindex">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; } }