我使用的技術棧是:react-native(0.56.0)+ react-navigation + react-redux + ant-design + axios
我在做的ReactNative項目需要實現同一WIFI下跨平臺互傳文件功能,涉及原生模塊的功能
我實現這兩個功能使用的庫有
首先需要明白限制條件是在同一個局域網內(連接同一wifi),前期已經用umi開發了一組傳輸文件時準備在PC上展示的相關網頁並打包出來。
核心技術:
通過使用NanoHTTPD在手機上建立本地服務器實現數據傳輸。
爲了保證安全性,需要PC端輸入APP生成的驗證碼予以驗證。
整體流程:
1. 判斷是否連接WIFI並獲取ip地址
WifiManager是 Android 暴露給開發者使用的一個系統服務管理類, 其中包含對WiFi的響應的操作函數。
//獲取wifi服務
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
//判斷wifi是否開啓
if (!wifiManager.isWifiEnabled()) {
return null;
}
//判斷wifi是否連接
if (wifiManager.getConnectionInfo() == null) {
return null;
}
// ip地址獲取
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
return (ipAddress & 0xFF) + "." +
((ipAddress >> 8) & 0xFF) + "." +
((ipAddress >> 16) & 0xFF) + "." +
(ipAddress >> 24 & 0xFF);
2. 啓動服務
主要邏輯繼承NanoHTTPD,根據你的需求重寫serve方法就可以了
在我寫的this.openHttp(port,code,mainKey,toPage)中,code是驗證碼,mainkey是自定義需要傳輸的部分內容,toPage是判斷是由手機向PC還是PC向手機傳輸文件。
// 隨機數防止端口被佔用,如果被佔用catch後stopServer
int port = (int)((Math.random()*9+1)*1000);
final String host = ip + ":" + port;
this.openHttp(port,code,mainKey,toPage);
promise.resolve("http://" + host);
//
private void openHttp(int port,String code,ReadableMap mainKey, String toPage) throws IOException {
httpServer = new HttpServer(getReactApplicationContext(), port,code,mainKey,toPage);
httpServer.start();
}
3. 自定義的serve,返回網頁或者響應請求
根據不同的路徑返回不同的數據
將文件轉化的字符串或者數組作爲響應內容返回return Response.newFixedLengthResponse(字符串)
或者return Response.newFixedLengthResponse(狀態碼,mime類型,字節數組)
public Response serve(IHTTPSession session){
String uri = session.getUri();
// 根據html的路徑返回相應的操作
if (uri.equals("/postValidatePhoneCode")) {
return validatecode(session);
}else if (uri.equals("/upload")){
return renderUpload(session);
}else if (uri.equals("/getPhoneKey")){
return getKey(session);
}else if (uri.equals("/device")){
return download(session);
}else if (uri.indexOf(".json")!=-1){
return downloadKey(session);
} else {
return renderRes(session);
}
}
renderRes
不帶路徑的url根據toPage參數返回html,保證PC訪問到初始頁面
private Response renderRes(IHTTPSession session) {
String uri = session.getUri();
String filename;
if (uri.equals("/")) filename = "index.html";
else filename = uri.substring(1);
// 定義mineType
String mimeType = null;
if (filename.endsWith(".html") || filename.endsWith(".htm")) {
mimeType = "text/html";
} else if (filename.endsWith(".js")) {
mimeType = "text/javascript";
} else if (filename.endsWith(".css")) {
mimeType = "text/css";
}
else if (filename.endsWith(".ico")) {
mimeType = "image/x-icon";
}
if (mimeType == null) {
return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "未知文件類型[" + filename + "]");
}
InputStreamReader inputStreamReader = null;
BufferedReader reader = null;
try {
// filename = "Resources"+filename;
inputStreamReader = new InputStreamReader(context.getAssets().open(filename));
reader = new BufferedReader(inputStreamReader);
StringBuilder returnMsg = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
returnMsg.append(line);
}
return newFixedLengthResponse(Response.Status.OK, mimeType, returnMsg.toString());
} catch (IOException e) {
Utils.closeSilent(reader);
Utils.closeSilent(inputStreamReader);
e.printStackTrace();
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", e.getMessage());
}
}
validatecode 驗證驗證碼
session.parseBody(files);
String param=files.get("postData");
JSONObject json = new JSONObject(param);
String code = json.getString("code");
if (code.equals(this.code+"")){
Map map = new HashMap();
map.put("validate",true);
map.put("toPage",this.toPage);
JSONObject jsonObj=new JSONObject(map);
return newFixedLengthResponse(Response.Status.OK, "text/plain", jsonObj.toString());
}else {
Map map = new HashMap();
map.put("validate",false);
JSONObject jsonObj=new JSONObject(map);
return newFixedLengthResponse(Response.Status.REDIRECT_SEE_OTHER, "text/plain", jsonObj.toString());
}
驗證碼正確則跳轉到相應頁面,進行下載或上傳
download 上傳文件到PC
private Response downloadKey(IHTTPSession session) {
try {
File distFile = new File(this.context.getFilesDir().getAbsolutePath() + File.separatorChar + "wifi" + File.separatorChar + this.mainKey.getString("key_name") + ".json");
//判斷該文件的所屬文件夾存不存在,不存在則創建文件夾
if(!distFile.getParentFile().exists()){
distFile.getParentFile().mkdirs();
}
//創建文件
if (!distFile.exists()){
//如果不存在file文件,則創建
distFile.createNewFile();
}
//創建字符流(使用字節流比較麻煩)
FileWriter fw = new FileWriter(distFile);
Map map = new HashMap();
map.put("address",this.mainKey.getString("pubaddress"));
map.put("algo","0x03");
map.put("encrypted",this.mainKey.getString("privateKeystr").substring(2,this.mainKey.getString("privateKeystr").length()));
map.put("version","2.0");
map.put("privateKeyEncrypted",false);
JSONObject jsonObj=new JSONObject(map);
fw.write(jsonObj.toString());
//這裏要說明一下,write方法是寫入緩存區,並沒有寫進file文件裏面,要使用flush方法才寫進去
fw.flush();
//關閉資源
fw.close();
FileInputStream fis = new FileInputStream(distFile.getPath());
return newFixedLengthResponse(Response.Status.OK, "application/octet-stream", fis,fis.available());
} catch (FileNotFoundException e) {
e.printStackTrace();
return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", e.getMessage());
} catch (IOException e) {
e.printStackTrace();
return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", e.getMessage());
}
}
upload 從PC端下載文件
private void sendUploadFileToClient(IHTTPSession session) throws IOException, ResponseException {
Map<String, String> files = new HashMap<>();
session.parseBody(files);
Map<String, List<String>> parameters = session.getParameters();
WritableArray params = Arguments.createArray();
String str = "";
for (String key : files.keySet()) {
List<String> nameStr = parameters.get(key);
if (nameStr == null) continue;
File tempFile = new File(files.get(key));
File distFile = new File(this.context.getFilesDir().getAbsolutePath() + File.separatorChar + "wifi" + File.separatorChar + tempFile.getName());
Utils.copyFile(tempFile, distFile);//NanoHttpd 會自動在請求完畢刪除掉文件 所以克隆一份
WritableMap map = Arguments.createMap();
map.putString("name", nameStr.get(0));
map.putString("path", distFile.getPath());
params.pushMap(map);
str = this.readTxt(distFile.getPath());
}
// 頁面監聽WifiServer.FILE_UPLOAD_NEW事件,接收從PC導入的數據
this.context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(WifiServer.FILE_UPLOAD_NEW, str);
}
private String readTxt(String filePath) {
StringBuilder result = new StringBuilder();
try {
BufferedReader bfr = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath)), "UTF-8"));
String lineTxt = null;
while ((lineTxt = bfr.readLine()) != null) {
result.append(lineTxt).append("\n");
}
bfr.close();
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
4. 關閉本地服務
this.httpServer.stop();
以上就是手機端起簡易服務器與PC端實現互傳的流程,涉及到很多java文件讀取的方法,我本來想使用nanohttpd的webserver解決,但是發現他只能展示頁面,不好修改(它本身就是依賴nanohttpd完成的,更多信息在nanohttpd的官方文檔)。讀了源碼也有些一知半解,最後在公司前輩的幫助下完成的該功能。
感謝閱讀,有疑問請留言,我會盡我所能解答的。(要源碼的就別留了)