1. 程式人生 > >【Android】靈雲銀行卡離線識別功能使用說明

【Android】靈雲銀行卡離線識別功能使用說明

註冊

使用靈雲的影象識別功能需要先在官網上進行註冊應用。官網地址

註冊比較簡單,就不做過多介紹了,註冊完應用以後,在後臺建立自己的應用,建立完應用以後需要給應用開通對應的影象功能。
這裡寫圖片描述

capKey說明:

  • ocr.local.bankcard 本地銀行卡識別功能
  • ocr.local.template.v6 本地身份證識別功能
  • ocr.local.bizcard.v6 本地名片識別功能
  • ocr.local 本地文字圖片識別功能

整合

下載靈雲的Android版本影象識別功能,下載地址
如果使用線上功能,下載對應的SDK,裡面有jar包和so,就可以滿足需求了。如果要使用離線的影象識別功能,還需要下載

靈雲資原始檔

資原始檔列表:

這裡寫圖片描述

原始碼

靈雲離線手寫識別功能

需要加入的so和jar包有:

  • libhci_curl.so
  • libhci_sys.so
  • libhci_sys_jni.so
  • libhci_hwr.so
  • libhci_hwr_hwr_jni.so
  • libhci_hwr_local_recog.so
  • libletter.conf.so
  • libletter.dic.so

需要使用的許可權

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

載入配置類

package com.miao.util;

/**
 * 靈雲配置資訊
 * Created by 10048 on 2016/12/3.
 */
public class ConfigUtil { /** * 靈雲APP_KEY */ public static final String APP_KEY = "c85d54f1"; /** * 開發者金鑰 */ public static final String DEVELOPER_KEY = "712ddd892cf9163e6383aa169e0454e3"; /** * 靈云云服務的介面地址 */ public static final String CLOUD_URL = "test.api.hcicloud.com:8888"; /** * 需要執行的靈雲能力 */ //雲端多字識別功能 public static final String CAP_KEY_HWR_CLOUD_FREETALK = "hwr.cloud.freetalk"; //雲端單字識別功能 public static final String CAP_KEY_HWR_CLOUD_LETTER = "hwr.cloud.letter"; //離線單字識別功能 public static final String CAP_KEY_HWR_LOCAL_LETTER = "hwr.local.letter"; //離線多字識別功能 public static final String CAP_KEY_HWR_LOCAL_FREESTYLUS = "hwr.local.freestylus"; //離線聯想功能 public static final String CAP_KEY_HWR_LOCAL_ASSOCIATE_WORD = "hwr.local.associateword"; //離線筆形功能 public static final String CAP_KEY_HWR_LOCAL_PENSCRIPT = "hwr.local.penscript"; }

封裝靈雲系統的初始化功能

package com.example.sinovoice.util;

import android.content.Context;
import android.os.Environment;
import android.util.Log;

import com.sinovoice.hcicloudsdk.api.HciCloudSys;
import com.sinovoice.hcicloudsdk.common.AuthExpireTime;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.InitParam;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Created by miaochangchun on 2016/11/28.
 */
public class HciCloudSysHelper {
    private static final String TAG = HciCloudSysHelper.class.getSimpleName();
    private static HciCloudSysHelper mHciCloudSysHelper = null;

    private HciCloudSysHelper(){
    }

    public static HciCloudSysHelper getInstance() {
        if (mHciCloudSysHelper == null) {
            return  new HciCloudSysHelper();
        }
        return  mHciCloudSysHelper;
    }

    /**
     * 初始化函式
     * @param context
     * @return
     */
    public int init(Context context){
        //配置串引數
        String strConfig = getInitParam(context);
        int errCode = HciCloudSys.hciInit(strConfig, context);
        if (errCode != HciErrorCode.HCI_ERR_NONE){
            Log.e(TAG, "hciInit Failed and return errcode = " + errCode);
            return errCode;
        }

        errCode = checkAuthAndUpdateAuth();
        if (errCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "checkAuthAndUpdateAuth Failed and return errcode = " + errCode);
            return errCode;
        }
        return HciErrorCode.HCI_ERR_NONE;
    }

    /**
     * 獲取授權
     * @return
     */
    private int checkAuthAndUpdateAuth() {
        // 獲取系統授權到期時間
        AuthExpireTime objExpireTime = new AuthExpireTime();
        int initResult = HciCloudSys.hciGetAuthExpireTime(objExpireTime);
        if (initResult == HciErrorCode.HCI_ERR_NONE) {
            // 顯示授權日期,如使用者不需要關注該值,此處程式碼可忽略
            Date date = new Date(objExpireTime.getExpireTime() * 1000);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
            Log.i(TAG, "expire time: " + sdf.format(date));

            if (objExpireTime.getExpireTime() * 1000 > System.currentTimeMillis()) {
                // 已經成功獲取了授權,並且距離授權到期有充足的時間(>7天)
                Log.i(TAG, "checkAuth success");
                return initResult;
            }
        }

        // 獲取過期時間失敗或者已經過期
        initResult = HciCloudSys.hciCheckAuth();
        if (initResult == HciErrorCode.HCI_ERR_NONE) {
            Log.i(TAG, "checkAuth success");
            return initResult;
        } else {
            Log.e(TAG, "checkAuth failed: " + initResult);
            return initResult;
        }
    }

    /**
     * 獲取配置傳引數
     * @param context
     * @return
     */
    private String getInitParam(Context context) {
        InitParam initParam = new InitParam();
        //靈云云服務的介面地址,此項必填
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_APP_KEY, ConfigUtil.APP_KEY);
        //靈云云服務的介面地址,此項必填
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, ConfigUtil.DEVELOPER_KEY);
        //靈云云服務的介面地址,此項必填
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_CLOUD_URL, ConfigUtil.CLOUD_URL);

        String authPath = context.getFilesDir().getAbsolutePath();
        //授權檔案所在路徑,此項必填
        initParam.addParam(InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authPath);

        //日誌數目,預設保留多少個日誌檔案,超過則覆蓋最舊的日誌
        initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_COUNT, "5");
        String logPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                + "sinovoice" + File.separator
                + context.getPackageName() + File.separator
                + "log" + File.separator;
        File file = new File(logPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        //日誌的路徑,可選,如果不傳或者為空則不生成日誌
        initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logPath);
        Log.d(TAG, "logPath = " + logPath);
        //日誌大小,預設一個日誌檔案寫多大,單位為K
        initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_SIZE, "1024");
        //日誌等級,0=無,1=錯誤,2=警告,3=資訊,4=細節,5=除錯,SDK將輸出小於等於logLevel的日誌資訊
        initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");

        return initParam.getStringConfig();
    }

    /**
     * 反初始化
     * @return
     */
    public int release(){
        return HciCloudSys.hciRelease();
    }
}

封裝靈雲手寫識別功能

package com.miao.util;

import android.content.Context;
import android.util.Log;

import com.sinovoice.hcicloudsdk.api.hwr.HciCloudHwr;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.Session;
import com.sinovoice.hcicloudsdk.common.hwr.HwrAssociateWordsResult;
import com.sinovoice.hcicloudsdk.common.hwr.HwrConfig;
import com.sinovoice.hcicloudsdk.common.hwr.HwrInitParam;
import com.sinovoice.hcicloudsdk.common.hwr.HwrRecogResult;
import com.sinovoice.hcicloudsdk.common.hwr.HwrRecogResultItem;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Created by miaochangchun on 2016/11/1.
 */
public class HciCloudHwrHelper {
    public static final String TAG = HciCloudHwrHelper.class.getSimpleName();
    private static HciCloudHwrHelper mHciCloudHwrHelper = null;

    private HciCloudHwrHelper(){

    }

    public static HciCloudHwrHelper getInstance(){
        if (mHciCloudHwrHelper == null) {
            return new HciCloudHwrHelper();
        }
        return  mHciCloudHwrHelper;
    }

    /**
     * 手寫功能初始化
     * @param context   上下文
     * @param capkey    使用的capkey,手寫單字識別為hwr.local.letter,多字識別為hwr.local.freestylus,聯想功能為hwr.local.associateword
     * @return  返回0為成功,其他為失敗
     */
    public int initHwr(Context context, String capkey){
        String strConfig = getHwrInitParam(context, capkey);
        Log.d(TAG, "strConfig = " + strConfig);
        int errorCode = HciCloudHwr.hciHwrInit(strConfig);
        return errorCode;
    }

    /**
     * 手寫識別函式
     * @param data  筆跡座標
     * @param capkey    使用的capkey,手寫單字識別為hwr.local.letter,多字識別為hwr.local.freestylus,聯想功能為hwr.local.associateword
     * @return  返回識別的結果
     */
    public String recog(short[] data, String capkey){
        Session session = new Session();
        String sessionConfig = getHwrSessionParam(capkey);
        Log.d(TAG, "sessionConfig = " + sessionConfig);
        //sessionStart開啟一個session
        int errorCode = HciCloudHwr.hciHwrSessionStart(sessionConfig, session);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "hciHwrSessionStart failed and return " + errorCode);
            HciCloudHwr.hciHwrSessionStop(session);
            return null;
        }
        //識別結果類進行初始化
        HwrRecogResult recogResult = new HwrRecogResult();
        //識別的配置串引數可以設定為空,預設就使用sessionConfig配置串引數

        errorCode = HciCloudHwr.hciHwrRecog(session, data, "", recogResult);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "hciHwrRecog failed and return " + errorCode);
            return null;
        }

        //關閉Session
        errorCode = HciCloudHwr.hciHwrSessionStop(session);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "hciHwrSessionStop failed and return " + errorCode);
            return null;
        }
        //返回識別結果
        StringBuilder sb = new StringBuilder();
        List<HwrRecogResultItem> lists = recogResult.getResultItemList();
        Iterator<HwrRecogResultItem> iterator = lists.iterator();
        while (iterator.hasNext()) {
            HwrRecogResultItem item = iterator.next();
            sb.append(item.getResult()).append(" , ");
        }
        return sb.toString();
//        return recogResult.getResultItemList().get(0).getResult();
    }

    /**
     * 聯想詞功能,對str進行聯想,返回聯想結果
     * @param str   需要聯想的字串
     * @param assCapkey 聯想功能對應的capkey。
     * @return
     */
    public String associateWord(String str, String assCapkey){
        Session session = new Session();
        String sessionConfig = getAssociateWordSessionParam(assCapkey);
        int errorCode = HciCloudHwr.hciHwrSessionStart(sessionConfig, session);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "HciCloudHwr.hciHwrSessionStart failed and return " + errorCode);
        }
        HwrAssociateWordsResult hwrAssociateWordsResult = new HwrAssociateWordsResult();
        errorCode = HciCloudHwr.hciHwrAssociateWords(session, str, "", hwrAssociateWordsResult);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "HciCloudHwr.hciHwrAssociateWords failed and return " + errorCode);
        }
        errorCode = HciCloudHwr.hciHwrSessionStop(session);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Log.e(TAG, "HciCloudHwr.hciHwrSessionStop failed and return " + errorCode);
        }
        ArrayList<String> lists = hwrAssociateWordsResult.getResultList();
        StringBuilder sb = new StringBuilder();
        Iterator<String> iterator = lists.iterator();
        while (iterator.hasNext()) {
            String string = iterator.next();
            sb.append(string).append(",");
        }
        return  sb.toString();
    }

    /**
     * 設定聯想詞的配置引數
     * @param assCapkey 聯想詞功能所需的capkey,需要設定為 assCapkey=hwr.local.associateword
     * @return  聯想詞配置的字串
     */
    private String getAssociateWordSessionParam(String assCapkey) {
        HwrConfig hwrConfig = new HwrConfig();
        hwrConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, assCapkey);
        return hwrConfig.getStringConfig();
    }

    /**
     * 獲取手寫識別的配置引數
     * @param hwrCapkey    使用的capkey,手寫單字識別為hwr.local.letter,多字識別為hwr.local.freestylus
     * @return  返回配置串
     */
    private String getHwrSessionParam(String hwrCapkey) {
        HwrConfig hwrConfig = new HwrConfig();
        hwrConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, hwrCapkey);
        //設定識別結果的候選個數
        hwrConfig.addParam(HwrConfig.ResultConfig.PARAM_KEY_CAND_NUM, "10");
        //設定識別結果的範圍
        hwrConfig.addParam(HwrConfig.ResultConfig.PARAM_KEY_RECOG_RANGE, "gbk");
        return hwrConfig.getStringConfig();
    }

    /**
     * 獲取手寫的初始化配置引數
     * @param context   上下文
     * @param initCapkeys    使用的capkey,手寫單字識別為hwr.local.letter,
     *                       多字識別為hwr.local.freestylus,
     *                       聯想功能為hwr.local.associateword
     *                       可以設定多個,中間以分號隔開
     * @return  返回配置串
     */
    private String getHwrInitParam(Context context, String initCapkeys) {
        HwrInitParam hwrInitParam = new HwrInitParam();
        hwrInitParam.addParam(HwrInitParam.PARAM_KEY_INIT_CAP_KEYS, initCapkeys);
        hwrInitParam.addParam(HwrInitParam.PARAM_KEY_FILE_FLAG, "android_so");
        String dataPath = context.getFilesDir().getAbsolutePath().replace("files", "lib");
        hwrInitParam.addParam(HwrInitParam.PARAM_KEY_DATA_PATH, dataPath);
        return hwrInitParam.getStringConfig();
    }

    /**
     * 手寫反初始化功能
     * @return  返回0為成功,其他為失敗
     */
    public int releaseHwr(){
        return HciCloudHwr.hciHwrRelease();
    }
}

在MainActivity中使用手寫識別的功能

package com.miao.hwrtest;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;

import com.miao.util.ConfigUtil;
import com.miao.util.HciCloudHwrHelper;
import com.miao.util.HciCloudSysHelper;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{

    private static final int MAX_POINT = 2048;
    private SurfaceHolder surfaceHolder;
    private SurfaceView surface;
    private Paint paint;
    private Path path;
    private int top;
    private int bottom;
    private boolean isRunning = true;
    private Canvas canvas;
    private String TAG = MainActivity.class.getSimpleName();
    private boolean start = true;
    private long init;
    private long now;
    private int mCurX, mCurY;
    private int mCurIndex;
    private short[] mPoints = null;
    private boolean mEnd;
    private HciCloudSysHelper mHciCloudSysHelper;
    private HciCloudHwrHelper mHciCloudHwrHelper;
    private EditText myEditText;

    private Handler myHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.arg1) {
                case 0:
                    Bundle bundle = msg.getData();
                    String result = bundle.getString("result");
                    myEditText.setText(result);
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        initView();
        initSinovoice();
    }

    private void initView() {
        surface = (SurfaceView) findViewById(R.id.surface);
        myEditText = (EditText) findViewById(R.id.text);

        surfaceHolder = surface.getHolder();        // 獲得SurfaceHolder物件
        surface.setZOrderOnTop(true);               //使surface可見
        surfaceHolder.setFormat(PixelFormat.TRANSPARENT);   //設定背景透明
        surfaceHolder.addCallback(this);          // 為SurfaceView新增狀態監聽

        paint = new Paint();          // 建立一個畫筆物件
        path = new Path();

        mPoints = new short[MAX_POINT * 2];
        mCurIndex = 0;
    }

    /**
     * 靈雲系統初始化
     */
    private void initSinovoice() {
        mHciCloudSysHelper = HciCloudSysHelper.getInstance();
        mHciCloudHwrHelper = HciCloudHwrHelper.getInstance();
        int errorCode = mHciCloudSysHelper.init(this);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Toast.makeText(this, "系統初始化失敗,錯誤碼=" + errorCode, Toast.LENGTH_SHORT).show();
            return;
        }
        errorCode = mHciCloudHwrHelper.initHwr(this, ConfigUtil.CAP_KEY_HWR_LOCAL_LETTER);
        if (errorCode != HciErrorCode.HCI_ERR_NONE) {
            Toast.makeText(this, "手寫初始化失敗,錯誤碼=" + errorCode, Toast.LENGTH_SHORT).show();
            return ;
        }
    }

    /**
     *  把座標新增到short陣列
     * @param x
     * @param y
     * @return
     */
    private boolean addStroke(short x, short y) {
        if (mCurX >= 0 && mCurY >= 0) {
            if ((mCurIndex / 2) < (MAX_POINT - 2)) {
                mPoints[mCurIndex] = x;
                mCurIndex++;
                mPoints[mCurIndex] = y;
                mCurIndex++;
                return true;
            } else if ((mCurIndex / 2) == (MAX_POINT - 2)) {
                mPoints[mCurIndex] = -1;
                mCurIndex++;
                mPoints[mCurIndex] = 0;
                mCurIndex++;
                return true;
            }
        }
        return false;
    }

    /**
     * 最後一筆新增座標(-1,-1)
     */
    public void addLastStrokePoint() {
        if(mCurIndex < 2 ||(mPoints[mCurIndex-1] == -1 && mPoints[mCurIndex-2] == -1))
        {
            return;
        }
        mPoints[mCurIndex] = -1;
        mCurIndex++;
        mPoints[mCurIndex] = -1;
        mCurIndex++;
    }

    /**
     * 每次擡筆新增座標(-1,0)
     */
    private void addStrokeEnd() {
        mPoints[mCurIndex] = -1;
        mCurIndex++;
        mPoints[mCurIndex] = 0;
        mCurIndex++;
    }

    /**
     * 重置筆跡點資料
     */
    private void resetStroke() {
        mPoints = new short[MAX_POINT * 2];
        mCurIndex = 0;
    }

    /**
     * 獲取筆跡點陣列
     * @return 筆跡點陣列
     */
    public short[] getStroke() {
        mEnd = true;
        addStrokeEnd();
        addLastStrokePoint();
        short[] stroke = new short[mCurIndex];
        System.arraycopy(mPoints, 0, stroke, 0, mCurIndex);

        return stroke;
    }

    /**
     * 當SurfaceView建立的時候,呼叫此函式
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        int[] location = new int[2];
        surface.getLocationOnScreen(location);
        top = location[1];
        Log.d(TAG, "top = " + top);
        bottom = top + surface.getHeight();
        Log.d(TAG, "bottom = " + bottom);
        //開啟一個繪畫執行緒
        new Thread(wlineThread).start();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getY() >= top && event.getY() <= bottom) {
            start = true;
            init = now;
            now = System.currentTimeMillis();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (now - init >= 100 && now - init <= 1000) {      //擡筆操作,加上(-1,0)
//                        Log.d(TAG, "X座標=" + "-1" + "\tY座標=" + "0");
                        addStrokeEnd();
                    }
                    path.moveTo(event.getX(), event.getY() - top);
//                    Log.d(TAG, "X座標=" + event.getX() + "\tY座標=" + event.getY());
//                    addStroke((short) event.getX(), (short) event.getY());

                    break;
                case MotionEvent.ACTION_MOVE:
                    path.lineTo(event.getX(), event.getY() - top);
//                    Log.d(TAG, "X座標=" + event.getX() + "\tY座標=" + event.getY());
                    addStroke((short) event.getX(), (short) event.getY());

                    break;
                default:
                    break;
            }
        }
        return true;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            isRunning = false;
        }
        return super.onKeyDown(keyCode, event);
    }

    /**
     * 當SurfaceView的檢視發生改變的時候,呼叫此函式
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * 當SurfaceView銷燬的時候,呼叫此函式
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d(TAG, "surface Destroyed");
        isRunning = false;
    }

    Runnable wlineThread =new Runnable(){

        @Override
        public void run() {
            while (isRunning) {
                drawView();
                if (start) {
                    long temp = System.currentTimeMillis() - now;
//                    Log.d(TAG, "temp=" + temp);
                    if (temp > 1000 && temp < 100000) {      //擡筆時間超過1秒,加上座標(-1,-1),過濾第一次時間的計算
//                        Log.d(TAG, "X座標=" + "-1" + "\tY座標=" + "-1");

                        short[] data = getStroke();

                        String result = mHciCloudHwrHelper.recog(data, ConfigUtil.CAP_KEY_HWR_LOCAL_LETTER);
                        Message message = new Message();
                        message.arg1 = 0;
                        Bundle bundle = new Bundle();
                        bundle.putString("result", result);
                        message.setData(bundle);
                        myHandler.sendMessage(message);

                        start = false;

                        clearCanvas();
                    }
                }
            }
        }
    };

    /**
     * 清空Canvas上的筆跡
     */
    private void clearCanvas() {
        for (int i = 0; i < 4; i++) {
            try {
                if (surfaceHolder != null) {
                    canvas = surfaceHolder.lockCanvas();
                    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                    path.reset();
                    resetStroke();
                    mEnd = false;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (canvas != null){
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

    /**
     * 再Canvas上畫
     */
    private void drawView() {
        try {
            if (surfaceHolder != null) {
                canvas = surfaceHolder.lockCanvas();
                canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
                paint.setColor(Color.RED);
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(5);

                canvas.drawPath(path, paint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }

    @Override
    protected void onDestroy() {
        releaseSinovoice();
        super.onDestroy();
    }

    /**
     * 靈雲系統反初始化
     */
    private void releaseSinovoice() {
        if (mHciCloudHwrHelper != null) {
            mHciCloudHwrHelper.releaseHwr();
        }
        if (mHciCloudSysHelper != null) {
            mHciCloudSysHelper.release();
        }
    }
}

注:靈雲的離線手寫識別能力,第一次使用的時候也是需要有一個聯網授權的過程,授權成功以後,即可在授權期內使用離線手寫能力。