新建項目,名為TOMHW,把編譯得到的包含.so文件的armeabi文件夾復制到項目中的libs文件夾下
打開模擬器,在ddms中把handwriting-zh_CN.model文件push到/data/data目錄下
整個項目視圖如下
AndroidManifest.xml代碼如下

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.tomhw" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> <application android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
mainactivity.xml代碼如下

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > </LinearLayout>
從zinnia官網的usage代碼可以看出,zinnia的使用流程如下:
1.讀取model文件
2.把筆畫用zinnia_character_add添加到character中
3.用recognizer_classify把character拿到model中識別並返回結果給result
4.從result中取出結果顯示出來
所以MainActivity.java的代碼如下

package com.tomhw; import java.util.Timer; import java.util.TimerTask; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MyView(this)); } //能顯示出手寫軌跡的view public class MyView extends SurfaceView implements Callback, Runnable{ //按下返回鍵即退出程序 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { finish(); } return true; } //建立手寫輸入對象 long recognizer = 0; long character = 0; long result = 0; int modelState = 0; //顯示model文件載入狀態 int strokes = 0; //總筆畫數 boolean resultDisplay = false; //是否顯示結果 int handwriteCount = 0; //筆畫數 private Thread mThread; SurfaceHolder mSurfaceHolder = null; Canvas mCanvas = null; Paint mPaint = null; Path mPath = null; Paint mTextPaint = null; //文字畫筆 public static final int FRAME = 60;//畫布更新幀數 boolean mIsRunning = false; //控制是否更新 float posX, posY; //觸摸點當前座標 //觸發定時識別任務 Timer tExit; TimerTask task; public MyView(Context context) { super(context); //設置擁有焦點 this.setFocusable(true); //設置觸摸時擁有焦點 this.setFocusableInTouchMode(true); //獲取holder mSurfaceHolder = this.getHolder(); //添加holder到callback函數之中 mSurfaceHolder.addCallback(this); //創建畫布 mCanvas = new Canvas(); //創建畫筆 mPaint = new Paint(); mPaint.setColor(Color.BLUE);//顏色 mPaint.setAntiAlias(true);//抗鋸齒 //Paint.Style.STROKE 、Paint.Style.FILL、Paint.Style.FILL_AND_STROKE //意思分別為 空心 、實心、實心與空心 mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeCap(Paint.Cap.ROUND);//設置畫筆為圓滑狀 mPaint.setStrokeWidth(5);//設置線的寬度 //創建路徑軌跡 mPath = new Path(); //創建文字畫筆 mTextPaint = new Paint(); mTextPaint.setColor(Color.BLACK); mTextPaint.setTextSize(15); //創建手寫識別 if (character == 0) { character = characterNew(); characterClear(character); characterSetWidth(character, 300); characterSetHeight(character, 300); } if (recognizer == 0) { recognizer = recognizerNew(); } //打開成功返回1 modelState = recognizerOpen(recognizer, "/data/data/handwriting-zh_CN.model"); if (modelState != 1) { System.out.println("model文件打開失敗"); return; } } @Override public boolean onTouchEvent(MotionEvent event) { //獲取觸摸動作以及座標 int action = event.getAction(); float x = event.getX(); float y = event.getY(); //按觸摸動作分發執行內容 switch (action) { case MotionEvent.ACTION_DOWN: if (tExit != null) { tExit.cancel(); tExit = null; task = null; } resultDisplay = false; mPath.moveTo(x, y);//設定軌跡的起始點 break; case MotionEvent.ACTION_MOVE: mPath.quadTo(posX, posY, x, y); //隨觸摸移動設置軌跡 characterAdd(character, handwriteCount, (int)x, (int)y); break; case MotionEvent.ACTION_UP: handwriteCount++; tExit = new Timer(); task = new TimerTask() { @Override public void run() { resultDisplay = true; } }; tExit.schedule(task, 1000); break; } //記錄當前座標 posX = x; posY = y; return true; } private void Draw(){ //防止canvas為null導致出現null pointer問題 if (mCanvas != null) { mCanvas.drawColor(Color.WHITE); //清空畫布 mCanvas.drawPath(mPath, mPaint); //畫出軌跡 //數據記錄 mCanvas.drawText("model打開狀態 : " + modelState, 5, 20, mTextPaint); mCanvas.drawText("觸點X的座標 : " + posX, 5, 40, mTextPaint); mCanvas.drawText("觸點Y的座標 : " + posY, 5, 60, mTextPaint); strokes = (int)characterStrokesSize(character); mCanvas.drawText("總筆畫數 : " + strokes, 5, 80, mTextPaint); } //進行文字檢索 if (strokes > 0 && resultDisplay) { result = recognizerClassify(recognizer, character, 10); if (tExit != null) { tExit.cancel(); tExit = null; task = null; } characterClear(character); strokes = 0; mPath.reset();//觸摸結束即清除軌跡 resultDisplay = false; handwriteCount = 0; } //顯示識別出的文字 if (result != 0) { for (int i = 0; i < resultSize(result); i++) { mCanvas.drawText(resultValue(result, i) + " : " + resultScore(result, i), 5, 100 + i * 20, mTextPaint); } } } @Override public void run() { while(mIsRunning){ //更新前的時間 long startTime = System.currentTimeMillis(); //線程安全鎖 synchronized(mSurfaceHolder){ mCanvas = mSurfaceHolder.lockCanvas(); Draw(); mSurfaceHolder.unlockCanvasAndPost(mCanvas); } //獲取更新后的時間 long endTime = System.currentTimeMillis(); //獲取更新時間差 int diffTime = (int)(endTime - startTime); //確保每次更新都為FRAME while(diffTime <= FRAME){ diffTime = (int)(System.currentTimeMillis() - startTime); //Thread.yield(): 與Thread.sleep(long millis):的區別, //Thread.yield(): 是暫停當前正在執行的線程對象 ,並去執行其他線程。 //Thread.sleep(long millis):則是使當前線程暫停參數中所指定的毫秒數然后在繼續執行線程 Thread.yield(); } } } @Override public void surfaceCreated(SurfaceHolder holder) { mIsRunning = true; mThread = new Thread(this); mThread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceDestroyed(SurfaceHolder holder) { resultDestroy(result); characterDestroy(character); recognizerDestroy(recognizer); mThread = null; } } //jni封裝方法的聲明 //charater public native long characterNew(); public native void characterDestroy(long c); public native void characterClear(long stroke); public native int characterAdd(long character, long id, int x, int y); public native void characterSetWidth(long character, long width); public native void characterSetHeight(long character, long height); public native long characterStrokesSize(long character); //recognizer public native long recognizerNew(); public native void recognizerDestroy(long recognizer); public native int recognizerOpen(long recognizer, String filename); public native String recognizerStrerror(long recognizer); public native long recognizerClassify(long recognizer, long character, long nbest); //result public native String resultValue(long result, long index); public native float resultScore(long result, long index); public native long resultSize(long result); public native void resultDestroy(long result); //載入.so文件 static{ System.loadLibrary("zinniajni"); } }
代碼流程:用surfaceview定時刷新畫面,用path來描繪手寫軌跡,用canvas顯示出來
當用戶碰觸屏幕時,即取消字體識別任務,用戶移動時用path記錄軌跡,用戶手指離開屏幕時就馬上約定識別任務,
若用戶下一次碰觸屏幕與上一次離開屏幕的時間差大於1秒,即進行字體識別
最終結果截圖如下
完整代碼下載請點擊這里