人在旅途

朝着胜利的方向~

在Android层实现触摸屏校验

Android 触摸屏校准

--by ONCE

话接上回,我们发现了手工利用tslib校验触摸屏的缺点。那么这一回 我们就来一次稍微高级一点的校验吧。

我们其实只需要相对的x,y以及lcd的x,y就可以把校验系数算出来。这里要说的是lcd的x,y是绝对的准确的 比如我们要在(50,50)画一个十字 那么这个50,50就是我们认为的绝对坐标。我们要的只是从android通过getX()和getY()拿到我们需要的相对坐标。

 

其实一切我们打算做的事情可以都在InputDevice里面做完

下面我把完整之后整个的InputDevice贴出来:

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.WindowManagerPolicy;
import java.io.FileInputStream;
import java.util.StringTokenizer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.os.SystemProperties;
public class InputDevice {
    /** Amount that trackball needs to move in order to generate a key event. */
    static final int TRACKBALL_MOVEMENT_THRESHOLD = 6;
   
    //once edit
    static final String CALIBRATION_FILE = "/data/etc/pointercal";
    //edit ends
   
    final int id;
    final int classes;
    final String name;
    final AbsoluteInfo absX;
    final AbsoluteInfo absY;
    final AbsoluteInfo absPressure;
    final AbsoluteInfo absSize;
       //once edit
    static TransformInfo tInfo;
    //edit ends
    long mDownTime = 0;
    int mMetaKeysState = 0;
    static File desFile;   
    final MotionState mAbs = new MotionState(0, 0);
    final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD,
            TRACKBALL_MOVEMENT_THRESHOLD);
   
    static class MotionState {
        int xPrecision;
        int yPrecision;
        float xMoveScale;
        float yMoveScale;
        MotionEvent currentMove = null;
        boolean changed = false;
        boolean down = false;
        boolean lastDown = false;
        long downTime = 0;
        int x = 0;
        int y = 0;
        int pressure = 1;
        int size = 0;
       
        MotionState(int mx, int my) {
            xPrecision = mx;
            yPrecision = my;
            xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f;
            yMoveScale = my != 0 ? (1.0f/my) : 1.0f;
        }
       
        MotionEvent generateMotion(InputDevice device, long curTime,
                boolean isAbs, Display display, int orientation,
                int metaState) {
            if (!changed) {
                return null;
            }
           //once edit
        //String prop = System.getProperty("ts.config.calibrate", "noset");
        String prop = SystemProperties.get("ts.config.calibrate", "noset");
        if (prop.equalsIgnoreCase("start")){
            Log.i("XXW prop", prop);
            Log.i("XXW", "prop.equalsIgnoreCase start");
            device.tInfo = null;
        }else if (prop.equalsIgnoreCase("done")){
            Log.i("XXW prop", prop);
            Log.i("XXW", "prop.equalsIgnoreCase done");
            readCalibrate();
            SystemProperties.set("ts.config.calibrate", "end");
        }else{
            Log.i("XXW prop", prop);
            Log.i("XXW", "prop.equalsIgnoreCase else");
        }
        //edit ends
            float scaledX = x;
            float scaledY = y;
            float temp;
            float scaledPressure = 1.0f;
            float scaledSize = 0;
            int edgeFlags = 0;
            if (isAbs) {
                int w = display.getWidth()-1;
                int h = display.getHeight()-1;
                if (orientation == Surface.ROTATION_90
                        || orientation == Surface.ROTATION_270) {
                    int tmp = w;
                    w = h;
                    h = tmp;
                }
                if (device.absX != null) {
                      //once edit
                    if (device.tInfo != null){
                        scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y + device.tInfo.z1)/ device.tInfo.s;
                     Log.i("XXW","x: "+x);
                     Log.i("XXW","trans x: "+scaledX);
                    }
                    else
                    //edit ends
                      scaledX = ((scaledX-device.absX.minValue)
                                / device.absX.range) * w;
                }
                if (device.absY != null) {
                    //once edit
                    if (device.tInfo != null){
                        scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y + device.tInfo.z2) / device.tInfo.s;
                    Log.i("XXW","y: "+y);

                    Log.i("XXW","trans y: "+scaledY);
                    }
                    else
                    //edit ends
                       scaledY = ((scaledY-device.absY.minValue)
                                / device.absY.range) * h;
                }
                if (device.absPressure != null) {
                    scaledPressure =
                        ((pressure-device.absPressure.minValue)
                                / (float)device.absPressure.range);
                }
                if (device.absSize != null) {
                    scaledSize =
                        ((size-device.absSize.minValue)
                                / (float)device.absSize.range);
                }
                switch (orientation) {
                    case Surface.ROTATION_90:
                        temp = scaledX;
                        scaledX = scaledY;
                        scaledY = w-temp;
                        break;
                    case Surface.ROTATION_180:
                        scaledX = w-scaledX;
                        scaledY = h-scaledY;
                        break;
                    case Surface.ROTATION_270:
                        temp = scaledX;
                        scaledX = h-scaledY;
                        scaledY = temp;
                        break;
                }

                if (scaledX == 0) {
                    edgeFlags += MotionEvent.EDGE_LEFT;
                } else if (scaledX == display.getWidth() - 1.0f) {
                    edgeFlags += MotionEvent.EDGE_RIGHT;
                }
               
                if (scaledY == 0) {
                    edgeFlags += MotionEvent.EDGE_TOP;
                } else if (scaledY == display.getHeight() - 1.0f) {
                    edgeFlags += MotionEvent.EDGE_BOTTOM;
                }
               
            } else {
                scaledX *= xMoveScale;
                scaledY *= yMoveScale;
                switch (orientation) {
                    case Surface.ROTATION_90:
                        temp = scaledX;
                        scaledX = scaledY;
                        scaledY = -temp;
                        break;
                    case Surface.ROTATION_180:
                        scaledX = -scaledX;
                        scaledY = -scaledY;
                        break;
                    case Surface.ROTATION_270:
                        temp = scaledX;
                        scaledX = -scaledY;
                        scaledY = temp;
                        break;
                }
            }
           
            changed = false;
            if (down != lastDown) {
                int action;
                lastDown = down;
                if (down) {
                    action = MotionEvent.ACTION_DOWN;
                    downTime = curTime;
                } else {
                    action = MotionEvent.ACTION_UP;
                }
                currentMove = null;
                if (!isAbs) {
                    x = y = 0;
                }
                return MotionEvent.obtain(downTime, curTime, action,
                        scaledX, scaledY, scaledPressure, scaledSize, metaState,
                        xPrecision, yPrecision, device.id, edgeFlags);
            } else {
                if (currentMove != null) {
                    if (false) Log.i("InputDevice", "Adding batch x=" + scaledX
                            + " y=" + scaledY + " to " + currentMove);
                    currentMove.addBatch(curTime, scaledX, scaledY,
                            scaledPressure, scaledSize, metaState);
                    if (WindowManagerPolicy.WATCH_POINTER) {
                        Log.i("KeyInputQueue", "Updating: " + currentMove);
                    }
                    return null;
                }
                MotionEvent me = MotionEvent.obtain(downTime, curTime,
                        MotionEvent.ACTION_MOVE, scaledX, scaledY,
                        scaledPressure, scaledSize, metaState,
                        xPrecision, yPrecision, device.id, edgeFlags);
                currentMove = me;
                return me;
            }
        }
    }
   
    static class AbsoluteInfo {
        int minValue;
        int maxValue;
        int range;
        int flat;
        int fuzz;
    };

       //once edit
    static class TransformInfo {
       float x1;
       float y1;
       float z1;
       float x2;
       float y2;
       float z2;
       float s;
    };
    //edit ends
   
    InputDevice(int _id, int _classes, String _name,
            AbsoluteInfo _absX, AbsoluteInfo _absY,
            AbsoluteInfo _absPressure, AbsoluteInfo _absSize) {
        id = _id;
        classes = _classes;
        name = _name;
        absX = _absX;
        absY = _absY;
        absPressure = _absPressure;
        absSize = _absSize;
       
        //once edit
        desFile = new File(CALIBRATION_FILE);
        readCalibrate();
        //edit ends
    }

    static void readCalibrate(){
        //xxw added
        Log.i("XXW","readCalibrate!");
        TransformInfo t = null;
        try {
            FileInputStream is = new FileInputStream(CALIBRATION_FILE);
            byte[] mBuffer = new byte[64];
            int len = is.read(mBuffer);
            is.close();
           
            if (len > 0) {
                int i;
                for (i = 0 ; i < len ; i++) {
                    if (mBuffer[i] == '/n' || mBuffer[i] == 0) {
                        break;
                    }
                }
                len = i;
            }
                               
            StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0, len));
            t = new TransformInfo ();
            t.x1 = Integer.parseInt( st.nextToken() );
            t.y1 = Integer.parseInt( st.nextToken() );
            t.z1 = Integer.parseInt( st.nextToken() );
            t.x2 = Integer.parseInt( st.nextToken() );
            t.y2 = Integer.parseInt( st.nextToken() );
            t.z2 = Integer.parseInt( st.nextToken() );
            t.s = Integer.parseInt( st.nextToken() );
        } catch (java.io.FileNotFoundException e) {
            Log.i("XXW", "FileNotFound!");
        } catch (java.io.IOException e) {
            Log.i("XXW", "IOException");
        }
        tInfo = t;
        Log.i("XXW","readCalibrate done!");
    }
};

与上一次的那个InputDevice相比 我将读取校准文件的代码单独的变成一个函数,之所以这么做 是因为我们打算不重启就可以直接让android校准完成。这里其实也没什么东西 只是读取校验文件 如果读取成功了就用校验公式计算出校准后的坐标。 为了避免重启所以用了一个系统属性ts.config.calibrate来决定重新读取一次文件。当然当ts.config.calibrate值表明正在校验的话 就直接传上来点击的原始坐标而不经过换算。校验完成之后读取一次校验文件然后将系统属性变成其他值不再读取文件。

 

下面我们就要写一个apk来实现校准了。

这里 我尝试了2种方法 一种是纯javaapk 一种是jniapk开始以为只需要java层将屏幕坐标利用公式算一下然后写一个校对文件就可以了 但是事实上不是这样。tslib做的事情不仅仅是一个函数的事情还有很多对触摸屏的识别和内部校验的事情。所以基本上只有jni一种方法可行。以前这个文章写的都是java层进行校验的 其实不可。

整个apk的java代码 目前只能用来做前台的界面以及通过jni调用底层的tslib的函数。

这个类是对ts calibrate里的函数的改写 但是其实仅仅这个是不行的。
package com.android.calibrate;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.util.Log;

public class Calibrate {
    private calibration cal;

    public Calibrate() {
        cal = new calibration();
    }

    class calibration {
        int x[] = new int[5];

        int xfb[] = new int[5];

        int y[] = new int[5];

        int yfb[] = new int[5];

        int a[] = new int[7];
    };

    boolean perform_calibration() {
        Log.i("XXW", "perform_calibration");
        int j;
        float n, x, y, x2, y2, xy, z, zx, zy;
        float det, a, b, c, e, f, i;
        float scaling = (float)65536.0;

        // Get sums for matrix
        n = x = y = x2 = y2 = xy = 0;
        for (j = 0; j < 5; j++) {
            n += 1.0;
            x += (float)cal.x[j];
            y += (float)cal.y[j];
            x2 += (float)(cal.x[j] * cal.x[j]);
            y2 += (float)(cal.y[j] * cal.y[j]);
            xy += (float)(cal.x[j] * cal.y[j]);
        }

        // Get determinant of matrix -- check if determinant is too small
        det = n * (x2 * y2 - xy * xy) + x * (xy * y - x * y2) + y * (x * xy - y * x2);
        if (det < 0.1 && det > -0.1) {
            Log.i("ts_calibrate: determinant is too small -- %f/n", "" + det);
            return false;
        }

        // Get elements of inverse matrix
        a = (x2 * y2 - xy * xy) / det;
        b = (xy * y - x * y2) / det;
        c = (x * xy - y * x2) / det;
        e = (n * y2 - y * y) / det;
        f = (x * y - n * xy) / det;
        i = (n * x2 - x * x) / det;

        // Get sums for x calibration
        z = zx = zy = 0;
        for (j = 0; j < 5; j++) {
            z += (float)cal.xfb[j];
            zx += (float)(cal.xfb[j] * cal.x[j]);
            zy += (float)(cal.xfb[j] * cal.y[j]);
        }

        // Now multiply out to get the calibration for framebuffer x coord
        cal.a[0] = (int)((a * z + b * zx + c * zy) * (scaling));
        cal.a[1] = (int)((b * z + e * zx + f * zy) * (scaling));
        cal.a[2] = (int)((c * z + f * zx + i * zy) * (scaling));

        System.out.printf("%f %f %f/n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c
                * z + f * zx + i * zy));

        // Get sums for y calibration
        z = zx = zy = 0;
        for (j = 0; j < 5; j++) {
            z += (float)cal.yfb[j];
            zx += (float)(cal.yfb[j] * cal.x[j]);
            zy += (float)(cal.yfb[j] * cal.y[j]);
        }

        // Now multiply out to get the calibration for framebuffer y coord
        cal.a[3] = (int)((a * z + b * zx + c * zy) * (scaling));
        cal.a[4] = (int)((b * z + e * zx + f * zy) * (scaling));
        cal.a[5] = (int)((c * z + f * zx + i * zy) * (scaling));

        System.out.printf("%f %f %f/n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c
                * z + f * zx + i * zy));

        // If we got here, we're OK, so assign scaling to a[6] and return
        cal.a[6] = (int)scaling;
        return true;
        /*
         * // This code was here originally to just insert default values
         * for(j=0;j<7;j++) { c->a[j]=0; } c->a[1] = c->a[5] = c->a[6] = 1;
         * return 1;
         */

    }

    void get_sample(int index, int x1, int y1, int x, int y) {
        Log.i("XXW", "get_sample");
        cal.x[index] = x1;
        cal.y[index] = y1;

        cal.xfb[index] = x;
        cal.yfb[index] = y;
    }

    int calibrate_main() {
        int result = 0;
        Log.i("XXW", "calibrate_main");
        if (perform_calibration()) {
            String strPara = String.format("%d %d %d %d %d %d %d", cal.a[1], cal.a[2], cal.a[0],
                    cal.a[4], cal.a[5], cal.a[3], cal.a[6]);

            boolean success = new File("/data/etc").mkdir();
            if (!success) {
                Log.i(this.toString(), "no success");
            }

            File desFile = new File("/data/etc/pointercal");
            if (!desFile.exists())
                try {
                    desFile.createNewFile();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            FileOutputStream fos;

            try {
                fos = new FileOutputStream(desFile);
                byte[] buf = strPara.getBytes();
                int bytesRead = buf.length;
                try {
                    fos.write(buf, 0, bytesRead);
                    fos.flush();
                    fos.close();
                } catch (IOException e) {                   
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {             
                e.printStackTrace();
            }

            result = 0;

        } else {
            result = -1;
        }
        return result;
    }
}

其实这个就是tslib里那个ts_calibrate.cpp的姐妹篇。重要的函数就一个perform_calibration() 。这里要注意的是 你必须首先利用get_sample读取5个点的值来初始化cal然后再调用calibrate_main来计算校验系数。而且那5个点的顺序为 左上 右上 右下 左下 中间。这4个点基本上应该在屏幕的边缘附近 否则计算出来的校验值可能不准。 利用上面的那个类 写一个apk出来跑跑看 流程应该就可以了。

 
package com.android.calibrate;

import com.android.calibrate.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.os.SystemProperties;

public class AndroidCalibrate extends Activity {
    final String TAG = "ScreenCalibration";

    final int UI_SCREEN_WIDTH = 800;

    final int UI_SCREEN_HEIGHT = 480;

    CrossView myview;

    int direction;

    private Calibrate cal;

    int xList[] = {
            50, UI_SCREEN_WIDTH - 50, UI_SCREEN_WIDTH - 50, 50, UI_SCREEN_WIDTH / 2
    };

    int yList[] = {
            50, UI_SCREEN_HEIGHT - 50, UI_SCREEN_HEIGHT - 50, 50, UI_SCREEN_HEIGHT / 2
    };

    static void setNotTitle(Activity act) {
        act.requestWindowFeature(Window.FEATURE_NO_TITLE);
    }

    static void setFullScreen(Activity act) {
        setNotTitle(act);
        act.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setFullScreen(this);   
        myview = new CrossView(this);
        setContentView(myview);
       
        SystemProperties.set("ts.config.calibrate", "start");
       
        cal = new Calibrate();
        direction = 0;
       
        myview.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.i("OnTouch", event.getX() + "," + event.getY());
                v.invalidate();
                if (direction < 4) {
                    Log.i("XXW time onTouchListener", " " + direction);
                    cal.get_sample(direction, (int)event.getX(), (int)event.getY(),
                            xList[direction], yList[direction]);
                }
                if (direction == 4) {
                    cal.get_sample(direction, (int)event.getX(), (int)event.getY(),
                            xList[direction], yList[direction]);
                    Log.i("XXW", "calibrate_main");
                    cal.calibrate_main();
                    Toast.makeText(getBaseContext(), "Calibrate Done!", Toast.LENGTH_SHORT).show();
                    SystemProperties.set("ts.config.calibrate", "done");
                    AndroidCalibrate.this.finish();
                }
                direction++;
                return false;
            }
        });
    }

    public class CrossView extends View {
        public CrossView(Context context) {
            super(context);
        }

        public void onDraw(Canvas canvas) {

            Paint paint = new Paint();
            paint.setColor(Color.GREEN);
            if (direction == 0) {
                canvas.drawLine(40, 50, 60, 50, paint);
                canvas.drawLine(50, 40, 50, 60, paint);
                paint.setColor(Color.WHITE);
            } else if (direction == 1) {
                canvas.drawLine(UI_SCREEN_WIDTH - 60, 50, UI_SCREEN_WIDTH - 40, 50, paint);
                canvas.drawLine(UI_SCREEN_WIDTH - 50, 40, UI_SCREEN_WIDTH - 50, 60, paint);
                paint.setColor(Color.WHITE);

            } else if (direction == 2) {
                canvas.drawLine(UI_SCREEN_WIDTH - 60, UI_SCREEN_HEIGHT - 50, UI_SCREEN_WIDTH - 40,
                        UI_SCREEN_HEIGHT - 50, paint);
                canvas.drawLine(UI_SCREEN_WIDTH - 50, UI_SCREEN_HEIGHT - 60, UI_SCREEN_WIDTH - 50,
                        UI_SCREEN_HEIGHT - 40, paint);
                paint.setColor(Color.WHITE);
            } else if (direction == 3) {
                canvas.drawLine(40, UI_SCREEN_HEIGHT - 50, 60, UI_SCREEN_HEIGHT - 50, paint);
                canvas.drawLine(50, UI_SCREEN_HEIGHT - 60, 50, UI_SCREEN_HEIGHT - 40, paint);
                paint.setColor(Color.WHITE);

            } else if (direction == 4) {
                canvas.drawLine(UI_SCREEN_WIDTH / 2 - 10, UI_SCREEN_HEIGHT / 2,
                        UI_SCREEN_WIDTH / 2 + 10, UI_SCREEN_HEIGHT / 2, paint);
                canvas.drawLine(UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 - 10,
                        UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 + 10, paint);
                paint.setColor(Color.WHITE);
            } else {

            }
            // canvas.drawText(getResources().getString(R.string.
            // screen_calibration_content),
            // UI_SCREEN_WIDTH / 2 - 50, UI_SCREEN_HEIGHT / 2, paint);
            super.onDraw(canvas);
        }
    }
}
综上所述 这篇文章只是一篇错了很久的文章。 不过最近可能会有一块板子调试下 我会用jni写一个版本出来用用。 jni的代码早就写了一份了 试验一下如果是好的 我会更新这个文章。

 

 

 

先把jni文件帖出来吧:

#include "jni.h"
#include "JNIHelp.h"
#include <utils/Log.h>
#include "ts_calibrate.h"
#include <android_runtime/AndroidRuntime.h>

static calibration cal;

static jint calibrate(JNIEnv * env, jobject jobj)
{
    LOGD("calibrate!/n");
    return calibrate_main(cal);
}

static jint getScreenWidth(JNIEnv * env, jobject job)
{
    LOGD("getXres!/n");
    return getXres();
}

static jint getScreenHeight(JNIEnv * env, jobject job)
{
    LOGD("getYres!/n");
    return getYres();
}

static void getXY(JNIEnv * env, jobject job, jint index, int x1, int y1, int x, int y)
{
    LOGD("getXY!/n");
    get_sample(cal, index, x1, y1, x, y);
}

static const char *classPathName = "com/test/TestTsLib";

static JNINativeMethod methods[] = {
  {"getScreenWidth", "()I", (void*)getScreenWidth },
  {"getScreenHeight", "()I", (void*)getScreenHeight },
  {"getXY", "(IIIII)V", (void*)getXY },
  {"calibrate", "()I", (void*)calibrate },
};

/*
 * Register several native methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
 * Register native methods for all classes we know about.
 *
 * returns JNI_TRUE on success.
 */
static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }

  return JNI_TRUE;
}

typedef union {
    JNIEnv* env;
    void* venv;
} UnionJNIEnvToVoid;

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    UnionJNIEnvToVoid uenv;
    uenv.venv = NULL;
    jint result = -1;
    JNIEnv* env = NULL;
   
    LOGI("JNI_OnLoad");

    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed");
        goto bail;
    }
    env = uenv.env;

    if (registerNatives(env) != JNI_TRUE) {
        LOGE("ERROR: registerNatives failed");
        goto bail;
    }
   
    result = JNI_VERSION_1_4;
   
bail:
    return result;
}

阅读更多
个人分类: Android(转)
想对作者说点什么? 我来说一句

通讯变频器LRC校验触摸屏

2018年05月22日 5KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭