android模拟器与pc间的串口通信

2 篇文章 0 订阅
1 篇文章 0 订阅

  在win7下模拟APP的串口通信。

  开发环境为win7,测试环境为win7+android自带模拟器。


开发步骤:

1.安装vspd软件,创建一对模拟串口对。一个负责接收,另一个负责发送。

2.使用串口调试助手。用来对发送串口进行设置、发送和接收数据。

3.使用控制台启动模拟器,并启用串口。其中COM2为APP使用的串口。Nexus_S_API_22为模拟器名字。

emulator @Nexus_S_API_22   -qemu -serial COM2

4.写测试程序进行测试。

SerialPort.c文件

/*
 * Copyright 2009-2011 Cedric Priscal
 *
 * 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.
 */

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>

#include "SerialPort.h"

#include "android/log.h"
static const char *TAG = "serial_port";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

static speed_t getBaudrate(jint baudrate) {
	switch (baudrate) {
	case 0:
		return B0;
	case 50:
		return B50;
	case 75:
		return B75;
	case 110:
		return B110;
	case 134:
		return B134;
	case 150:
		return B150;
	case 200:
		return B200;
	case 300:
		return B300;
	case 600:
		return B600;
	case 1200:
		return B1200;
	case 1800:
		return B1800;
	case 2400:
		return B2400;
	case 4800:
		return B4800;
	case 9600:
		return B9600;
	case 19200:
		return B19200;
	case 38400:
		return B38400;
	case 57600:
		return B57600;
	case 115200:
		return B115200;
	case 230400:
		return B230400;
	case 460800:
		return B460800;
	case 500000:
		return B500000;
	case 576000:
		return B576000;
	case 921600:
		return B921600;
	case 1000000:
		return B1000000;
	case 1152000:
		return B1152000;
	case 1500000:
		return B1500000;
	case 2000000:
		return B2000000;
	case 2500000:
		return B2500000;
	case 3000000:
		return B3000000;
	case 3500000:
		return B3500000;
	case 4000000:
		return B4000000;
	default:
		return -1;
	}
}

/*
 * Class:     com_luoye_serialport_SerialPort
 * Method:    open
 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
 */JNIEXPORT jobject JNICALL Java_com_luoye_serialport_SerialPort_open(
		JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) {
	int fd;
	speed_t speed;
	jobject mFileDescriptor;

	/* Check arguments */
	{
		speed = getBaudrate(baudrate);
		if (speed == -1) {
			/* TODO: throw an exception */
			LOGE("Invalid baudrate");
			return NULL;
		}
	}

	/* Opening device */
	{
		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) {
			/* Throw an exception */
			LOGE("Cannot open port");
			/* TODO: throw an exception */
			return NULL;
		}
	}

	/* Configure device */
	{
		struct termios cfg;
		LOGD("Configuring serial port");
		if (tcgetattr(fd, &cfg)) {
			LOGE("tcgetattr() failed");
			close(fd);
			/* TODO: throw an exception */
			return NULL;
		}

		cfmakeraw(&cfg);
		cfsetispeed(&cfg, speed);
		cfsetospeed(&cfg, speed);

		if (tcsetattr(fd, TCSANOW, &cfg)) {
			LOGE("tcsetattr() failed");
			close(fd);
			/* TODO: throw an exception */
			return NULL;
		}
	}

	/* Create a corresponding file descriptor */
	{
		jclass cFileDescriptor = (*env)->FindClass(env,
				"java/io/FileDescriptor");
		jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor,
				"<init>", "()V");
		jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor,
				"descriptor", "I");
		mFileDescriptor = (*env)->NewObject(env, cFileDescriptor,
				iFileDescriptor);
		(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint) fd);
	}

	return mFileDescriptor;
}

/*
 * Class:     com_luoye_serialport_SerialPort
 * Method:    close
 * Signature: ()V
 */JNIEXPORT void JNICALL Java_com_luoye_serialport_SerialPort_close(JNIEnv *env,
		jobject thiz) {
	jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
	jclass FileDescriptorClass = (*env)->FindClass(env,
			"java/io/FileDescriptor");

	jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd",
			"Ljava/io/FileDescriptor;");
	jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass,
			"descriptor", "I");

	jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
	jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);

	LOGD("close(fd = %d)", descriptor);
	close(descriptor);
}

 Android.mk文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

TARGET_PLATFORM := android-3
LOCAL_MODULE    := serial_port
LOCAL_SRC_FILES := SerialPort.c
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)
Application.mk文件

APP_ABI := all

SerialHelper.java类

package com.luoye.serialport;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;

/**
 * 串口辅助工具类
 * 
 * @author LUOYE
 * @data 2015-07-05 01:13:50
 */
public class SerialHelper {
	/** 串口对象 */
	private SerialPort mSerialPort;
	/** 输出流,用来发送字节数据 */
	private OutputStream mOutputStream;
	/** 输入流,用来接收字节数据 */
	private InputStream mInputStream;
	/** 接收线程 */
	private ReadThread mReadThread;
	/** 发送线程 */
	private SendThread mSendThread;
	/** 串口号   /dev/ttyS1模拟器     */  
	private static String mPort = "/dev/ttyS1";
	/** 波特率  */
	private int mBaudRate = 9600;
	/** 是否已打开串口 */
	private boolean mbIsOpen = false;
	/** 串口循环发送数据 */
	private byte[] mLoopData = new byte[] { 0x30 };
	/** 延时发送时间 */
	private int iDelay = 500;

	/**
	 * 构造函数
	 * 
	 * @param port
	 *            串口名
	 * @param baudRate
	 *            波特率
	 */
	public SerialHelper(String port, int baudRate) {
		mPort = port;
		mBaudRate = baudRate;
	}

	/**
	 * 构造函数,默认端口号/dev/ttyS0,默认波特率9600
	 */
	public SerialHelper() {
		this(mPort, 9600);
	}

	/**
	 * 构造函数,默认波特率9600
	 * 
	 * @param port
	 *            串口名
	 */
	public SerialHelper(String port) {
		this(port, 9600);
	}

	/**
	 * 构造函数
	 * 
	 * @param port
	 *            串口名
	 * @param baudRate
	 *            波特率
	 * @throws NumberFormatException
	 *             波特率字符串不是数字字符串时抛出
	 */
	public SerialHelper(String port, String baudRate)
			throws NumberFormatException {
		this(port, Integer.parseInt(baudRate));
	}

	/**
	 * 打开串口
	 * 
	 * @throws SecurityException
	 *             打开串口失败时将抛出
	 * @throws IOException
	 *             获取串口输入输出流失败时将抛出
	 * @throws InvalidParameterException
	 *             无效参数异常,端口号或波特率无效时抛出
	 */
	public void open() throws SecurityException, IOException,
			InvalidParameterException {
		mSerialPort = new SerialPort(new File(mPort), mBaudRate, 0);
		mOutputStream = mSerialPort.getOutputStream();
		mInputStream = mSerialPort.getInputStream();
		mReadThread = new ReadThread();
		mReadThread.start();
		mSendThread = new SendThread();
		mSendThread.setSuspendFlag();
		mSendThread.start();
		mbIsOpen = true;
	}

	/**
	 * 关闭串口
	 */
	public void close() {
		if (mReadThread != null)
			mReadThread.interrupt();
		if (mSerialPort != null) {
			mSerialPort.close();
			mSerialPort = null;
		}
		mbIsOpen = false;
	}

	/**
	 * 发送串口数据
	 * 
	 * @param data
	 *            字节数组
	 * @throws IOException
	 *             发送串口数据时将抛出IO异常
	 */
	public void send(byte[] data) throws IOException {
		mOutputStream.write(data);
	}

	/**
	 * 发送十六进制字符串数据
	 * 
	 * @param hex
	 *            十六进制字符串
	 * @throws IOException
	 *             发送十六进制字符串失败时将抛出IO异常
	 */
	public void sendHex(String hex) throws IOException {
		byte[] bOutArray = MyFunc.hexToByteArr(hex);
		send(bOutArray);
	}

	/**
	 * 发送字符串
	 * 
	 * @param text
	 *            字符串
	 * @throws IOException
	 *             发送数据失败时将抛出IO异常
	 */
	public void sendTxt(String text) throws IOException {
		byte[] bOutArray = text.getBytes();
		send(bOutArray);
	}

	/**
	 * 串口接收线程
	 * 
	 * @author LUOYE
	 * @data 2015-07-05 1:20:32
	 */
	private class ReadThread extends Thread {
		@Override
		public void run() {
			super.run();
			while (!isInterrupted()) {
				try {
					if (mInputStream == null)
						break;
					byte[] buffer = new byte[1024];
					int size = mInputStream.read(buffer);
					if (size > 0) {
						if (onDataReceiveListener != null) {
							onDataReceiveListener.onReceive(buffer);
						}

					}
					try {
						Thread.sleep(50);// 延时50ms
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} catch (Throwable e) {
					e.printStackTrace();
					break;
				}
			}
		}
	}

	/**
	 * 串口发送线程
	 * 
	 * @author LUOYE
	 * @data 2015-07-05 01:52:14
	 */
	private class SendThread extends Thread {
		public boolean suspendFlag = true;// 控制线程的执行

		@Override
		public void run() {
			super.run();
			while (!isInterrupted()) {
				synchronized (this) {
					while (suspendFlag) {
						try {
							wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
				try {
					send(getLoopData());
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					Thread.sleep(iDelay);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		/**
		 * 暂停线程
		 */
		public void setSuspendFlag() {
			suspendFlag = true;
		}

		/**
		 * 唤醒线程
		 */
		public synchronized void setResume() {
			suspendFlag = false;
			notify();
		}
	}

	/**
	 * 获取波特率
	 * 
	 * @return 波特率
	 */
	public int getBaudRate() {
		return mBaudRate;
	}

	/**
	 * 设置波特率
	 * 
	 * @param baudRate
	 *            波特率
	 * @return true 设置成功,false 串口已开启,修改无效
	 */
	public boolean setBaudRate(int baudRate) {
		if (mbIsOpen) {
			return false;
		} else {
			mBaudRate = baudRate;
			return true;
		}
	}

	/**
	 * 设置波特率
	 * 
	 * @param baudRate
	 *            波特率
	 * @return true 设置成功,false 串口已开启,修改无效
	 */
	public boolean setBaudRate(String baudRate) {
		int iBaud = Integer.parseInt(baudRate);
		return setBaudRate(iBaud);
	}

	/**
	 * 获取串口名
	 * 
	 * @return 串口名
	 */
	public String getPort() {
		return mPort;
	}

	/**
	 * 设置串口名
	 * 
	 * @param port
	 *            串口名
	 * @return true 设置成功,false 串口已开启,修改无效
	 */
	public boolean setPort(String port) {
		if (mbIsOpen) {
			return false;
		} else {
			mPort = port;
			return true;
		}
	}

	/**
	 * 串口是否已开启
	 * 
	 * @return true 串口已开启, false 串口已关闭
	 */
	public boolean isOpen() {
		return mbIsOpen;
	}

	/**
	 * 获取循环发送的数据,若未设置,返回值为0x30
	 * 
	 * @return 字节数组
	 */
	public byte[] getLoopData() {
		return mLoopData;
	}

	/**
	 * 设置循环发送数据
	 * 
	 * @param loopData
	 *            字节数组
	 */
	public void setbLoopData(byte[] loopData) {
		mLoopData = loopData;
	}

	/**
	 * 设置循环发送文本
	 * 
	 * @param text
	 *            文本字符串
	 */
	public void setTxtLoopData(String text) {
		mLoopData = text.getBytes();
	}

	/**
	 * 设置循环发送十六进制文本
	 * 
	 * @param hex
	 *            十六进制文本
	 */
	public void setHexLoopData(String hex) {
		mLoopData = MyFunc.hexToByteArr(hex);
	}

	/**
	 * 获取延时时长
	 * 
	 * @return 延时时长
	 */
	public int getiDelay() {
		return iDelay;
	}

	/**
	 * 设置延时时长
	 * 
	 * @param delay
	 *            延时时长
	 */
	public void setiDelay(int delay) {
		this.iDelay = delay;
	}

	/**
	 * 恢复循环发送线程
	 */
	public void resumeSend() {
		if (mSendThread != null) {
			mSendThread.setResume();
		}
	}

	/**
	 * 暂停循环发送线程
	 */
	public void pauseSend() {
		if (mSendThread != null) {
			mSendThread.setSuspendFlag();
		}
	}

	/**
	 * 串口数据接收回调函数
	 * 
	 * @param btData
	 *            接收到的字节数组
	 */

	private OnDataReceiveListener onDataReceiveListener = null;

	public interface OnDataReceiveListener {
		public void onReceive(byte[] btData);
	}

	public void setOnDataReceiveListener(
			OnDataReceiveListener dataReceiveListener) {
		onDataReceiveListener = dataReceiveListener;
	}
}
SerialPort.java类

/*
 * Copyright 2009 Cedric Priscal
 * 
 * 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.luoye.serialport;

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.os.Build;
import android.util.Log;

/**
 * 串口类
 * 
 * @author LUOYE
 * @data 2015-07-05 11:03:15
 */
public class SerialPort {
	/** Log日志输出标识 */
	private static final String TAG = "SerialPort";

	/** 串口文件描述符,禁止删除或重命名,因为native层关闭串口时需要使用 */
	private FileDescriptor mFd;
	/** 输入流,用于接收串口数据 */
	private FileInputStream mFileInputStream;
	/** 输出流,用于发送串口数据 */
	private FileOutputStream mFileOutputStream;

	/**
	 * 构造函数
	 * 
	 * @param device
	 *            串口名
	 * @param baudrate
	 *            波特率
	 * @param flags
	 *            操作标识
	 * @throws SecurityException
	 *             安全异常,当串口文件不可读写时触发
	 * @throws IOException
	 *             IO异常,开启串口失败时触发
	 */
	public SerialPort(File device, int baudrate, int flags)
			throws SecurityException, IOException {
		/* 检测设备管理权限,即文件的权限属性 */
		if (!device.canRead() || !device.canWrite()) {
			try {
				if (Build.VERSION.SDK_INT >= 23) {
					String file_contexts = "/device/sprd/scx20/common/sepolicy/file_contexts";
					String device_te = "/device/sprd/scx20/common/sepolicy/device.te";
					String untrusted_app_te = "/device/sprd/scx20/common/sepolicy/untrusted_app.te";

					String text1 = "/dev/abc u:object_r:abc_device:s0";
					String text2 = "type abc_device, dev_type, mlstrustedobject;";
					String text3 = "allow untrusted_app adc_device:chr_fileoperate;";

					String text4 = "echo ";
					String text5 = ">>";

					for (int i = 0; i < 3; i++) {
						String content = "";
						String filePath = "";
						if (i == 0) {
							content = text1;
							filePath = file_contexts;
						} else if (i == 1) {
							content = text2;
							filePath = device_te;
						} else if (i == 2) {
							content = text3;
							filePath = untrusted_app_te;
						}
						Process su;
						su = Runtime.getRuntime().exec("/system/bin/su");
						String cmd = text4 + content + text5 + filePath + "\n";
						su.getOutputStream().write(cmd.getBytes());
						if ((su.waitFor() != 0)) {
							throw new SecurityException();
						}
					}
				}

				/* 若没有读/写权限,试着chmod该设备 */
				Process su;
				su = Runtime.getRuntime().exec("/system/bin/su");
				String cmd = "chmod 777 " + 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);
	}

	/**
	 * 获取输入流
	 * 
	 * @return 串口输入流
	 */
	public InputStream getInputStream() {
		return mFileInputStream;
	}

	/**
	 * 获取输出流
	 * 
	 * @return 串口输出流
	 */
	public OutputStream getOutputStream() {
		return mFileOutputStream;
	}

	/**
	 * 原生函数,开启串口虚拟文件
	 * 
	 * @param path
	 *            串口虚拟文件路径
	 * @param baudrate
	 *            波特率
	 * @param flags
	 *            操作标识
	 * @return
	 */
	private native static FileDescriptor open(String path, int baudrate,
			int flags);

	/**
	 * 原生函数,关闭串口虚拟文件
	 */
	public native void close();

	static {
		System.loadLibrary("serial_port");
	}
}

SerialPortFinder.java类

/*
 * Copyright 2009 Cedric Priscal
 * 
 * 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.luoye.serialport;

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;

/**
 * 串口检索类
 * 
 * @author LUOYE
 * @data 2015-07-05 10:45:02
 */
public class SerialPortFinder {

	/**
	 * 串口驱动类
	 * 
	 * @author LUOYE
	 * @data 2015-07-05 10:47:36
	 */
	public class Driver {
		/**
		 * 构造函数
		 * 
		 * @param name 串口名
		 * @param root 串口根目录
		 */
		public Driver(String name, String root) {
			mDriverName = name;
			mDeviceRoot = root;
		}

		/** 驱动名 */
		private String mDriverName;
		/** 设备根目录 */
		private String mDeviceRoot;
		/** 设备列表 */
		Vector<File> mDevices = null;

		/**
		 * 获取设备列表
		 * 
		 * @return 串口设备列表
		 */
		public Vector<File> getDevices() {
			if (mDevices == null) {
				mDevices = new Vector<File>();
				File dev = new File("/dev");
				File[] files = dev.listFiles();
				for (int 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;
		}

		/**
		 * 获取驱动名
		 * 
		 * @return 驱动名
		 */
		public String getName() {
			return mDriverName;
		}
	}

	/** Log日志输出标识 */
	private static final String TAG = "SerialPort";

	/** 驱动列表 */
	private Vector<Driver> mDrivers = null;

	/**
	 * 获取驱动列表
	 * 
	 * @return 驱动列表
	 * @throws IOException
	 */
	Vector<Driver> getDrivers() throws IOException {
		if (mDrivers == null) {
			mDrivers = new Vector<Driver>();
			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;
	}

	/**
	 * 获取所有设备
	 * 
	 * @return 设备列表
	 */
	public String[] getAllDevices() {
		Vector<String> devices = new Vector<String>();
		// Parse each driver
		Iterator<Driver> itdriv;
		try {
			itdriv = getDrivers().iterator();
			while (itdriv.hasNext()) {
				Driver driver = itdriv.next();
				Iterator<File> 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()]);
	}

	/**
	 * 获取所有设备路径
	 * 
	 * @return 串口设备路径
	 */
	public String[] getAllDevicesPath() {
		Vector<String> devices = new Vector<String>();
		// Parse each driver
		Iterator<Driver> itdriv;
		try {
			itdriv = getDrivers().iterator();
			while (itdriv.hasNext()) {
				Driver driver = itdriv.next();
				Iterator<File> 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()]);
	}
}

效果截图:



总结:

目前只有模拟器能测试通过。

实体机有两个比较难控制的;

  1.串口号,不同的机器,串口号不一样。没有一个固定的串口号。

  2. 6.0以上无法正常使用。解决方法还未证实可用(http://blog.csdn.net/lushengchu_luis/article/details/52775740)



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值