Vue.js實戰——H5拍照遷移至Android App_14

一、目標

    1、解決從純H5開發且帶拍照功能的App遷移至Android平臺時,保證拍照和裁剪功能正常;

    2、解析實現過程中碰到的各種問題;

二、實現效果

    爲了達成上述目標,先大體介紹下思路:

    1)Android平臺下支持h5的input拍照標籤,但是需要在Android側對拍照過程做一些特殊處理才能正常獲取到此拍照圖片;

    2)Android平臺存在版本碎片化問題,不同版本的拍照接口不同,需要做好兼容處理;

三、步驟

    1、拍照需要用到Activity對象這個上下文,所以在初始化WebView時,需要把Activity對象(this)引用,傳遞給WebView,繼而傳遞給WebView中的WebCromeClient。如下是WebView中的代碼(WebChromeClient中的代碼詳見github):

    /**
     * 綁定activity
     * 
     * @param activity
     */
    public void bind(Activity activity)
    {
        // this.activity = activity;
        this.webChromeClient.bind(activity);
    }

    2、Android中的拍照核心代碼:

    1)WebChromeClient通用拍照代碼:

    /**
     * 拍照
     */
    private void takePicture()
    {
        try
        {
            cameraFile = new File(path, SystemClock.currentThreadTimeMillis() + ".jpg");
            Log.i(TAG, "camera file path=" + cameraFile.toString());
            cameraUri = Uri.fromFile(cameraFile);
            Log.i(TAG, "camera uri=" + cameraUri.toString());
            PictureUtil.takePicture(this.activity, cameraUri, Constant.TAKE_PICTURE_CODE);
        }
        catch (Throwable t)
        {
            Log.e(TAG, "ERROR:", t);
        }
    }

    2)上述只是封裝了一個Uri,真正的拍照代碼在PictureUtil中:

    /**
     * 拍照
     *
     * @param activity 當前activity
     * @param imageUri 拍照後照片存儲路徑
     * @param requestCode 調用系統相機請求碼
     */
    public static void takePicture(Activity activity, Uri imageUri, int requestCode)
    {
        Log.i(TAG, "start to take a picture.");
        // 調用系統相機
        Intent cameraIntent = new Intent();
        cameraIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        activity.startActivityForResult(cameraIntent, requestCode);
        Log.i(TAG, "end to take a picture.");
    }

    這裏是調用了拍照的Activity,拍照完成後,需要在指定的Activity中,根據約定的requestCode來接收拍照完成後的結果。

    3)上述拍照傳入的是MainActivity,其處理結果應在onActivityResult中,代碼如下:

  @Override
    protected void onActivityResult(int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            // 拍照
            case Constant.TAKE_PICTURE_CODE: {
                Log.i(TAG, "camera result:" + resultCode);
                Log.i(TAG, "camera data:" + (data == null));
                // 取消拍照
                if (resultCode == Activity.RESULT_CANCELED) {
                    Log.i(TAG, "abort to take a picture.");
                } else {
                    Log.i(TAG, "successfully to take a picture.");
                }
                this.webView.cropPicture(resultCode);
                break;
            }
        }
    }

    4)上述Activity拍照成功的邏輯中,調用了圖片的裁剪功能。

   WebChromeClient中的裁剪代碼如下:

    /**
     * 開始裁剪照片
     *
     * @param activity
     * @param cameraUri
     */
    private void cropPicture(Activity activity, Uri cameraUri)
    {
        File cropFile = new File(path, SystemClock.currentThreadTimeMillis() + ".jpg");
        Log.i(TAG, "crop file path=" + cropFile.toString());
        
        cropUri = Uri.fromFile(cropFile);
        Log.i(TAG, "crop uri=" + cropUri.toString());
        PictureUtil.Crop crop = new PictureUtil.Crop(1, 1, 250, 250);
        PictureUtil.cropPicture(activity, cameraUri, cropUri, crop, Constant.CROP_PICTURE_CODE);
    }

    PictureUtil中真正的裁剪代碼爲:

    /**
     * 裁剪圖片
     * 
     * @param activity 當前activity
     * @param orgUri 剪裁原圖的Uri
     * @param desUri 剪裁後的圖片的Uri
     * @param crop:{aspectX X方向的比例;aspectY:Y方向的比例;width:剪裁圖片的寬度;height:剪裁圖片高度}
     * @param requestCode 剪裁圖片的請求碼
     */
    public static void cropPicture(Activity activity, Uri orgUri, Uri desUri, Crop crop, int requestCode)
    {
        Intent intent = new Intent("com.android.camera.action.CROP");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
        {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }
        intent.setDataAndType(orgUri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", crop.getAspectX());
        intent.putExtra("aspectY", crop.getAspectY());
        intent.putExtra("outputX", crop.getWidth());
        intent.putExtra("outputY", crop.getHeight());
        intent.putExtra("scale", true);
        // 將剪切的圖片保存到目標Uri中
        intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
        // 取消人臉識別
        intent.putExtra("noFaceDetection", true);
        // true:返回bitmap,false:返回bitmap
        intent.putExtra("return-data", true);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        activity.startActivityForResult(intent, requestCode);
    }

    裁剪的過程和拍照的過程類似,也會在指定的Activity中,根據約定的requestCode來接收裁剪完成後的結果。

    6)上述裁剪傳入的Activity是MainActivity,其處理結果應在onActivityResult中,代碼如下:

  @Override
    protected void onActivityResult(int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case Constant.CROP_PICTURE_CODE: {
                Log.i(TAG, "crop result:" + resultCode);
                Log.i(TAG, "crop data:" + data);
                Log.i(TAG, "crop data.getData:" + data.getData());
                Bitmap photo = data.getParcelableExtra("data");
                Log.i(TAG, "crop picture:" + photo);
                // String base64Pic = PictureUtil.getPictureBase64(photo);
                // Log.i(TAG, "base64Pic:" + base64Pic);

                this.webView.cropCallback(resultCode);
                break;
            }
        }
    }

    注意:此處的data中是可以拿到裁剪的Bitmap對象的,如果有這種訴求,可以在這裏加獲取的業務邏輯。

    7)裁剪完成後,需要把處理完成的照片結果,返回給拍照接口中指定的回調對象,然後由Android返回給調用方(即我們的H5拍照組件),WebChromeClient中回調的核心代碼如下所示,其中valueCallback爲回調結果的處理接口,由Android系統接管。

   
    /**
     * 拍照異步回調
     *
     * @param resultCode
     * @param uri
     */
    public void cropCallback(int resultCode, Uri uri)
    {
        
        if (resultCode == Activity.RESULT_CANCELED)
        {
            takePictureCancel();
        }
        else
        {
            if (null != oldValueCallback)
            {
                oldValueCallback.onReceiveValue(cropUri);
            }
            else if (null != valueCallback)
            {
                valueCallback.onReceiveValue(new Uri[] {cropUri});
            }
            
            oldValueCallback = null;
            valueCallback = null;
        }
        
        if (cameraFile != null)
        {
            Log.i(TAG, "delete cached camera file:" + cameraFile.getPath());
            cameraFile.delete();
        }
    }
    

    注意:上述代碼末尾有刪除拍照文件的操作,是因爲拍照和裁剪會分別產生圖片,而拍照的圖片大小又特別大(在我真機上測試佔有3M左右),所以裁剪完成後,需要把原圖從手機中刪掉。這個因具體業務而已。

    3、上面一步詳細講了Android系統中的拍照和裁剪流程,下面再來列下Android版本碎片化的拍照入口,同樣在WebChromeClient中:

  
    public void openFileChooser(ValueCallback<Uri> filePathCallback)
    {
        Log.d(TAG, "call openFileChooser01");
        oldValueCallback = filePathCallback;
        takePicture();
    }
    
    // For Android 3.0+
    public void openFileChooser(ValueCallback filePathCallback, String acceptType)
    {
        Log.d(TAG, "call openFileChooser02");
        oldValueCallback = filePathCallback;
        takePicture();
    }
    
    // For Android 4.1
    public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture)
    {
        Log.d(TAG, "call openFileChooser03");
        oldValueCallback = filePathCallback;
        takePicture();
    }
    
    // For Android 5.0+
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
        FileChooserParams fileChooserParams)
    {
        // super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        Log.d(TAG, "call openFileChooser04");
        valueCallback = filePathCallback;
        
        takePicture();
        return true;
    }

    經過上面3步,基本上就完成了拍照的全過程。再次重申,完整代碼見github。由於時間關係,本github代碼僅在本人真機和模擬器中驗證OK(Android 8.0+),低版本有待各位自己去驗證。

四、總結

    1、拍照是當今手機一個非常核心的功能,掌握好這個知識點非常有用,網上資料也相當多,而且還出現了幾種不同的處理方案(有人說涉及拍照的權限升級,只能在高版本拍照時使用FileProvider得到的Uri,有人說必須設置XX權限);實際驗證卻並不 完全一致。至於原因,是不是當時的版本或者Android API差異導致的,無從得知;

    2、在查找資料的過程中,發現有拍照和裁剪封裝成一個startActivityResult的,但由於時間問題,當時並沒有深入去研究,作爲一個遺留問題,留給愛思考愛動腦筋的你:)

五、參考資料

[1]Android7.0完美適配——FileProvider拍照裁剪全解析 - 擦肩的陽光的專欄 

[2]Android webView拍照與展示相冊圖片 - 追風箏的擺渡人 

[3]Android WebView實現選擇本地圖片拍照功能 - Ho博客

[4]深坑之Webview,解決H5調用android相機拍照和錄像 - villa_mou的博客

 

上一篇:Vue.js實戰——開發Android H5 App之Webview高級配置_13

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章