Cordova 自定义插件(Android)

2 篇文章 0 订阅
2 篇文章 0 订阅

最近在学习cordova,cordova本身提供一些插件,但是有时并不能满足工作的需要,有些插件需要自己来写。最近我需要一个从本地图库选择带二维码的图片并识别的插件,在网上找了好多例子,不是版本太旧就是顺序写的不清楚,我摸索着鼓捣了两天终于弄好了,现在来给大家分享一下,希望可以帮到你们!如果需要实现别的功能,思路和步骤是一样的,大家可以参考一下。
接下来我们一步一步的开始啦!(在这里默认大家已经安装好了node和cordova,注意node必须为最新版本)

cordova应用

创建一个cordova应用

cordova create hello com.test.hello HelloWorld

添加Android平台

cordova platform add android

这里写图片描述

在Android Studio中引入项目

在Android Studio中选择的路径为hello/platforms/android

这里写图片描述

ok,我们的项目已经创建完成,接下来我们来创建插件

自定义插件

pluman的安装

npm install -g plugman

创建插件

plugman create --name CoolPlugin --plugin_id coolPlugin --plugin_version 0.0.1 

name: 插件名字,plugin_id: 插件的id,plugin_version: 插件的版本
如果需要对插件添加一下更详细的信息,大家可以百度下,网上都有。

创建好的插件目录为:src,www(CoolPlugin.js),plugin.xml
我们再来在插件目录下添加Android平台和package.json文件(如果不添加package.json文件,项目在引用插件时会报错)

plugman platform add --platform_name android
sudo plugman createpackagejson .

这里写图片描述

这时在插件目录下会多一个package.json文件,在src文件夹下会多出一个android文件夹,android文件夹下有一个CoolPlugin.java

这里写图片描述

ok,项目也有了,插件也准备好了,接下来我们把插件添加到我们的项目里

添加插件

我们在项目路径下来添加插件,注意添加的插件路径,

 cordova plugin add ~/wanguying/CoolPlugin

添加完成后,我们看Android Studio,在src目录下显示我们刚刚添加的插件

这里写图片描述

(小乌龙:大家src目录下的是com.test.hello我的是com.example.hello,大家不用太在意,因为我误删了之前的项目,又新建了一个忘了改包名了,这个不影响!不用管就行!哈哈哈)

继续,首先我们来看platform_www目录下CoolPlugin.js文件

cordova.define("coolPlugin.CoolPlugin", function(require, exports, module) {
var exec = require('cordova/exec');

exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "CoolPlugin", "coolMethod", [arg0]);
};

});

,我们再打开src目录下CoolPlugin.java文件,这个文件可能会报错,应该是包名的问题,这个不用管,我们根据这两个文件来学习下

package coolPlugin;

import android.widget.Toast;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * This class echoes a string called from JavaScript.
 */
public class CoolPlugin extends CordovaPlugin {

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (action.equals("coolMethod")) {
            String message = args.getString(0);
            Toast.makeText(cordova.getActivity(), message, Toast.LENGTH_SHORT).show();
            this.coolMethod(message, callbackContext);
            return true;
        }
        return false;
    }

    private void coolMethod(String message, CallbackContext callbackContext) {
        if (message != null && message.length() > 0) {
            callbackContext.success(message);
        } else {
            callbackContext.error("Expected one non-empty string argument.");
        }
    }
}

js文件的第一行是插件的id,剩下的是他固定的写法,其中exec方法中”CoolPlugin”, “coolMethod”这个参数,就是java文件的类名和方法名,在java文件中args就是html界面传过来的参数,我们在用toast方法来显示出来。
现在我们就来写html界面,继续Android Studio,打开assets/www目录下的index.html,我们来写一个按钮,当我们点击按钮的时候,我们来调用Android代码。在index.html这个界面我简化了一下,代码如下

<!DOCTYPE html>
<html>
<head>
    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <link rel="stylesheet" type="text/css" href="css/index.css">
    <title>Hello World</title>
</head>
<body>
<button onclick="btnClick()">btnClick</button>
<script type="text/javascript" src="js/jquery-3.2.1.js"></script>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
</script>
</body>
</html>

在上述代码中,我添加了一个jquery文件,如果不加,点击按钮可能会没反应,jQuery下载地址https://code.jquery.com/jquery-3.2.1.js
下载完成后直接添加到和index.js同目录下就ok了。
在index.js中,我们来写一下,按钮的点击事件

  function btnClick(){
//   cordova.plugins.CoolPlugin.coolMethod(function (successMsg) {
//            alert("successMsg:" + successMsg);
//
//        }, function (errorMsg) {
//            alert("errorMsg:" + errorMsg);
//        }, "CoolPlugin", "coolMethod", ["success"]);

    cordova.exec(function (successMsg) {
          alert("successMsg:" + successMsg);

      }, function (errorMsg) {
          alert("errorMsg:" + errorMsg);
      }, "CoolPlugin", "coolMethod", ["success"]);
}

上述代码写在app.initialize();这句话的下面就可以了。
这个里面我写了两个方法,我注释的那个有点问题,还没有解决,大家可以尝试这解决一下,下边那个是可以用的。
现在就可以在Android Studio中直接运行了,当我们点击按钮的时候,会有一个success的对话框和提示框,这就说明成功了!

上述只是自定义插件的一个简单小例子,接下来我们来实现从本地图库选择带二维码的图片并识别,其实主要是修改我们的CoolPlugin.java代码。

实现从本地选择带二维码的图片并识别

下载zxing.jar http://download.csdn.net/download/queen_332/9968925
大家也可以从网上自己搜。下载后放到libs目录下,Ctrl+Shift+Alt+S 弹出窗口,把我们的文件添加进来就ok了

这里写图片描述

下面就是我修改后的java文件

public class CoolPlugin extends CordovaPlugin {
    private CallbackContext mCallbackContext;
    private int RESULT_LOAD_IMAGE = 1;
    private String photo_path = null;
    private String recode = null;


    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        this.mCallbackContext = callbackContext;
        if ("toast".equals(action)) {
            getPic();

            return true;
        }
        mCallbackContext.error("error");
        return false;
    }

    /**
     * 从本地图库选图片
     */
    private void getPic() {
        Intent innerIntent = new Intent(); // "android.intent.action.GET_CONTENT"
        if (Build.VERSION.SDK_INT < 19) {
            innerIntent.setAction(Intent.ACTION_GET_CONTENT);
        } else {
            // innerIntent.setAction(Intent.ACTION_OPEN_DOCUMENT);  这个方法报 图片地址 空指针;使用下面的方法
            innerIntent.setAction(Intent.ACTION_PICK);
        }

        innerIntent.setType("image/*");

        Intent wrapperIntent = Intent.createChooser(innerIntent, "选择二维码图片");

        cordova.startActivityForResult(this, wrapperIntent, RESULT_LOAD_IMAGE);

    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RESULT_LOAD_IMAGE) {
            if (resultCode == RESULT_OK) {

                String[] proj = {MediaStore.Images.Media.DATA};
                // 获取选中图片的路径
                final Cursor cursor = cordova.getActivity().getContentResolver().query(data.getData(),
                        proj, null, null, null);

                if (cursor.moveToFirst()) {
                    int column_index = cursor
                            .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                    photo_path = cursor.getString(column_index);

                    if (photo_path == null) {
                        photo_path = Utils.getPath(cordova.getActivity(),
                                data.getData());

                    }

                }

                cursor.close();
                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {

                        Result result = Utils.scanningImage(photo_path);
                        // String result = decode(photo_path);
                        if (result == null) {
                            Looper.prepare();
                            Toast.makeText(cordova.getActivity(), "图片格式有误", Toast.LENGTH_SHORT)
                                    .show();
                            Looper.loop();
                        } else {
                            // 数据返回
                            recode = Utils.recode(result.toString());

                        }
                        mCallbackContext.success(recode);
                    }
                });

            }

        }

    }

}

然后在我们的CoolPlugin.java的同一目录下新建两个文件Utils和RGBLuminanceSource文件如图
这里写图片描述

代码如下

package coolPlugin;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.google.zxing.LuminanceSource;

import java.io.FileNotFoundException;

/**
 * Created by wangying on 17-9-7.
 */

public class RGBLuminanceSource  extends LuminanceSource {
    private final byte[] luminances;

    public RGBLuminanceSource(String path) throws FileNotFoundException {
        this(loadBitmap(path));
    }

    public RGBLuminanceSource(Bitmap bitmap) {
        super(bitmap.getWidth(), bitmap.getHeight());
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        // In order to measure pure decoding speed, we convert the entire image
        // to a greyscale array
        // up front, which is the same as the Y channel of the
        // YUVLuminanceSource in the real app.
        luminances = new byte[width * height];
        for (int y = 0; y < height; y++) {
            int offset = y * width;
            for (int x = 0; x < width; x++) {
                int pixel = pixels[offset + x];
                int r = (pixel >> 16) & 0xff;
                int g = (pixel >> 8) & 0xff;
                int b = pixel & 0xff;
                if (r == g && g == b) {
                    // Image is already greyscale, so pick any channel.
                    luminances[offset + x] = (byte) r;
                } else {
                    // Calculate luminance cheaply, favoring green.
                    luminances[offset + x] = (byte) ((r + g + g + b) >> 2);
                }
            }
        }
    }

    @Override
    public byte[] getRow(int y, byte[] row) {
        if (y < 0 || y >= getHeight()) {
            throw new IllegalArgumentException(
                    "Requested row is outside the image: " + y);
        }
        int width = getWidth();
        if (row == null || row.length < width) {
            row = new byte[width];
        }
        System.arraycopy(luminances, y * width, row, 0, width);
        return row;
    }

    // Since this class does not support cropping, the underlying byte array
    // already contains
    // exactly what the caller is asking for, so give it to them without a copy.
    @Override
    public byte[] getMatrix() {
        return luminances;
    }

    private static Bitmap loadBitmap(String path) throws FileNotFoundException {
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        if (bitmap == null) {
            throw new FileNotFoundException("Couldn't open " + path);
        }
        return bitmap;
    }
}
package coolPlugin;

import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Hashtable;

/**
 * Created by wangying on 17-9-7.
 */

public class Utils {
    public static Bitmap scanBitmap = null;
//    public static final boolean isChineseCharacter(String chineseStr) {
//        char[] charArray = chineseStr.toCharArray();
//        for (int i = 0; i < charArray.length; i++) {
//            // 是否是Unicode编码,除了"�"这个字符.这个字符要另外处理
//            if ((charArray[i] >= '\u0000' && charArray[i] < '\uFFFD')
//                    || ((charArray[i] > '\uFFFD' && charArray[i] < '\uFFFF'))) {
//                continue;
//            } else {
//                return false;
//            }
//        }
//        return true;
//    }

    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     * Framework Documents, as well as the _data field for the MediaStore and
     * other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri     The Uri to query.
     * @author paulburke
     */
    @TargetApi(19)
    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/"
                            + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"),
                        Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[]{split[1]};

                return getDataColumn(context, contentUri, selection,
                        selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context       The context.
     * @param uri           The Uri to query.
     * @param selection     (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri,
                                       String selection, String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column};

        try {
            cursor = context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri
                .getAuthority());
    }


    /**
     * //TODO: TAOTAO 将bitmap由RGB转换为YUV //TOOD: 研究中
     *
     * @param bitmap 转换的图形
     * @return YUV数据
     */
    public static byte[] rgb2YUV(Bitmap bitmap) {
        // 该方法来自QQ空间
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

        int len = width * height;
        byte[] yuv = new byte[len * 3 / 2];
        int y, u, v;
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int rgb = pixels[i * width + j] & 0x00FFFFFF;

                int r = rgb & 0xFF;
                int g = (rgb >> 8) & 0xFF;
                int b = (rgb >> 16) & 0xFF;

                y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
                u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
                v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;

                y = y < 16 ? 16 : (y > 255 ? 255 : y);
                u = u < 0 ? 0 : (u > 255 ? 255 : u);
                v = v < 0 ? 0 : (v > 255 ? 255 : v);

                yuv[i * width + j] = (byte) y;
                // yuv[len + (i >> 1) * width + (j & ~1) + 0] = (byte) u;
                // yuv[len + (i >> 1) * width + (j & ~1) + 1] = (byte) v;
            }
        }
        return yuv;
    }


    /**
     * 中文乱码
     * <p>
     * 暂时解决大部分的中文乱码 但是还有部分的乱码无法解决 .
     * <p>
     * 如果您有好的解决方式 请联系 2221673069@qq.com
     * <p>
     * 我会很乐意向您请教 谢谢您
     *
     * @return
     */
    public static String recode(String str) {
        String formart = "";

        try {
            boolean ISO = Charset.forName("ISO-8859-1").newEncoder()
                    .canEncode(str);
            if (ISO) {
                formart = new String(str.getBytes("ISO-8859-1"), "GB2312");
                Log.i("1234      ISO8859-1", formart);
            } else {
                formart = str;
                Log.i("1234      stringExtra", str);
            }
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return formart;
    }

    // TODO: 解析部分图片
    public static Result scanningImage(String path) {
        if (TextUtils.isEmpty(path)) {

            return null;

        }
        // DecodeHintType 和EncodeHintType
        Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8"); // 设置二维码内容的编码
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 先获取原大小
        scanBitmap = BitmapFactory.decodeFile(path, options);
        options.inJustDecodeBounds = false; // 获取新的大小

        int sampleSize = (int) (options.outHeight / (float) 200);

        if (sampleSize <= 0)
            sampleSize = 1;
        options.inSampleSize = sampleSize;
        scanBitmap = BitmapFactory.decodeFile(path, options);

        // --------------测试的解析方法---PlanarYUVLuminanceSource-这几行代码对project没作功----------

        LuminanceSource source1 = new PlanarYUVLuminanceSource(
                Utils.rgb2YUV(scanBitmap), scanBitmap.getWidth(),
                scanBitmap.getHeight(), 0, 0, scanBitmap.getWidth(),
                scanBitmap.getHeight(), false);
        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(
                source1));
        MultiFormatReader reader1 = new MultiFormatReader();
        Result result1;
        try {
            result1 = reader1.decode(binaryBitmap);
            String content = result1.getText();
            Log.i("123content", content);
        } catch (NotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        // ----------------------------

        coolPlugin.RGBLuminanceSource source = new coolPlugin.RGBLuminanceSource(scanBitmap);
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        QRCodeReader reader = new QRCodeReader();
        try {

            try {
                try {
                    return reader.decode(bitmap1, hints);
                } catch (FormatException e) {
                    e.printStackTrace();
                }
            } catch (ChecksumException e) {
                e.printStackTrace();
            }

        } catch (NotFoundException e) {

            e.printStackTrace();

        }

        return null;

    }


}

修改index.js文件

  function btnClick(){
    cordova.exec(function (successMsg) {
          alert("successMsg:" + successMsg);

      }, function (errorMsg) {
          alert("errorMsg:" + errorMsg);
      }, "CoolPlugin", "coolMethod", []);
}

ok,再运行就是选图片,识别二维码了!
后期会把源码分享给大家,因为我是刚刚接触,有的地方可能说的不够仔细,如果有错误的地方,请指正,多谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值