【Android应用开发】-(19)Android 串口编程原理和实现方式

分类: Android 11955人阅读 评论(81) 收藏 举报 android Android exception Exception jni JNI string String 编程

目录(?)[+]

提到串口编程,就不得不提到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的相关项目写法)

这是关键的代码:

  1. <spanstyle="font-size:18px;">intfd;
  2. speed_tspeed;
  3. jobjectmFileDescriptor;
  4. /*Checkarguments*/
  5. {
  6. speed=getBaudrate(baudrate);
  7. if(speed==-1){
  8. /*TODO:throwanexception*/
  9. LOGE("Invalidbaudrate");
  10. returnNULL;
  11. }
  12. }
  13. /*Openingdevice*/
  14. {
  15. jbooleaniscopy;
  16. constchar*path_utf=(*env)->GetStringUTFChars(env,path,&iscopy);
  17. LOGD("Openingserialport%swithflags0x%x",path_utf,O_RDWR|flags);
  18. fd=open(path_utf,O_RDWR|flags);
  19. LOGD("open()fd=%d",fd);
  20. (*env)->ReleaseStringUTFChars(env,path,path_utf);
  21. if(fd==-1)
  22. {
  23. /*Throwanexception*/
  24. LOGE("Cannotopenport");
  25. /*TODO:throwanexception*/
  26. returnNULL;
  27. }
  28. }
  29. /*Configuredevice*/
  30. {
  31. structtermioscfg;
  32. LOGD("Configuringserialport");
  33. if(tcgetattr(fd,&cfg))
  34. {
  35. LOGE("tcgetattr()failed");
  36. close(fd);
  37. /*TODO:throwanexception*/
  38. returnNULL;
  39. }
  40. cfmakeraw(&cfg);
  41. cfsetispeed(&cfg,speed);
  42. cfsetospeed(&cfg,speed);
  43. if(tcsetattr(fd,TCSANOW,&cfg))
  44. {
  45. LOGE("tcsetattr()failed");
  46. close(fd);
  47. /*TODO:throwanexception*/
  48. returnNULL;
  49. }
  50. }
  51. </span>

(二)FileDescritor:

文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的FileInputStreamFileOutputStream。这是API的描述,不太好理解,其实可简单的理解为:FileDescritor就是对一个文件进行读写。

(三)实现串口通信细节

1) 建工程:SerialDemo包名:org.winplus.serial,并在工程目录下新建jni和libs两个文件夹和一个org.winplus.serial.utils,如下图:

【Android应用开发】-(19)Android 串口编程原理和实现方式_第1张图片

2) 新建一个类:SerialPortFinder,添加如下代码:

  1. <spanstyle="font-size:18px;">packageorg.winplus.serial.utils;
  2. importjava.io.File;
  3. importjava.io.FileReader;
  4. importjava.io.IOException;
  5. importjava.io.LineNumberReader;
  6. importjava.util.Iterator;
  7. importjava.util.Vector;
  8. importandroid.util.Log;
  9. publicclassSerialPortFinder{
  10. privatestaticfinalStringTAG="SerialPort";
  11. privateVector<Driver>mDrivers=null;
  12. publicclassDriver{
  13. publicDriver(Stringname,Stringroot){
  14. mDriverName=name;
  15. mDeviceRoot=root;
  16. }
  17. privateStringmDriverName;
  18. privateStringmDeviceRoot;
  19. Vector<File>mDevices=null;
  20. publicVector<File>getDevices(){
  21. if(mDevices==null){
  22. mDevices=newVector<File>();
  23. Filedev=newFile("/dev");
  24. File[]files=dev.listFiles();
  25. inti;
  26. for(i=0;i<files.length;i++){
  27. if(files[i].getAbsolutePath().startsWith(mDeviceRoot)){
  28. Log.d(TAG,"Foundnewdevice:"+files[i]);
  29. mDevices.add(files[i]);
  30. }
  31. }
  32. }
  33. returnmDevices;
  34. }
  35. publicStringgetName(){
  36. returnmDriverName;
  37. }
  38. }
  39. Vector<Driver>getDrivers()throwsIOException{
  40. if(mDrivers==null){
  41. mDrivers=newVector<Driver>();
  42. LineNumberReaderr=newLineNumberReader(newFileReader(
  43. "/proc/tty/drivers"));
  44. Stringl;
  45. while((l=r.readLine())!=null){
  46. //Issue3:
  47. //Sincedrivernamemaycontainspaces,wedonotextract
  48. //drivernamewithsplit()
  49. Stringdrivername=l.substring(0,0x15).trim();
  50. String[]w=l.split("+");
  51. if((w.length>=5)&&(w[w.length-1].equals("serial"))){
  52. Log.d(TAG,"Foundnewdriver"+drivername+"on"
  53. +w[w.length-4]);
  54. mDrivers.add(newDriver(drivername,w[w.length-4]));
  55. }
  56. }
  57. r.close();
  58. }
  59. returnmDrivers;
  60. }
  61. publicString[]getAllDevices(){
  62. Vector<String>devices=newVector<String>();
  63. //Parseeachdriver
  64. Iterator<Driver>itdriv;
  65. try{
  66. itdriv=getDrivers().iterator();
  67. while(itdriv.hasNext()){
  68. Driverdriver=itdriv.next();
  69. Iterator<File>itdev=driver.getDevices().iterator();
  70. while(itdev.hasNext()){
  71. Stringdevice=itdev.next().getName();
  72. Stringvalue=String.format("%s(%s)",device,
  73. driver.getName());
  74. devices.add(value);
  75. }
  76. }
  77. }catch(IOExceptione){
  78. e.printStackTrace();
  79. }
  80. returndevices.toArray(newString[devices.size()]);
  81. }
  82. publicString[]getAllDevicesPath(){
  83. Vector<String>devices=newVector<String>();
  84. //Parseeachdriver
  85. Iterator<Driver>itdriv;
  86. try{
  87. itdriv=getDrivers().iterator();
  88. while(itdriv.hasNext()){
  89. Driverdriver=itdriv.next();
  90. Iterator<File>itdev=driver.getDevices().iterator();
  91. while(itdev.hasNext()){
  92. Stringdevice=itdev.next().getAbsolutePath();
  93. devices.add(device);
  94. }
  95. }
  96. }catch(IOExceptione){
  97. e.printStackTrace();
  98. }
  99. returndevices.toArray(newString[devices.size()]);
  100. }
  101. }
  102. </span>

上面这个类在“android-serialport-api串口工具测试随笔”中有详细的说明,我就不多说了。

3)新建SerialPort类,这个类主要用来加载SO文件,通过JNI的方式打开关闭串口

  1. <spanstyle="font-size:18px;">packageorg.winplus.serial.utils;
  2. importjava.io.File;
  3. importjava.io.FileDescriptor;
  4. importjava.io.FileInputStream;
  5. importjava.io.FileOutputStream;
  6. importjava.io.IOException;
  7. importjava.io.InputStream;
  8. importjava.io.OutputStream;
  9. importandroid.util.Log;
  10. publicclassSerialPort{
  11. privatestaticfinalStringTAG="SerialPort";
  12. /*
  13. *DonotremoveorrenamethefieldmFd:itisusedbynativemethod
  14. *close();
  15. */
  16. privateFileDescriptormFd;
  17. privateFileInputStreammFileInputStream;
  18. privateFileOutputStreammFileOutputStream;
  19. publicSerialPort(Filedevice,intbaudrate,intflags)
  20. throwsSecurityException,IOException{
  21. /*Checkaccesspermission*/
  22. if(!device.canRead()||!device.canWrite()){
  23. try{
  24. /*Missingread/writepermission,tryingtochmodthefile*/
  25. Processsu;
  26. su=Runtime.getRuntime().exec("/system/bin/su");
  27. Stringcmd="chmod666"+device.getAbsolutePath()+"\n"
  28. +"exit\n";
  29. su.getOutputStream().write(cmd.getBytes());
  30. if((su.waitFor()!=0)||!device.canRead()
  31. ||!device.canWrite()){
  32. thrownewSecurityException();
  33. }
  34. }catch(Exceptione){
  35. e.printStackTrace();
  36. thrownewSecurityException();
  37. }
  38. }
  39. mFd=open(device.getAbsolutePath(),baudrate,flags);
  40. if(mFd==null){
  41. Log.e(TAG,"nativeopenreturnsnull");
  42. thrownewIOException();
  43. }
  44. mFileInputStream=newFileInputStream(mFd);
  45. mFileOutputStream=newFileOutputStream(mFd);
  46. }
  47. //Gettersandsetters
  48. publicInputStreamgetInputStream(){
  49. returnmFileInputStream;
  50. }
  51. publicOutputStreamgetOutputStream(){
  52. returnmFileOutputStream;
  53. }
  54. //JNI
  55. privatenativestaticFileDescriptoropen(Stringpath,intbaudrate,
  56. intflags);
  57. publicnativevoidclose();
  58. static{
  59. System.loadLibrary("serial_port");
  60. }
  61. }
  62. </span>

4) 新建一个MyApplication 继承android.app.Application,用来对串口进行初始化和关闭串口
  1. <spanstyle="font-size:18px;">packageorg.winplus.serial;
  2. importjava.io.File;
  3. importjava.io.IOException;
  4. importjava.security.InvalidParameterException;
  5. importorg.winplus.serial.utils.SerialPort;
  6. importorg.winplus.serial.utils.SerialPortFinder;
  7. importandroid.content.SharedPreferences;
  8. publicclassMyApplicationextendsandroid.app.Application{
  9. publicSerialPortFindermSerialPortFinder=newSerialPortFinder();
  10. privateSerialPortmSerialPort=null;
  11. publicSerialPortgetSerialPort()throwsSecurityException,IOException,InvalidParameterException{
  12. if(mSerialPort==null){
  13. /*Readserialportparameters*/
  14. SharedPreferencessp=getSharedPreferences("android_serialport_api.sample_preferences",MODE_PRIVATE);
  15. Stringpath=sp.getString("DEVICE","");
  16. intbaudrate=Integer.decode(sp.getString("BAUDRATE","-1"));
  17. /*Checkparameters*/
  18. if((path.length()==0)||(baudrate==-1)){
  19. thrownewInvalidParameterException();
  20. }
  21. /*Opentheserialport*/
  22. mSerialPort=newSerialPort(newFile(path),baudrate,0);
  23. }
  24. returnmSerialPort;
  25. }
  26. publicvoidcloseSerialPort(){
  27. if(mSerialPort!=null){
  28. mSerialPort.close();
  29. mSerialPort=null;
  30. }
  31. }
  32. }
  33. </span>

5) 新建一个继承抽象的Activity类,主要用于读取串口的信息
  1. <spanstyle="font-size:18px;">packageorg.winplus.serial;
  2. importjava.io.IOException;
  3. importjava.io.InputStream;
  4. importjava.io.OutputStream;
  5. importjava.security.InvalidParameterException;
  6. importorg.winplus.serial.utils.SerialPort;
  7. importandroid.app.Activity;
  8. importandroid.app.AlertDialog;
  9. importandroid.content.DialogInterface;
  10. importandroid.content.DialogInterface.OnClickListener;
  11. importandroid.os.Bundle;
  12. publicabstractclassSerialPortActivityextendsActivity{
  13. protectedMyApplicationmApplication;
  14. protectedSerialPortmSerialPort;
  15. protectedOutputStreammOutputStream;
  16. privateInputStreammInputStream;
  17. privateReadThreadmReadThread;
  18. privateclassReadThreadextendsThread{
  19. @Override
  20. publicvoidrun(){
  21. super.run();
  22. while(!isInterrupted()){
  23. intsize;
  24. try{
  25. byte[]buffer=newbyte[64];
  26. if(mInputStream==null)
  27. return;
  28. /**
  29. *这里的read要尤其注意,它会一直等待数据,等到天荒地老,海枯石烂。如果要判断是否接受完成,只有设置结束标识,或作其他特殊的处理。
  30. */
  31. size=mInputStream.read(buffer);
  32. if(size>0){
  33. onDataReceived(buffer,size);
  34. }
  35. }catch(IOExceptione){
  36. e.printStackTrace();
  37. return;
  38. }
  39. }
  40. }
  41. }
  42. privatevoidDisplayError(intresourceId){
  43. AlertDialog.Builderb=newAlertDialog.Builder(this);
  44. b.setTitle("Error");
  45. b.setMessage(resourceId);
  46. b.setPositiveButton("OK",newOnClickListener(){
  47. publicvoidonClick(DialogInterfacedialog,intwhich){
  48. SerialPortActivity.this.finish();
  49. }
  50. });
  51. b.show();
  52. }
  53. @Override
  54. protectedvoidonCreate(BundlesavedInstanceState){
  55. super.onCreate(savedInstanceState);
  56. mApplication=(MyApplication)getApplication();
  57. try{
  58. mSerialPort=mApplication.getSerialPort();
  59. mOutputStream=mSerialPort.getOutputStream();
  60. mInputStream=mSerialPort.getInputStream();
  61. /*Createareceivingthread*/
  62. mReadThread=newReadThread();
  63. mReadThread.start();
  64. }catch(SecurityExceptione){
  65. DisplayError(R.string.error_security);
  66. }catch(IOExceptione){
  67. DisplayError(R.string.error_unknown);
  68. }catch(InvalidParameterExceptione){
  69. DisplayError(R.string.error_configuration);
  70. }
  71. }
  72. protectedabstractvoidonDataReceived(finalbyte[]buffer,finalintsize);
  73. @Override
  74. protectedvoidonDestroy(){
  75. if(mReadThread!=null)
  76. mReadThread.interrupt();
  77. mApplication.closeSerialPort();
  78. mSerialPort=null;
  79. super.onDestroy();
  80. }
  81. }
  82. </span>

6)编写string.xml 以及baudrates.xml文件

在string.xml文件中添加:
  1. <spanstyle="font-size:18px;"><stringname="error_configuration">Pleaseconfigureyourserialportfirst.</string>
  2. <stringname="error_security">Youdonothaveread/writepermissiontotheserialport.</string>
  3. <stringname="error_unknown">Theserialportcannotbeopenedforanunknownreason.</string>
  4. </span>

在baudrates.xml文件中添加
  1. <spanstyle="font-size:18px;"><?xmlversion="1.0"encoding="utf-8"?>
  2. <resources>
  3. <string-arrayname="baudrates_name">
  4. <item>50</item>
  5. <item>75</item>
  6. <item>110</item>
  7. <item>134</item>
  8. <item>150</item>
  9. <item>200</item>
  10. <item>300</item>
  11. <item>600</item>
  12. <item>1200</item>
  13. <item>1800</item>
  14. <item>2400</item>
  15. <item>4800</item>
  16. <item>9600</item>
  17. <item>19200</item>
  18. <item>38400</item>
  19. <item>57600</item>
  20. <item>115200</item>
  21. <item>230400</item>
  22. <item>460800</item>
  23. <item>500000</item>
  24. <item>576000</item>
  25. <item>921600</item>
  26. <item>1000000</item>
  27. <item>1152000</item>
  28. <item>1500000</item>
  29. <item>2000000</item>
  30. <item>2500000</item>
  31. <item>3000000</item>
  32. <item>3500000</item>
  33. <item>4000000</item>
  34. </string-array>
  35. <string-arrayname="baudrates_value">
  36. <item>50</item>
  37. <item>75</item>
  38. <item>110</item>
  39. <item>134</item>
  40. <item>150</item>
  41. <item>200</item>
  42. <item>300</item>
  43. <item>600</item>
  44. <item>1200</item>
  45. <item>1800</item>
  46. <item>2400</item>
  47. <item>4800</item>
  48. <item>9600</item>
  49. <item>19200</item>
  50. <item>38400</item>
  51. <item>57600</item>
  52. <item>115200</item>
  53. <item>230400</item>
  54. <item>460800</item>
  55. <item>500000</item>
  56. <item>576000</item>
  57. <item>921600</item>
  58. <item>1000000</item>
  59. <item>1152000</item>
  60. <item>1500000</item>
  61. <item>2000000</item>
  62. <item>2500000</item>
  63. <item>3000000</item>
  64. <item>3500000</item>
  65. <item>4000000</item>
  66. </string-array>
  67. </resources>
  68. </span>

7)开始编写界面了:在main.xml布局文件中添加两个编辑框,一个用来发送命令,一个用来接收命令:
  1. <spanstyle="font-size:18px;"><?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical">
  6. <EditText
  7. android:id="@+id/EditTextReception"
  8. android:layout_width="fill_parent"
  9. android:layout_height="fill_parent"
  10. android:layout_weight="1"
  11. android:gravity="top"
  12. android:hint="Reception"
  13. android:isScrollContainer="true"
  14. android:scrollbarStyle="insideOverlay">
  15. </EditText>
  16. <EditText
  17. android:id="@+id/EditTextEmission"
  18. android:layout_width="fill_parent"
  19. android:layout_height="wrap_content"
  20. android:hint="Emission"
  21. android:lines="1">
  22. </EditText>
  23. </LinearLayout>
  24. </span>

8) SerialDemoActivity类的实现:

  1. <spanstyle="font-size:18px;">packageorg.winplus.serial;
  2. importjava.io.IOException;
  3. importandroid.os.Bundle;
  4. importandroid.view.KeyEvent;
  5. importandroid.widget.EditText;
  6. importandroid.widget.TextView;
  7. importandroid.widget.TextView.OnEditorActionListener;
  8. publicclassSerialDemoActivityextendsSerialPortActivity{
  9. EditTextmReception;
  10. @Override
  11. protectedvoidonCreate(BundlesavedInstanceState){
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.main);
  14. //setTitle("Loopbacktest");
  15. mReception=(EditText)findViewById(R.id.EditTextReception);
  16. EditTextEmission=(EditText)findViewById(R.id.EditTextEmission);
  17. Emission.setOnEditorActionListener(newOnEditorActionListener(){
  18. publicbooleanonEditorAction(TextViewv,intactionId,KeyEventevent){
  19. inti;
  20. CharSequencet=v.getText();
  21. char[]text=newchar[t.length()];
  22. for(i=0;i<t.length();i++){
  23. text[i]=t.charAt(i);
  24. }
  25. try{
  26. mOutputStream.write(newString(text).getBytes());
  27. mOutputStream.write('\n');
  28. }catch(IOExceptione){
  29. e.printStackTrace();
  30. }
  31. returnfalse;
  32. }
  33. });
  34. }
  35. @Override
  36. protectedvoidonDataReceived(finalbyte[]buffer,finalintsize){
  37. runOnUiThread(newRunnable(){
  38. publicvoidrun(){
  39. if(mReception!=null){
  40. mReception.append(newString(buffer,0,size));
  41. }
  42. }
  43. });
  44. }
  45. }
  46. </span>

写到这里,代码基本上写完了。下面就是要实现JNI层的功能了,要实现JNI,必须首先生成头文件,头文件的生成方式也很简单, 我们编译工程,在终端输入 javah org.winplus.serial.utils.SerialPort 则会生成头文件:org_winplus_serial_utils_SerialPort.h,这个头文件的名字可以随意命名。我们将它命名为:SerialPort.h拷贝到新建的目录jni中,新建SerialPort.c 文件,这两个文件的代码就不贴出来了。直接到上传的代码中看吧。

【Android应用开发】-(19)Android 串口编程原理和实现方式_第2张图片

(四)串口的应用,可实现扫描头,指纹识别等外围USB转串口的特色应用。

原创文章,转载请注明出处:http://www.blog.csdn.net/tangcheng_ok

还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!


更多相关文章

  1. Android 打开相机、相册获取图片文件,支持Android 9.0系统
  2. 解决 android 在sd卡新建文件后需要重启才能找到
  3. Android遍历文件Listfile返回值为null问题解决方法适用Android8.
  4. Android上传文件至PHP服务器
  5. 【知识点】android代码中设置margin
  6. Gradle 修改生成apk时的文件名
  7. Android 实现apk文件下载并自动安装
  8. Android客户端上传文件到服务器端
  9. Android蓝牙通信代码

随机推荐

  1. android 深度搜索笔记一
  2. Android studio使用心得(九)------如何设
  3. 在omap3530上移植成功Android的ALSA声卡
  4. Android(安卓)编年史
  5. Android(安卓)Studio检测不到模拟器/真机
  6. framebuffer (2)
  7. Android之Activity之间传递对象
  8. Android 手机上获取物理唯一标识码
  9. Android感应检测Sensor(简单介绍)
  10. Android(安卓)AppLink功能实现,原理和干货