天天看點

序列槽 多個activity 安卓_Android開發之序列槽程式設計原理和實作方式

提到序列槽程式設計,就不得不提到JNI,不得不提到JavaAPI中的檔案描述符類:FileDescriptor。下面我分别對JNI、FileDescriptor以及序列槽的一些知識點和實作的源碼進行分析說明。這裡主要是參考了開源項目android-serialport-api。

序列槽程式設計需要了解的基本知識點:對于序列槽程式設計,我們隻需對序列槽進行一系列的設定,然後打開序列槽,這些操作我們可以參考序列槽調試助手的源碼進行學習。在Java中如果要實作序列槽的讀寫功能隻需操作檔案裝置類:FileDescriptor即可,其他的事都由驅動來完成不用多管!當然,你想了解,那就得看驅動代碼了。這裡并不打算對驅動進行說明,隻初略闡述應用層的實作方式。

(一)JNI:

關于JNI的文章網上有很多,不再多做解釋,想詳細了解的朋友可以檢視雲中漫步的技術文章,寫得很好,分析也很全面,那麼在這篇拙文中我強調3點:

1、如何将編譯好的SO檔案打包到APK中?(方法很簡單,直接在工程目錄下建立檔案夾 libs/armeabi,将SO檔案Copy到此目錄即可)

2、命名要注意的地方?(在編譯好的SO檔案中,将檔案重命名為:libfilename.so即可。其中filename.so是編譯好後生成的檔案)

3、MakeFile檔案的編寫(不用多說,可以直接參考package/apps目錄下用到JNI的相關項目寫法)

這是關鍵的代碼:

int fd;

speed_t speed;

jobject mFileDescriptor;

{

speed = getBaudrate(baudrate);

if (speed == -1) {

LOGE("Invalid baudrate");

return NULL;

}

}

{

jboolean iscopy;

const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);

LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);

fd = open(path_utf, O_RDWR | flags);

LOGD("open() fd = %d", fd);

(*env)->ReleaseStringUTFChars(env, path, path_utf);

if (fd == -1)

{

LOGE("Cannot open port");

return NULL;

}

}

{

struct termios cfg;

LOGD("Configuring serial port");

if (tcgetattr(fd, &cfg))

{

LOGE("tcgetattr() failed");

close(fd);

return NULL;

}

cfmakeraw(&cfg);

cfsetispeed(&cfg, speed);

cfsetospeed(&cfg, speed);

if (tcsetattr(fd, TCSANOW, &cfg))

{

LOGE("tcsetattr() failed");

close(fd);

return NULL;

}

}

(二)FileDescritor:

檔案描述符類的執行個體用作與基礎機器有關的某種結構的不透明句柄,該結構表示開放檔案、開放套接字或者位元組的另一個源或接收者。檔案描述符的主要實際用途是建立一個包含該結構的FileInputStream 或FileOutputStream。這是API的描述,不太好了解,其實可簡單的了解為:FileDescritor就是對一個檔案進行讀寫。

(三)實作序列槽通信細節

1) 建工程:SerialDemo包名:org.winplus.serial,并在工程目錄下建立jni和libs兩個檔案夾和一個org.winplus.serial.utils,如下圖:

2) 建立一個類:SerialPortFinder,添加如下代碼:

package org.winplus.serial.utils;

import java.io.File;

import java.io.FileReader;

import java.io.IOException;

import java.io.LineNumberReader;

import java.util.Iterator;

import java.util.Vector;

import android.util.Log;

public class SerialPortFinder {

private static final String TAG = "SerialPort";

private Vector mDrivers = null;

public class Driver {

public Driver(String name, String root) {

mDriverName = name;

mDeviceRoot = root;

}

private String mDriverName;

private String mDeviceRoot;

Vector mDevices = null;

public Vector getDevices() {

if (mDevices == null) {

mDevices = new Vector();

File dev = new File("/dev");

File[] files = dev.listFiles();

int i;

for (i = 0; i < files.length; i++) {

if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {

Log.d(TAG, "Found new device: " + files[i]);

mDevices.add(files[i]);

}

}

}

return mDevices;

}

public String getName() {

return mDriverName;

}

}

Vector getDrivers() throws IOException {

if (mDrivers == null) {

mDrivers = new Vector();

LineNumberReader r = new LineNumberReader(new FileReader(

"/proc/tty/drivers"));

String l;

while ((l = r.readLine()) != null) {

// Issue 3:

// Since driver name may contain spaces, we do not extract

// driver name with split()

String drivername = l.substring(0, 0x15).trim();

String[] w = l.split(" +");

if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {

Log.d(TAG, "Found new driver " + drivername + " on "

+ w[w.length - 4]);

mDrivers.add(new Driver(drivername, w[w.length - 4]));

}

}

r.close();

}

return mDrivers;

}

public String[] getAllDevices() {

Vector devices = new Vector();

// Parse each driver

Iterator itdriv;

try {

itdriv = getDrivers().iterator();

while (itdriv.hasNext()) {

Driver driver = itdriv.next();

Iterator itdev = driver.getDevices().iterator();

while (itdev.hasNext()) {

String device = itdev.next().getName();

String value = String.format("%s (%s)", device,

driver.getName());

devices.add(value);

}

}

} catch (IOException e) {

e.printStackTrace();

}

return devices.toArray(new String[devices.size()]);

}

public String[] getAllDevicesPath() {

Vector devices = new Vector();

// Parse each driver

Iterator itdriv;

try {

itdriv = getDrivers().iterator();

while (itdriv.hasNext()) {

Driver driver = itdriv.next();

Iterator itdev = driver.getDevices().iterator();

while (itdev.hasNext()) {

String device = itdev.next().getAbsolutePath();

devices.add(device);

}

}

} catch (IOException e) {

e.printStackTrace();

}

return devices.toArray(new String[devices.size()]);

}

}

上面這個類在“android-serialport-api序列槽工具測試随筆”中有詳細的說明,我就不多說了。

3)建立SerialPort類,這個類主要用來加載SO檔案,通過JNI的方式打開關閉序列槽

package org.winplus.serial.utils;

import java.io.File;

import java.io.FileDescriptor;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import android.util.Log;

public class SerialPort {

private static final String TAG = "SerialPort";

private FileDescriptor mFd;

private FileInputStream mFileInputStream;

private FileOutputStream mFileOutputStream;

public SerialPort(File device, int baudrate, int flags)

throws SecurityException, IOException {

if (!device.canRead() || !device.canWrite()) {

try {

Process su;

su = Runtime.getRuntime().exec("/system/bin/su");

String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"

+ "exit\n";

su.getOutputStream().write(cmd.getBytes());

if ((su.waitFor() != 0) || !device.canRead()

|| !device.canWrite()) {

throw new SecurityException();

}

} catch (Exception e) {

e.printStackTrace();

throw new SecurityException();

}

}

mFd = open(device.getAbsolutePath(), baudrate, flags);

if (mFd == null) {

Log.e(TAG, "native open returns null");

throw new IOException();

}

mFileInputStream = new FileInputStream(mFd);

mFileOutputStream = new FileOutputStream(mFd);

}

// Getters and setters

public InputStream getInputStream() {

return mFileInputStream;

}

public OutputStream getOutputStream() {

return mFileOutputStream;

}

// JNI

private native static FileDescriptor open(String path, int baudrate,

int flags);

public native void close();

static {

System.loadLibrary("serial_port");

}

}

4) 建立一個MyApplication 繼承android.app.Application,用來對序列槽進行初始化和關閉序列槽

package org.winplus.serial;

import java.io.File;

import java.io.IOException;

import java.security.InvalidParameterException;

import org.winplus.serial.utils.SerialPort;

import org.winplus.serial.utils.SerialPortFinder;

import android.content.SharedPreferences;

public class MyApplication extends android.app.Application {

public SerialPortFinder mSerialPortFinder = new SerialPortFinder();

private SerialPort mSerialPort = null;

public SerialPort getSerialPort() throws SecurityException, IOException, InvalidParameterException {

if (mSerialPort == null) {

SharedPreferences sp = getSharedPreferences("android_serialport_api.sample_preferences", MODE_PRIVATE);

String path = sp.getString("DEVICE", "");

int baudrate = Integer.decode(sp.getString("BAUDRATE", "-1"));

if ( (path.length() == 0) || (baudrate == -1)) {

throw new InvalidParameterException();

}

mSerialPort = new SerialPort(new File(path), baudrate, 0);

}

return mSerialPort;

}

public void closeSerialPort() {

if (mSerialPort != null) {

mSerialPort.close();

mSerialPort = null;

}

}

}

5) 建立一個繼承抽象的Activity類,主要用于讀取序列槽的資訊

package org.winplus.serial;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.security.InvalidParameterException;

import org.winplus.serial.utils.SerialPort;

import android.app.Activity;

import android.app.AlertDialog;

import android.content.DialogInterface;

import android.content.DialogInterface.OnClickListener;

import android.os.Bundle;

public abstract class SerialPortActivity extends Activity {

protected MyApplication mApplication;

protected SerialPort mSerialPort;

protected OutputStream mOutputStream;

private InputStream mInputStream;

private ReadThread mReadThread;

private class ReadThread extends Thread {

@Override

public void run() {

super.run();

while (!isInterrupted()) {

int size;

try {

byte[] buffer = new byte[64];

if (mInputStream == null)

return;

size = mInputStream.read(buffer);

if (size > 0) {

onDataReceived(buffer, size);

}

} catch (IOException e) {

e.printStackTrace();

return;

}

}

}

}

private void DisplayError(int resourceId) {

AlertDialog.Builder b = new AlertDialog.Builder(this);

b.setTitle("Error");

b.setMessage(resourceId);

b.setPositiveButton("OK", new OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

SerialPortActivity.this.finish();

}

});

b.show();

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mApplication = (MyApplication) getApplication();

try {

mSerialPort = mApplication.getSerialPort();

mOutputStream = mSerialPort.getOutputStream();

mInputStream = mSerialPort.getInputStream();

mReadThread = new ReadThread();

mReadThread.start();

} catch (SecurityException e) {

DisplayError(R.string.error_security);

} catch (IOException e) {

DisplayError(R.string.error_unknown);

} catch (InvalidParameterException e) {

DisplayError(R.string.error_configuration);

}

}

protected abstract void onDataReceived(final byte[] buffer, final int size);

@Override

protected void onDestroy() {

if (mReadThread != null)

mReadThread.interrupt();

mApplication.closeSerialPort();

mSerialPort = null;

super.onDestroy();

}

}

6)編寫string.xml 以及baudrates.xml檔案

在string.xml檔案中添加:

Please configure your serial port first.

You do not have read/write permission to the serial port.

The serial port can not be opened for an unknown reason.

在baudrates.xml檔案中添加

50

75

110

134

150

200

300

600

1200

1800

2400

4800

9600

19200

38400

57600

115200

230400

460800

500000

576000

921600

1000000

1152000

1500000

2000000

2500000

3000000

3500000

4000000

50

75

110

134

150

200

300

600

1200

1800

2400

4800

9600

19200

38400

57600

115200

230400

460800

500000

576000

921600

1000000

1152000

1500000

2000000

2500000

3000000

3500000

4000000

7)開始編寫界面了:在main.xml布局檔案中添加兩個編輯框,一個用來發送指令,一個用來接收指令:

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical" >

android:id="@+id/EditTextReception"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:layout_weight="1"

android:gravity="top"

android:hint="Reception"

android:isScrollContainer="true"

android:scrollbarStyle="insideOverlay" >

android:id="@+id/EditTextEmission"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:hint="Emission"

android:lines="1" >

8) SerialDemoActivity類的實作:

package org.winplus.serial;

import java.io.IOException;

import android.os.Bundle;

import android.view.KeyEvent;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.TextView.OnEditorActionListener;

public class SerialDemoActivity extends SerialPortActivity{

EditText mReception;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

// setTitle("Loopback test");

mReception = (EditText) findViewById(R.id.EditTextReception);

EditText Emission = (EditText) findViewById(R.id.EditTextEmission);

Emission.setOnEditorActionListener(new OnEditorActionListener() {

public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

int i;

CharSequence t = v.getText();

char[] text = new char[t.length()];

for (i=0; i

text[i] = t.charAt(i);

}

try {

mOutputStream.write(new String(text).getBytes());

mOutputStream.write('\n');

} catch (IOException e) {

e.printStackTrace();

}

return false;

}

});

}

@Override

protected void onDataReceived(final byte[] buffer, final int size) {

runOnUiThread(new Runnable() {

public void run() {

if (mReception != null) {

mReception.append(new String(buffer, 0, size));

}

}

});

}

}

寫到這裡,代碼基本上寫完了。下面就是要實作JNI層的功能了,要實作JNI,必須首先生成頭檔案,頭檔案的生成方式也很簡單, 我們編譯工程,在終端輸入 javah org.winplus.serial.utils.SerialPort 則會生成頭檔案:org_winplus_serial_utils_SerialPort.h,這個頭檔案的名字可以随意命名。我們将它命名為:SerialPort.h拷貝到建立的目錄jni中,建立SerialPort.c 檔案,這兩個檔案的代碼就不貼出來了。直接到上傳的代碼中看吧。

(四)序列槽的應用,可實作掃描頭,指紋識别等外圍USB轉序列槽的特色應用

還蠻繁瑣的,以上隻是對開源項目android-serialport-api 進行精簡想了解此項目請點選此處!就這樣吧,晚了準備見周公去!