其实这个功能时比较简单的,但是有点逻辑性,也是工作中碰到的,本文只讲概念代码,并不会去具体实现,不过我相信你看完之后会知道如何去做的,相信我
假设我现在一个手机端,一个设备端,而我们现在做的是手机端,设备端我们暂时不管,那我们通过OTA是怎么样的流程?
这就是一个比较完整的看逻辑了,那好,我们就用伪代码来实现一遍,首先是创建8080的TCP端口连接
/** * TCP连接 */
private void connect() {
Log.i(TAG, "connect");
new Thread() {
@Override
public void run() {
try {
mSocket = new Socket("ip", 8080);
mReader = mSocket.getInputStream();
mWriter = mSocket.getOutputStream();
revMsg();
mWriter.flush();
} catch (IOException e) {
Log.e(TAG, "connect:" + e.toString());
}
}
}.start();
}
这里为了发送和接收方便,我们单独封装两个个方法
/** * 发送指令 * * @param msg */
private void sendMsg(final String msg) {
new Thread() {
@Override
public void run() {
try {
Log.i(TAG, "send:" + msg);
mWriter.write(msg.getBytes("utf-8"));
mWriter.flush();
} catch (Exception e) {
Log.e(TAG, "sendMsg:" + e.toString());
}
}
}.start();
}
/** * 接收 */
private void revMsg() {
new Thread() {
@Override
public void run() {
try {
while (true) {
byte[] mbyte = new byte[1024];
int temp = mReader.read(mbyte);
String result = new String(mbyte, 0, temp);
Log.i(TAG, "result:" + result);
}
} catch (Exception e) {
Log.e(TAG, "revMsg:" + e.toString());
}
}
}.start();
}
专门用来发送和接收,并且我们实现一个MD5Utils
public class MD5Utils {
/** * MD5加密文件 * @param file * @return * @throws FileNotFoundException */
public static String getMd5ByFile(File file) throws FileNotFoundException {
String value = null;
FileInputStream in = new FileInputStream(file);
try {
MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(byteBuffer);
BigInteger bi = new BigInteger(1, md5.digest());
value = bi.toString(16);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return value;
}
}
OK,前面的工作都做完了,那我们就来实现逻辑了,在点击事件里检测了
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnOta:
//1.检查是否存在update.zip
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/update.zip");
if (file.exists()) {
} else {
Toast.makeText(this, "未检测到固件", Toast.LENGTH_SHORT).show();
}
break;
}
}
可以,在这里我们把第一步实现了,然后第二步如果不耗时的话可以和第三步一起,我之前就碰到一个15MB的File进行CRC32的时候就耗时了,所以假设不耗时,我们可以直接发送CMD_UPDATE_LODING:校验码,如代码
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnOta:
//1.检查是否存在update.zip
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/update.zip");
if (file.exists()) {
try {
sendMsg("CMD_UPDATE_LODING:" + MD5Utils.getMd5ByFile(file));
} catch (FileNotFoundException e) {
Log.e(TAG, "MD5 Error" + e.toString());
}
} else {
Toast.makeText(this, "未检测到固件", Toast.LENGTH_SHORT).show();
}
break;
}
}
这里算是发出去了,那我们在接收的地方可以这样去操作
/** * 接收 */
private void revMsg() {
new Thread() {
@Override
public void run() {
try {
while (true) {
byte[] mbyte = new byte[1024];
int temp = mReader.read(mbyte);
String result = new String(mbyte, 0, temp);
Log.i(TAG, "result:" + result);
if (result.startsWith("CMD_UPDATE_LODING")) {
String[] mStr = result.split(":");
switch (mStr[1]) {
case "0":
mHandler.sendEmptyMessage(UPDATE_OK);
break;
case "-1":
mHandler.sendEmptyMessage(UPDATE_OK);
break;
}
}
}
} catch (Exception e) {
Log.e(TAG, "revMsg:" + e.toString());
}
}
}.start();
}
这里如果他返回0,说明他准备好了
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case UPDATE_OK:
connectFile();
break;
case UPDATE_NO:
Toast.makeText(MainActivity.this, "OTA未准备好", Toast.LENGTH_SHORT).show();
break;
}
}
};
是0,那我就可以开始推送File了,推送File要要考虑的就是进度,所以我专门写了一个方法来监听进度
/** * 上传文件 */
private void connectFile() {
new Thread() {
@Override
public void run() {
try {
Log.i(TAG, "connectFile");
mSocketFile = new Socket("ip", 9090);
mReaderFile = mSocketFile.getInputStream();
mWriterFile = mSocketFile.getOutputStream();
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/update.zip");
FileInputStream fin = new FileInputStream(file);
OutputStream out = mSocketFile.getOutputStream();
long fileLength = file.length();
byte[] buf = new byte[2048];
int len = 0;
int progress = 0;
int progressAll = 0;
while ((len = fin.read(buf)) != -1) {
out.write(buf, 0, len);
progress += len;
progressAll = progress * 100 / (int) fileLength;
System.out.println("progressAll"+ progressAll);
}
mSocketFile.shutdownInput();
mWriterFile.flush();
fin.close();
mSocketFile.close();
} catch (IOException e) {
Log.e(TAG, "connectFile:" + e.toString());
}
}
}.start();
}
这个方法要仔细看,我在这里新开了一个端口9090,然后进行文件的上传,其中我还在计算当前的进度为 progress += len,去乘以100/总大小fileLength就是我们当前的进度了,OK,这个时候他返回的是什么呢?这个时候设备端返回CMD_ACK_OTA_Upload_Success | CMD_ACK_OTA_Upload_Fault,我们又回到了接收的地方
} else if (result.equals("CMD_ACK_OTA_Upload_Success")) {
mHandler.sendEmptyMessage(UPLOAD_SUCCESS);
} else if (result.equals("CMD_ACK_OTA_Upload_Fault")) {
mHandler.sendEmptyMessage(UPLOAD_FAULT);
}
我在接收的地方判断了结果,然后继续回到handler里面
case UPLOAD_SUCCESS:
sendMsg("CMD_OTA_Update");
break;
case UPLOAD_FAULT:
Toast.makeText(MainActivity.this, "文件效验失败", Toast.LENGTH_SHORT).show();
break;
这里更容易,如果成功我就发送CMD_OTA_Update,失败我就提示失败,就是这么简单,到此流程就完全走了一遍了,虽然是伪代码,不过逻辑还是痛顺畅的,送上完整的伪代码
package com.liuguilin.ota_tcp_sample;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/** * - 1.检测内存卡上是否存在update.zip * - 2.进行加密效验(比如MD5,CRC32) * - 3.TCP(端口8080)发送CMD_UPDATE_LODING:校验码 * - 4.设备端回应CMD_UPDATE_LODING:0 | -1 * - 5.TCP(端口9090)发送文件 * - 6.设备端返回CMD_ACK_OTA_Upload_Success | CMD_ACK_OTA_Upload_Fault * - 7.TCP(端口8080)发送CMD_OTA_Update */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final String TAG = MainActivity.class.getSimpleName();
public static final int UPDATE_OK = 1001;
public static final int UPDATE_NO = 1002;
public static final int UPLOAD_SUCCESS = 1003;
public static final int UPLOAD_FAULT = 1004;
//tcp 8080
private Socket mSocket;
private InputStream mReader;
private OutputStream mWriter;
//tcp 9090
private Socket mSocketFile;
private InputStream mReaderFile;
private OutputStream mWriterFile;
private Button btnOta;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case UPDATE_OK:
connectFile();
break;
case UPDATE_NO:
Toast.makeText(MainActivity.this, "OTA未准备好", Toast.LENGTH_SHORT).show();
break;
case UPLOAD_SUCCESS:
sendMsg("CMD_OTA_Update");
break;
case UPLOAD_FAULT:
Toast.makeText(MainActivity.this, "文件效验失败", Toast.LENGTH_SHORT).show();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//开始连接
connect();
btnOta = (Button) findViewById(R.id.btnOta);
btnOta.setOnClickListener(this);
}
/** * TCP连接 */
private void connect() {
Log.i(TAG, "connect");
new Thread() {
@Override
public void run() {
try {
mSocket = new Socket("ip", 8080);
mReader = mSocket.getInputStream();
mWriter = mSocket.getOutputStream();
revMsg();
mWriter.flush();
} catch (IOException e) {
Log.e(TAG, "connect:" + e.toString());
}
}
}.start();
}
/** * 发送指令 * * @param msg */
private void sendMsg(final String msg) {
new Thread() {
@Override
public void run() {
try {
Log.i(TAG, "send:" + msg);
mWriter.write(msg.getBytes("utf-8"));
mWriter.flush();
} catch (Exception e) {
Log.e(TAG, "sendMsg:" + e.toString());
}
}
}.start();
}
/** * 接收 */
private void revMsg() {
new Thread() {
@Override
public void run() {
try {
while (true) {
byte[] mbyte = new byte[1024];
int temp = mReader.read(mbyte);
String result = new String(mbyte, 0, temp);
Log.i(TAG, "result:" + result);
if (result.startsWith("CMD_UPDATE_LODING")) {
String[] mStr = result.split(":");
switch (mStr[1]) {
case "0":
mHandler.sendEmptyMessage(UPDATE_OK);
break;
case "-1":
mHandler.sendEmptyMessage(UPDATE_OK);
break;
}
} else if (result.equals("CMD_ACK_OTA_Upload_Success")) {
mHandler.sendEmptyMessage(UPLOAD_SUCCESS);
} else if (result.equals("CMD_ACK_OTA_Upload_Fault")) {
mHandler.sendEmptyMessage(UPLOAD_FAULT);
}
}
} catch (Exception e) {
Log.e(TAG, "revMsg:" + e.toString());
}
}
}.start();
}
/** * 上传文件 */
private void connectFile() {
new Thread() {
@Override
public void run() {
try {
Log.i(TAG, "connectFile");
mSocketFile = new Socket("ip", 9090);
mReaderFile = mSocketFile.getInputStream();
mWriterFile = mSocketFile.getOutputStream();
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/update.zip");
FileInputStream fin = new FileInputStream(file);
OutputStream out = mSocketFile.getOutputStream();
long fileLength = file.length();
byte[] buf = new byte[2048];
int len = 0;
int progress = 0;
int progressAll = 0;
while ((len = fin.read(buf)) != -1) {
out.write(buf, 0, len);
progress += len;
progressAll = progress * 100 / (int) fileLength;
System.out.println("progressAll" + progressAll);
}
mSocketFile.shutdownInput();
mWriterFile.flush();
fin.close();
mSocketFile.close();
} catch (IOException e) {
Log.e(TAG, "connectFile:" + e.toString());
}
}
}.start();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnOta:
//1.检查是否存在update.zip
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/update.zip");
if (file.exists()) {
try {
sendMsg("CMD_UPDATE_LODING:" + MD5Utils.getMd5ByFile(file));
} catch (FileNotFoundException e) {
Log.e(TAG, "MD5 Error" + e.toString());
}
} else {
Toast.makeText(this, "未检测到固件", Toast.LENGTH_SHORT).show();
}
break;
}
}
}