通过USB连接到Android平板电脑的设备将断开连接,重新连接时会赢得恢复通信
我正在处理一个问题,即一种称为聚合器板的设备(简称Aggboard)与Android平板电脑断开,并且在重新连接后,它不会在端点之间恢复通信。
一些背景:我一直在尝试重构这个Android应用程序的USB系统。最初,为了在端点之间传输数据,他们使用了usbdeviceconnection.bulktransfer()方法。我对使用USBREQUEST.Initialize(),USBREQUEST.QUEUE()和USBDeviceConnection.RequestWait()方法进行了重构。当在平板电脑上启动Android程序时,我的新方法工作正常(甚至更好),但是当Aggboard有USB中断/断开连接时,一旦重新连接,它就不会在其上停止的地方接收。但是,它们的旧方法usbdeviceconnection.bulktransfer()确实在重新连接时会进行通信。
旧的bulktransfer()方法始终删除数据包,这一过程刺激了整个过程,我们认为使用USBREQUEST类可能是一个改进。似乎情况似乎是这种情况,但是看到该产品必须倾向于使其Aggoble被断开/中断,因此,新方法必须允许Aggboard在重新连接后继续提供信息。
这是我重构的代码,以及原始方法:
原始方法[bulktransfer()] -
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class PayloadTask extends AsyncTask<Payload, Void, Payload> {
private UsbInterface usbInterface;
private UsbDeviceConnection usbConnection;
private UsbEndpoint usbToAggBoard;
private UsbEndpoint usbFromAggBoard;
private AggBoard owner;
PayloadTask(AggBoard owner, UsbInterface intf, UsbDeviceConnection connection) {
usbConnection = connection;
usbInterface = intf;
/*synchronized (intf)*/ {
usbToAggBoard = intf.getEndpoint(1);
usbFromAggBoard = intf.getEndpoint(0);
}
this.owner = owner;
}
@Override
protected Payload doInBackground(Payload... payloads) {
if (payloads != null) {
if (payloads.length > 1) {
Log.d("PayloadTask", "There was more than one payload. Going to have to iterate.");
int i = 0;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Payload payload : payloads) {
byte[] buffer = payload.getBytes();
while (AndroidUnityPlayerActivity.aggBoard == null)
{
try {
Log.d("PayloadTask", "Waiting for AndroidUnityPlayerActivity.aggboard to not be null");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int sent = usbConnection.bulkTransfer(usbToAggBoard, buffer, buffer.length, 1500);
busyWaitMicros(20);
if (++i % 1024 == 0) {
Log.e(AndroidUnityPlayerActivity.TAG, new Integer(i / 1024 * 8).toString() + "KB sent");
}
AggBoard.percentInstalled = i * 100 / payloads.length;
// Log.w(AndroidUnityPlayerActivity.TAG, new Float((float)i /payloads.length).toString());
}
Log.d("PayloadTask", "Finished iterating over the payloads.");
// rk3188 doesn't seem to see the success/fail message.. so let's fake one :/
if (AggBoard.encryptedBytes != null) {
Log.d("PayloadTask", "Aggboard.encryptedBytes was null. Creating some kind of success response payload??");
byte[] response = new byte [] { (byte)Payload.BOOTLOAD_STATUS,
(byte)BootloadStatusPayload.AGGREGATOR,
(byte)BootloadStatusPayload.SUCCESS,
0, 0, 0, 0,
(byte)Payload.AGGREGATOR_TAIL };
if (AggBoard.percentInstalled < 100) {
response[2] = (byte)BootloadStatusPayload.ERROR ;
}
Log.d("PayloadTask", "Creating bootload status SUCCESS task");
return Payload.CreatePayload(response);
}
} else if (payloads.length == 1 && payloads[0] != null) {
Log.d("PayloadTask", "There was only one payload. Running it");
byte[] buffer = payloads[0].getBytes();
if (AndroidUnityPlayerActivity.aggBoard != null) {
Log.d("PayloadTask", "Only one payload, beginning bulk transfer");
usbConnection.bulkTransfer(usbToAggBoard, buffer, buffer.length, 100);
/*if (Payload.START_SENDING_DATA == buffer[0]) {
AndroidUnityPlayerActivity.hasAggBoard = true;
}*/
}
else{
Log.d("PayloadTask", "AndroidUnityPlayerActivity's aggboard was null. Can't perform bulk transfer of payload.");
}
}
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
byte[] response = new byte[64];
int result = 0;
int tries = 0;
while (result < 1){
if (tries > 5) {
Log.d("PayloadTask", "PayloadTask Timed Out.");
return null;
} else if (isCancelled()) {
Log.w("PayloadTask", "PayloadTask Cancelled!");
return null;
}
/*synchronized (usbConnection)*/
result = usbConnection.bulkTransfer(usbFromAggBoard, response, response.length, 25);
tries += 1;
}
Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: ");
Log.d("PayloadTask", "doInBackground returning some payload from the aggboard.");
return Payload.CreatePayload(response);
}
@Override
protected void onPostExecute(Payload payload) {
owner.handlePayload(payload);
}
@Override
protected void onCancelled() {
Log.d("PayloadTask", "thread canceled");
super.onCancelled();
}
@Override
protected void onPreExecute() {
Log.d("PayloadTask", "started thread");
if (!usbConnection.claimInterface(usbInterface, true)) {
Log.w("PayloadTask", "Failed to claim interface!");
}
else{
Log.w("PayloadTask", "Claimed interface!");
}
}
public static void busyWaitMicros(long micros){
long waitUntil = System.nanoTime() + (micros * 1000);
while(waitUntil > System.nanoTime()){
;
}
}
}
重构代码[usbrequest/requestWait()] -
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class PayloadTask extends AsyncTask<Payload, Void, Payload> {
private UsbInterface usbInterface;
private UsbDeviceConnection usbConnection;
private UsbEndpoint usbToAggBoard;
private UsbEndpoint usbFromAggBoard;
private AggBoard owner;
PayloadTask(AggBoard owner, UsbInterface intf, UsbDeviceConnection connection) {
usbConnection = connection;
usbInterface = intf;
/*synchronized (intf)*/ {
usbToAggBoard = intf.getEndpoint(1);
usbFromAggBoard = intf.getEndpoint(0);
}
this.owner = owner;
}
@Override
protected Payload doInBackground(Payload... payloads) {
if (payloads != null) {
if (payloads.length > 1) {
Log.d("PayloadTask", "There was more than one payload. Going to have to iterate.");
int i = 0;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Payload payload : payloads) {
byte[] buffer = payload.getBytes();
while (AndroidUnityPlayerActivity.aggBoard == null)
{
try {
Log.d("PayloadTask", "Waiting for AndroidUnityPlayerActivity.aggboard to not be null");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbToAggBoard);
if (!request.queue(byteBuffer, buffer.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
int sent = request.hashCode();
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
busyWaitMicros(20);
if (++i % 1024 == 0) {
Log.e(AndroidUnityPlayerActivity.TAG, new Integer(i / 1024 * 8).toString() + "KB sent");
}
AggBoard.percentInstalled = i * 100 / payloads.length;
// Log.w(AndroidUnityPlayerActivity.TAG, new Float((float)i /payloads.length).toString());
}
Log.d("PayloadTask", "Finished iterating over the payloads.");
// rk3188 doesn't seem to see the success/fail message.. so let's fake one :/
if (AggBoard.encryptedBytes != null) {
Log.d("PayloadTask", "Aggboard.encryptedBytes was null. Creating some kind of success response payload??");
byte[] response = new byte [] { (byte)Payload.BOOTLOAD_STATUS,
(byte)BootloadStatusPayload.AGGREGATOR,
(byte)BootloadStatusPayload.SUCCESS,
0, 0, 0, 0,
(byte)Payload.AGGREGATOR_TAIL };
if (AggBoard.percentInstalled < 100) {
response[2] = (byte)BootloadStatusPayload.ERROR ;
}
Log.d("PayloadTask", "Creating bootload status SUCCESS task");
return Payload.CreatePayload(response);
}
} else if (payloads.length == 1 && payloads[0] != null) {
Log.d("PayloadTask", "There was only one payload. Running it");
byte[] buffer = payloads[0].getBytes();
if (AndroidUnityPlayerActivity.aggBoard != null) {
Log.d("PayloadTask", "Only one payload, beginning bulk transfer");
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbToAggBoard);
if (!request.queue(byteBuffer, buffer.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
/*if (Payload.START_SENDING_DATA == buffer[0]) {
AndroidUnityPlayerActivity.hasAggBoard = true;
}*/
}
else{
Log.d("PayloadTask", "AndroidUnityPlayerActivity's aggboard was null. Can't perform bulk transfer of payload.");
}
}
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
byte[] response = new byte[64];
int result = 0;
int tries = 0;
while (result < 1){
if (tries > 5) {
Log.d("PayloadTask", "PayloadTask Timed Out.");
return null;
} else if (isCancelled()) {
Log.w("PayloadTask", "PayloadTask Cancelled!");
return null;
}
/*synchronized (usbConnection)*/
if (usbConnection != null) {
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(response);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbFromAggBoard);
if (!request.queue(byteBuffer, response.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
result = request.hashCode();
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
}
tries += 1;
}
Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: ");
Log.d("PayloadTask", "doInBackground returning some payload from the aggboard.");
return Payload.CreatePayload(response);
}
@Override
protected void onPostExecute(Payload payload) {
owner.handlePayload(payload);
}
@Override
protected void onCancelled() {
Log.d("PayloadTask", "thread canceled");
super.onCancelled();
}
@Override
protected void onPreExecute() {
Log.d("PayloadTask", "started thread");
if (!usbConnection.claimInterface(usbInterface, true)) {
Log.w("PayloadTask", "Failed to claim interface!");
}
else{
Log.w("PayloadTask", "Claimed interface!");
}
}
public static void busyWaitMicros(long micros){
long waitUntil = System.nanoTime() + (micros * 1000);
while(waitUntil > System.nanoTime()){
;
}
}
}
I am dealing with an issue where a device called an aggregator board (AggBoard for short) becomes disconnected from an Android tablet, and upon re-connection it won't resume communication between endpoints.
Some background: I have been trying to refactor this Android app's Usb system. Initially, in order to transfer data between endpoints, they used the UsbDeviceConnection.bulkTransfer() method. I refactored it to use the UsbRequest.initialize(), UsbRequest.queue(), and then UsbDeviceConnection.requestWait() methods. When the Android program is booted on the tablet, my new methods work fine (perhaps even better), but when there is a USB disruption/disconnection from the AggBoard, once reconnected, it won't pick up where it left off. However, their old method, UsbDeviceConnection.bulkTransfer() DOES pick up communication upon reconnect.
This entire process was spurred by data packets consistently being dropped by the old bulktransfer() method, and we thought using the UsbRequest class might be an improvement. It appears this might be the case, but seeing as this product has to tendency to have its AggBoard become disconnected/disrupted, it is imperative that the new method allows the AggBoard to continue to deliver information upon reconnect.
Here is the code I refactored, along with the original method:
Original Method [bulkTransfer()] -
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class PayloadTask extends AsyncTask<Payload, Void, Payload> {
private UsbInterface usbInterface;
private UsbDeviceConnection usbConnection;
private UsbEndpoint usbToAggBoard;
private UsbEndpoint usbFromAggBoard;
private AggBoard owner;
PayloadTask(AggBoard owner, UsbInterface intf, UsbDeviceConnection connection) {
usbConnection = connection;
usbInterface = intf;
/*synchronized (intf)*/ {
usbToAggBoard = intf.getEndpoint(1);
usbFromAggBoard = intf.getEndpoint(0);
}
this.owner = owner;
}
@Override
protected Payload doInBackground(Payload... payloads) {
if (payloads != null) {
if (payloads.length > 1) {
Log.d("PayloadTask", "There was more than one payload. Going to have to iterate.");
int i = 0;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Payload payload : payloads) {
byte[] buffer = payload.getBytes();
while (AndroidUnityPlayerActivity.aggBoard == null)
{
try {
Log.d("PayloadTask", "Waiting for AndroidUnityPlayerActivity.aggboard to not be null");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int sent = usbConnection.bulkTransfer(usbToAggBoard, buffer, buffer.length, 1500);
busyWaitMicros(20);
if (++i % 1024 == 0) {
Log.e(AndroidUnityPlayerActivity.TAG, new Integer(i / 1024 * 8).toString() + "KB sent");
}
AggBoard.percentInstalled = i * 100 / payloads.length;
// Log.w(AndroidUnityPlayerActivity.TAG, new Float((float)i /payloads.length).toString());
}
Log.d("PayloadTask", "Finished iterating over the payloads.");
// rk3188 doesn't seem to see the success/fail message.. so let's fake one :/
if (AggBoard.encryptedBytes != null) {
Log.d("PayloadTask", "Aggboard.encryptedBytes was null. Creating some kind of success response payload??");
byte[] response = new byte [] { (byte)Payload.BOOTLOAD_STATUS,
(byte)BootloadStatusPayload.AGGREGATOR,
(byte)BootloadStatusPayload.SUCCESS,
0, 0, 0, 0,
(byte)Payload.AGGREGATOR_TAIL };
if (AggBoard.percentInstalled < 100) {
response[2] = (byte)BootloadStatusPayload.ERROR ;
}
Log.d("PayloadTask", "Creating bootload status SUCCESS task");
return Payload.CreatePayload(response);
}
} else if (payloads.length == 1 && payloads[0] != null) {
Log.d("PayloadTask", "There was only one payload. Running it");
byte[] buffer = payloads[0].getBytes();
if (AndroidUnityPlayerActivity.aggBoard != null) {
Log.d("PayloadTask", "Only one payload, beginning bulk transfer");
usbConnection.bulkTransfer(usbToAggBoard, buffer, buffer.length, 100);
/*if (Payload.START_SENDING_DATA == buffer[0]) {
AndroidUnityPlayerActivity.hasAggBoard = true;
}*/
}
else{
Log.d("PayloadTask", "AndroidUnityPlayerActivity's aggboard was null. Can't perform bulk transfer of payload.");
}
}
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
byte[] response = new byte[64];
int result = 0;
int tries = 0;
while (result < 1){
if (tries > 5) {
Log.d("PayloadTask", "PayloadTask Timed Out.");
return null;
} else if (isCancelled()) {
Log.w("PayloadTask", "PayloadTask Cancelled!");
return null;
}
/*synchronized (usbConnection)*/
result = usbConnection.bulkTransfer(usbFromAggBoard, response, response.length, 25);
tries += 1;
}
Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: ");
Log.d("PayloadTask", "doInBackground returning some payload from the aggboard.");
return Payload.CreatePayload(response);
}
@Override
protected void onPostExecute(Payload payload) {
owner.handlePayload(payload);
}
@Override
protected void onCancelled() {
Log.d("PayloadTask", "thread canceled");
super.onCancelled();
}
@Override
protected void onPreExecute() {
Log.d("PayloadTask", "started thread");
if (!usbConnection.claimInterface(usbInterface, true)) {
Log.w("PayloadTask", "Failed to claim interface!");
}
else{
Log.w("PayloadTask", "Claimed interface!");
}
}
public static void busyWaitMicros(long micros){
long waitUntil = System.nanoTime() + (micros * 1000);
while(waitUntil > System.nanoTime()){
;
}
}
}
Refactored Code [UsbRequest/requestWait()] -
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
public class PayloadTask extends AsyncTask<Payload, Void, Payload> {
private UsbInterface usbInterface;
private UsbDeviceConnection usbConnection;
private UsbEndpoint usbToAggBoard;
private UsbEndpoint usbFromAggBoard;
private AggBoard owner;
PayloadTask(AggBoard owner, UsbInterface intf, UsbDeviceConnection connection) {
usbConnection = connection;
usbInterface = intf;
/*synchronized (intf)*/ {
usbToAggBoard = intf.getEndpoint(1);
usbFromAggBoard = intf.getEndpoint(0);
}
this.owner = owner;
}
@Override
protected Payload doInBackground(Payload... payloads) {
if (payloads != null) {
if (payloads.length > 1) {
Log.d("PayloadTask", "There was more than one payload. Going to have to iterate.");
int i = 0;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Payload payload : payloads) {
byte[] buffer = payload.getBytes();
while (AndroidUnityPlayerActivity.aggBoard == null)
{
try {
Log.d("PayloadTask", "Waiting for AndroidUnityPlayerActivity.aggboard to not be null");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbToAggBoard);
if (!request.queue(byteBuffer, buffer.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
int sent = request.hashCode();
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
busyWaitMicros(20);
if (++i % 1024 == 0) {
Log.e(AndroidUnityPlayerActivity.TAG, new Integer(i / 1024 * 8).toString() + "KB sent");
}
AggBoard.percentInstalled = i * 100 / payloads.length;
// Log.w(AndroidUnityPlayerActivity.TAG, new Float((float)i /payloads.length).toString());
}
Log.d("PayloadTask", "Finished iterating over the payloads.");
// rk3188 doesn't seem to see the success/fail message.. so let's fake one :/
if (AggBoard.encryptedBytes != null) {
Log.d("PayloadTask", "Aggboard.encryptedBytes was null. Creating some kind of success response payload??");
byte[] response = new byte [] { (byte)Payload.BOOTLOAD_STATUS,
(byte)BootloadStatusPayload.AGGREGATOR,
(byte)BootloadStatusPayload.SUCCESS,
0, 0, 0, 0,
(byte)Payload.AGGREGATOR_TAIL };
if (AggBoard.percentInstalled < 100) {
response[2] = (byte)BootloadStatusPayload.ERROR ;
}
Log.d("PayloadTask", "Creating bootload status SUCCESS task");
return Payload.CreatePayload(response);
}
} else if (payloads.length == 1 && payloads[0] != null) {
Log.d("PayloadTask", "There was only one payload. Running it");
byte[] buffer = payloads[0].getBytes();
if (AndroidUnityPlayerActivity.aggBoard != null) {
Log.d("PayloadTask", "Only one payload, beginning bulk transfer");
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbToAggBoard);
if (!request.queue(byteBuffer, buffer.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
/*if (Payload.START_SENDING_DATA == buffer[0]) {
AndroidUnityPlayerActivity.hasAggBoard = true;
}*/
}
else{
Log.d("PayloadTask", "AndroidUnityPlayerActivity's aggboard was null. Can't perform bulk transfer of payload.");
}
}
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
byte[] response = new byte[64];
int result = 0;
int tries = 0;
while (result < 1){
if (tries > 5) {
Log.d("PayloadTask", "PayloadTask Timed Out.");
return null;
} else if (isCancelled()) {
Log.w("PayloadTask", "PayloadTask Cancelled!");
return null;
}
/*synchronized (usbConnection)*/
if (usbConnection != null) {
UsbRequest request = new UsbRequest();
ByteBuffer byteBuffer = ByteBuffer.wrap(response);
byteBuffer.rewind();
try {
request.initialize(usbConnection, usbFromAggBoard);
if (!request.queue(byteBuffer, response.length)) {
throw new IOException("Error queueing USB request.");
}
usbConnection.requestWait();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(usbConnection != null) {
result = request.hashCode();
request.close();
}
else{
Log.d("PayloadTask", "Request not received by usbConnection");
}
}
}
tries += 1;
}
Log.i("PayloadTask", new Integer(response.length).toString() + " bytes received: ");
Log.d("PayloadTask", "doInBackground returning some payload from the aggboard.");
return Payload.CreatePayload(response);
}
@Override
protected void onPostExecute(Payload payload) {
owner.handlePayload(payload);
}
@Override
protected void onCancelled() {
Log.d("PayloadTask", "thread canceled");
super.onCancelled();
}
@Override
protected void onPreExecute() {
Log.d("PayloadTask", "started thread");
if (!usbConnection.claimInterface(usbInterface, true)) {
Log.w("PayloadTask", "Failed to claim interface!");
}
else{
Log.w("PayloadTask", "Claimed interface!");
}
}
public static void busyWaitMicros(long micros){
long waitUntil = System.nanoTime() + (micros * 1000);
while(waitUntil > System.nanoTime()){
;
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论