前言
使用 adb forward 打造一个通过USB连接建立的PC端与手机端交互的工具,类似于银行里的服务评价器,由PC端发出指令,在手机端(或终端)接收指令并显示页面。
1、 adb forward 原理概述
adb forward 的功能是建立一个转发
如:adb forward tcp:8000 tcp:9000 的意思是,将PC端的 8000 端口收到的数据,转发给手机中的 9000 端口。
但是光执行这个命令还不能转发数据,还需要完成下面两个步骤才能传数据:
- (a)在手机端,建立一个端口为9000的 server,并打开 server 到监听状态。
- (b)在PC端,建立一个 socket client 端,连接到端口为8000的 server 上。
adb forward tcp:8000 tcp:9000 原理示意图:
-
PC端应用与手机端应用 通信建立过程:
(1)执行 adb forward tcp:8000 tcp:9000
(2)启动手机端应用,建立端口为9000的 server,并处于监听状态(LISTENING)
(3)启动PC端应用,连接端口为8000的 server(adb 创建的)
之后,就可以传输数据了 -
PC端应用与手机端应用之间传输数据的过程:
(1)PC端应用将数据发送给端口为8000的 server(adb 创建的)
(2)adb 将数据转发给手机端 adbd 进程(通过USB传输)
(3)adbd 进程将数据发送给端口为9000的 server(手机端应用创建的)
传递是双向的,第(1)和第(3)步是通过socket实现的,所以通过 socket 的读和写就完成了PC端和手机端的数据传递。
2、 实现PC端和手机端的通信
步骤:
1、确保 Android设备通过USB连接到PC上,在PC上运行 adb forward tcp:8000 tcp:9000,将PC端8000端口的数据, 转发到Android端的9000端口上。
2、Android 设备上编写APP,作为网络通信端的 Server 端,建立 ServerSocket,打开端口9000。
3、PC上编写程序,作为网络通信的 Client 端,打开端口8000。
4、PC端与Android端即可建立 Socket 连接进行通信。
实现:
2.1 PC端
package com.example.usbpc;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
public class MyClass {
private static boolean mRunning = true;
public static void main(String[] args) {
if (!setupAdbForward()) {
System.out.println("设置端口转发失败");
return;
}
System.out.println("任意字符, 回车键发送");
startClient();
}
private static boolean setupAdbForward() {
try {
//将PC端8000端口的数据, 转发到Android端的9000端口上
Runtime.getRuntime().exec("adb forward tcp:8000 tcp:9000");
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private static void startClient() {
try {
//设置pc端的端口为8000
Socket socket = new Socket("127.0.0.1", 8000);
// InThread 接收手机端传来的数据
new Thread(new InThread(socket)).start();
// OutThread 用于从PC端输入并传给手机端
Scanner scanner = new Scanner(System.in);
while (true) {
String msg = scanner.next();
sendToServer(socket, msg);
}
// new Thread(new OutThread(socket, msg)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 接收手机端传来的数据
*/
static class InThread implements Runnable {
Socket socket = null;
public InThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
while (mRunning) {
if (socket.isClosed()) {
mRunning = false;
break;
}
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
byte[] buffer = new byte[256];
int len = dis.read(buffer);
if (len > 0) {
System.out.println("\n接收到:" + new String(buffer, 0, len, "UTF-8"));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 从PC端输入并传给手机端
*
* @param msg 信息
* @throws IOException 异常
*/
public static void sendToServer(Socket socket, String msg) throws IOException {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(msg);
dos.flush();
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.2 Android端
1、声明网络权限
一定要声明 android.permission.INTERNET 权限,否则会抛出异常 java.net.SocketException: Permission denied
在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.INTERNET" />
2.2.1 接收PC端传来的数据
1、启动 Server
在 Application 中启动 Server
package com.example.rs.risenevaluatorpad;
import android.app.Application;
import android.content.Context;
import com.example.rs.risenevaluatorpad.server.ServerThread;
/**
* Created by sgll on 2018/12/10.
*/
public class MyApplication extends Application {
public static Context mContext;
public static ServerThread mServer;
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
mServer = new ServerThread();
mServer.start();
// new ServerThread().start();
}
}
2、接受PC端传来的数据
ServerThread (单线程)
package com.example.rs.risenevaluatorpad.server;
import android.text.TextUtils;
import android.util.Log;
import com.example.rs.risenevaluatorpad.model.DataHolder;
import com.example.rs.risenevaluatorpad.util.ActivityCollector;
import com.example.rs.risenevaluatorpad.util.Constants;
import com.example.rs.risenevaluatorpad.util.OperationUtil;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by sgll on 2018/12/10.
*/
public class ServerThread extends Thread {
private ServerSocket serverSocket;
private Socket socket;
boolean isLoop = true;
public void setIsLoop(boolean isLoop) {
this.isLoop = isLoop;
}
public Socket getSocket() {
return socket;
}
@Override
public void run() {
super.run();
try {
//设置Android端口为9000
serverSocket = new ServerSocket(9000);
while (isLoop) {
try {
//从连接队列中取出一个连接,如果没有则等待
socket = serverSocket.accept();
Log.v(Constants.SERVER_TAG, "从连接队列中取出一个连接");
// new ProcessClientRequestThread(socket).start();
while (true) {
/**
* isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()
* 这些方法无法判断服务端是否断开,只能判断本地的状态
*/
// 发送心跳包,单线程中使用,判断socket是否断开
socket.sendUrgentData(0xFF);
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
// InputStreamReader reader = new InputStreamReader(inputStream);
// InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
// char[] buffer = new char[1024];
int bt;
String text = "";
// StringBuffer stringBuffer = new StringBuffer();
int index = 0;
Log.v(Constants.SERVER_TAG, "inputStream.read 之前");
while ((bt = inputStream.read(buffer)) != -1) {
Log.v(Constants.SERVER_TAG, "bt:" + bt + ",," + (index++));
// stringBuffer.append(buffer, 0, bt);
text += new String(buffer, 0, bt).trim();
// String aaa = new String(buffer, 0, bt).trim();
// text = text + aaa;
Log.e(Constants.SERVER_TAG, "------------");
Log.e(Constants.SERVER_TAG, "text: " + text.trim());
/**
* 当接收到大数据,inputStream读取完后,由于inputStream通道是一直在的,
* inputStream.read(buffer)处在堵塞状态,所以并不会跳出当前循环,
* 导致无法判断数据是否传输完,所以PC端传输数据时需要在数据中
* 加入报尾,Android设备上接收到报尾,代表此次数据传输完毕,
* 也可以加上报头,来表示数据的开始
*
*/
if (text.endsWith("-vvv")) {
text = text.substring(0, text.lastIndexOf("-vvv"));
// OperationUtil.getOperation(text);
// DataHolder.getInstance().setData("msg", text);
// ActivityCollector.startActivity(text);
Log.v(Constants.SERVER_TAG, "内容长度:" + text.length());
Log.v(Constants.SERVER_TAG, "读取结束,内容为:" + text);
Log.v(Constants.SERVER_TAG, "inputStream.read 之后");
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2.2 发送消息到PC端
1、BaseActivity
package com.example.rs.risenevaluatorpad.base;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import com.example.rs.risenevaluatorpad.MyApplication;
import com.example.rs.risenevaluatorpad.R;
import com.example.rs.risenevaluatorpad.util.ActivityCollector;
import com.example.rs.risenevaluatorpad.util.ToastUtils;
import java.net.Socket;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* Created by sgll on 2018/12/11.
*/
public abstract class BaseActivity extends AppCompatActivity {
//反注销
private Unbinder unbinder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
unbinder = ButterKnife.bind(this);
ActivityCollector.addActivity(this);
setServer();
onCreateView(savedInstanceState);
processLogic();
setListener();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbinder.unbind();
ActivityCollector.removeActivity(this);
}
/**
* 设置服务
*/
private void setServer(){
//TODO:重启服务
if (MyApplication.mServer == null) {
ToastUtils.toast(this, getString(R.string.error_server_socket));
return;
}
}
public Socket getSocket(){
Socket mSocket = null;
if (MyApplication.mServer.getSocket() != null) {
mSocket = MyApplication.mServer.getSocket();
}
return mSocket;
}
/**
* 加载页面 Layout
* @return R.layout.xxx
*/
protected abstract int getLayoutResId();
/**
* 初始化控件
*/
protected abstract void onCreateView(Bundle savedInstanceState);
/**
* 设置各种事件的监听器
*/
protected abstract void setListener();
/**
* 业务逻辑处理,主要与后端交互(网络请求)
*/
protected abstract void processLogic();
}
2、Main1Activity
package com.example.rs.risenevaluatorpad.activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.example.rs.risenevaluatorpad.R;
import com.example.rs.risenevaluatorpad.base.BaseActivity;
import com.example.rs.risenevaluatorpad.server.SendThread;
import com.example.rs.risenevaluatorpad.util.ToastUtils;
import java.net.Socket;
import butterknife.BindView;
import butterknife.OnClick;
/**
* Created by sgll on 2019/1/14.
*/
public class Main1Activity extends BaseActivity {
@BindView(R.id.bt_submit)
Button btSubmit;
@BindView(R.id.et_content)
EditText etContent;
@Override
protected int getLayoutResId() {
return R.layout.activity_main;
}
@Override
protected void onCreateView(Bundle savedInstanceState) {
}
@Override
protected void setListener() {
}
@Override
protected void processLogic() {
}
@OnClick(R.id.bt_submit)
void onSubmit(View view) {
Socket mSocket = getSocket();
if (mSocket == null) {
ToastUtils.toast(this, getString(R.string.error_socket));
return;
}
String content = etContent.getText().toString().trim();
if (TextUtils.isEmpty(content)) {
ToastUtils.toast(this, "内容不能为空");
return;
}
new SendThread(content, mSocket).start();
}
}
3、SendThread 发送消息到PC端
package com.example.rs.risenevaluatorpad.server;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* Created by sgll on 2018/12/11.
* 发送消息到PC端
*/
public class SendThread extends Thread {
private String data;
private Socket socket;
public SendThread(String data, Socket socket){
this.data = data;
this.socket = socket;
}
@Override
public void run() {
super.run();
sendDatas();
}
private void sendDatas(){
try {
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.write(data.getBytes("UTF-8"));
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行PC端和Android端,即可进行通信
参考资料
https://blog.csdn.net/u013553529/article/details/80296870
https://blog.csdn.net/merrylilili/article/details/74641369
adb常用命令