本人从事多年的Android智能设备开发,做过手机、MiFi、智能门锁等产品,除了手机之外,其他的产品在后期的维护及版本迭代过程中,经常需要通过分析查看设备的日志来定位问题,比如智能门锁,遇到故障时,经常就抱着一台笔记本,打开设备的usb调试开关后,用usb线连接设备进行问题重现和日志分析。当然,获取日志的方式是多种多样的,也可以通过网络上传。但总免不了需要现场查看,现场查看就有个问题,笔记本并不是随身携带,而手机确实随身携带的,因此,能不能在手机端实时查看设备的日志呢?答案是肯定的。
MiFi和智能门锁都开放了热点,因此,调试的手机可以连接到设备的热点,进行组网后实现实时日志查看。这种方式既不需要数据线,也不需要笔记本,甚至usb调试开关也不用开启。
先说服务端(设备端),注意这里用到了线程池,因此服务端是具备了一对多进行响应的基础能力的:
/**
* @作用 开启tcp服务
*/
public void startTCP() {
if (runnableTcp != null) {
isStart = false;
runnableTcp = null;
}
try {
runnableTcp = new TcpReceive();
threadTcp = new Thread(runnableTcp);
isStart = true;
threadTcp.start();
} catch (Exception e) {
logd("开启TCP失败:" + e.getMessage());
}
try {
mExe = Executors.newCachedThreadPool();// 创建一个线程池
} catch (Exception e) {
logd("创建线程池失败:" + e);
}
}
private class TcpReceive implements Runnable {
Socket socket = null;
ServerSocket server = null;
public void run() {
try {
//server = new ServerSocket(53858);
if (server == null) {
server = new ServerSocket();
server.setReuseAddress(true);
server.bind(new InetSocketAddress(53858));
}
allSockets = new HashSet();
while (isRunning) {
if (!server.isClosed()) {
//logd("serverSocket "+server.hashCode()+" 监听53858端口中...");
socket = server.accept();
}
if (socket != null) {
allSockets.add(socket);
//logd("新的socket " + socket.hashCode() + " 加入,当前socket总数为:" + allSockets.size());
mExe.execute(new EchoThread(socket));
}
}
} catch (IOException e) {
logd("serverSocket创建异常"+e);
}
}
}
public class EchoThread extends Thread {
Socket socket;
PrintWriter out;
BufferedReader in;
public EchoThread(Socket _socket){
socket = _socket;
try {
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
logd("PrintWriter或BufferedReader异常"+e);
}
}
public void run() {
try {
while (isStart) {
if (socket == null || socket.isClosed() || !socket.isBound() || !socket.isConnected()) {
break;
}
String line = in.readLine();
String ip = socket.getInetAddress().toString();
if (ip.contains("/")) {
ip = ip.substring(1, ip.length());
}
if (line == null){
logd("line请求为空");
break;
}
String temp[] = line.split("/");
if(temp == null || temp.length <2 ||!temp[0].equals(TcpConfig._HEAD_)){
return;
}else{
line = temp[1];
}
if (!line.equals(TcpConfig.GET_DEVICE_INFO) && !line.contains(TcpConfig.RESTART_TEST_GET_RES)){
logd("Tcp收到app请求消息为:" + line + ";app对应手机的ip为:" + ip);
}
if (line.equals(TcpConfig.GET_REAL_TIME_LOGS)) {
isRealTimeLogs = true;
App.getInstance().setRealTimeLogQueque(mQueue);
String logs = null;
while (isRealTimeLogs) {
try {
logs = mQueue.take();
out.println(logs);
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
logd(e);
}
}
out.print("false,quit.");
}
if (!isCmds(line)) {
out.println("Request Error");
}
out.flush();
out.close();
socket.close();
allSockets.remove(socket);
}
} catch (IOException e) {
e.printStackTrace();
logd("socket信息交互异常:" + e);
} finally {
try {
if (in != null) {
// logd("BufferedReader关闭,hashCode:" + in.hashCode());
in.close();
}
if (out != null) {
// logd("PrintWriter关闭,hashCode:" + out.hashCode());
out.close();
}
if (socket != null) {
// logd("socket "+socket.hashCode()+" 关闭");
socket.close();
allSockets.remove(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码中已经略去了其他的设备控制接口,注意实时日志查看的接口和退出实时查看的接口:
if (line.equals(TcpConfig.GET_REAL_TIME_LOGS)) {
isRealTimeLogs = true;
App.getInstance().setRealTimeLogQueque(mQueue);
String logs = null;
while (isRealTimeLogs) {
try {
logs = mQueue.take();
out.println(logs);
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
logd(e);
}
}
out.print("false,quit.");
}
if(line.equals(TcpConfig.CANCEL_REAL_TIME_LOGS)){
isRealTimeLogs = false;
mQueue.clear();
App.getInstance().setRealTimeLogQueque(null);
out.print(true);
}
其实就是从队列里面循环取日志内容往对端发送。
那么日志是在哪里加入队列的呢?我把记录日志的方法写在了Application中,整个工程记录日志最终都会走到这个方法。那么在记录日志的同时就加入到该队列就可以了。
/**
* @作用 把传递消息写进Log日志
*/
public static void showTestInfo(final Object msg) {
if (msg == null || msg.toString().isEmpty()) {
return;
}
String appMsg = getDateToStringStyle("MM-dd HH:mm:ss,SSS", new Date()) + ":" + msg.toString();
if (logger == null) {
logger = Logger.getLogger(TGTConfig.TAG);
}
logger.warn(appMsg);
if(mQueue != null){
mQueue.add(appMsg);
}
}
这个队列的在收到查看实时日志指令时初始化,在退出实时日志查看时置空(日志不会加入队列):
private static PriorityBlockingQueue mQueue;
public void setRealTimeLogQueque(PriorityBlockingQueue queue){
mQueue = queue;
}
服务端就是这样,接下来看客户端:
private void startRealTimeLog(){
cmd = TcpConfig.GET_REAL_TIME_LOGS;
new Thread(sendTcp).start();
new Thread(new Runnable() {
@Override
public void run() {
doloop();
}
}).start();
}
private void stopRealTimeLog(){
cmd = TcpConfig.CANCEL_REAL_TIME_LOGS;
new Thread(sendTcp).start();
}
private void doloop(){
String res = null;
while (isStart){
try{
res = mQueue.take();
addIntoList(res);
Thread.sleep(50);
}catch (Exception e){
e.printStackTrace();
logd(e);
}
}
logd("loop quit.");
}
private void addIntoList(String string){
if (realTimeLogs.size() > MAX_LINE) {
realTimeLogs.removeFirst();
}
realTimeLogs.add(string);
uiHandler.removeMessages(MSG_REFRESH_UI);
uiHandler.sendMessageDelayed(Message.obtain(uiHandler,MSG_REFRESH_UI,0,0,realTimeLogs),100);
}
Runnable sendTcp = new Runnable() {
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress(TcpUtil.hostIP, TcpConfig.port), 10 * 1000);
if (!cmd.isEmpty()) {
while (socket.isConnected() && !socket.isClosed()) {
try {
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
out.println(TcpConfig._HEAD_ + "/" + cmd);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
logd(line);
mQueue.add(line);
}
break;
} catch (Exception e) {
e.printStackTrace();
logd(e);
}
}
}
} catch (IOException e) {
e.printStackTrace();
logd(e);
} finally {
try {
if (in != null)
in.close();
if (socket != null)
socket.close();
if (out != null)
out.close();
} catch (IOException e) {
logd(e);
}
}
}
};
其实你会发现,并么有多复杂,一个线程阻塞地去读取服务端返回的数据,另一个线程循环的从队列读取数据,并发送到UI线程进行UI刷新。