提交给php服务的同一文件,相同的操作(客户端),接收和保存它只需要500毫秒,提交给Android的接收和保存,只需要50毫秒。
android avd:android电视api 25
android avd端口转发:android/platform-tools/adb forward tcp:11111 tcp:11111
客户是chrome:http://php.local.qidizi.com/i.php?html=1
此测试只有两个文件MainActivity.java
和i.php
。
package qidizi.t.myapplication;
import android.os.Build;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MainActivity extends AppCompatActivity {
final static int PORT = 11111;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
create_httpd();
}
private void debug(String msg) {
Log.d("qidizi_debug", msg);
}
private void create_httpd() {
new Thread(new Runnable() {
@Override
public void run() {
ServerSocket httpd;
try {
// 使用端口转发功能,把虚拟机avd端口转发到开发机的11111上,就可以使用 http://127.0.0.1:11111 来访问
// android/platform-tools/adb forward tcp:11111 tcp:11111
httpd = new ServerSocket(PORT, 1);
} catch (Exception e) {
debug("httpd-fail:" + e.getMessage());
return;
}
debug("httpd running");
//noinspection
while (true) {
long accept_a = System.currentTimeMillis();
debug("accept waiting " + accept_a);
debug(
"RELEASE " + android.os.Build.VERSION.RELEASE
+ ",model " + android.os.Build.MODEL
+ ",dev " + Build.DEVICE
+ ",brand " + android.os.Build.BRAND
+ ",board " + Build.BOARD
+ ",MANUFACTURER " + Build.MANUFACTURER
);
try (
Socket client = httpd.accept();
InputStream is = client.getInputStream()
) {
Log.d("qidizi_debug", "accept receive " + accept_a + ",cost " + (System.currentTimeMillis() - accept_a));
long while_a = System.currentTimeMillis();
try {
// get 支持的长度
int mark, index = 0, max_head = 1024;
int[] ints = new int[max_head];
int rn_rn = 0;
// 读取文件头时,使用单个byte读取
while (true) {
mark = is.read();
if (mark < 0)
throw new Exception("http head error");
if (index + 1 > max_head)
throw new Exception("http head too big");
ints[index] = mark;
index++;
if ('\n' == mark || '\r' == mark) {
rn_rn++;
if (4 == rn_rn) {
// 找到head与body分隔
break;
}
} else {
rn_rn = 0;
}
}
// 取首行
String head = new String(ints, 0, index).toLowerCase();
//noinspection UnusedAssignment
ints = null;
int body_size = 0;
String len_str = "content-length: ";
int len_a = head.indexOf(len_str);
if (len_a > -1) {
len_a += len_str.length();
// 不含数字前空格和后面换行符号
body_size = Integer.parseInt(
head.substring(len_a, head.indexOf('\r', len_a)), 10
);
}
debug("body size: " + body_size + ",read head cost " + (System.currentTimeMillis() - while_a));
if (head.startsWith("options /"))
debug("allow");
else if (head.startsWith("post /apk?"))
install_apk(is, body_size);
else
throw new Exception("action now support:" + head);
debug("process cost: " + (System.currentTimeMillis() - while_a));
} catch (Exception e) {
e.printStackTrace();
debug("error: " + e.getMessage());
} finally {
String body = "ok";
client.getOutputStream().write((
"HTTP/1.1 200 OK\r\n"
+ "Allow: PUT, GET, POST, OPTIONS\r\n"
+ "Content-Type: text/html;charset=utf-8\r\n"
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Headers: *\r\n"
+ "Content-Length: " + body.getBytes().length + "\r\n"
+ "Connection: Close\r\n"
+ "\r\n"
+ body
).getBytes());
}
} catch (Exception e) {
debug("new socket fail:" + e.getMessage());
e.printStackTrace();
}
Log.d("qidizi_debug", "while end,cost " + (System.currentTimeMillis() - accept_a));
}
}
}).start();
}
private void install_apk(InputStream is, int body_size) throws Exception {
// 上传文件
if (body_size < 1024)
throw new Exception("apk body < 1024");
if (null == is)
throw new Exception("http inputStream null");
long a = System.currentTimeMillis();
String apk_path = getCacheDir().getAbsolutePath() + "/tmp.apk";
File file = new File(apk_path);
try (OutputStream out = new FileOutputStream(file)) {
int mark;
long size = body_size, while_a = System.currentTimeMillis(),
loop_a = System.currentTimeMillis(), tip_times = 500000;
while (size > 0 && -1 != (mark = is.read())) {
// is.read() never return -1 ,don't use -1 != (mark = is.read()) && size > 0
out.write(mark);
--size;
if (0 == size % tip_times) {
debug(is.available() + " available,read " + tip_times + " (size = " + size + "),cost "
+ (System.currentTimeMillis() - loop_a));
loop_a = System.currentTimeMillis();
}
}
debug("read body cost: " + (System.currentTimeMillis() - while_a));
if (0 != size)
throw new Exception("http body broken,body-content-size " + body_size + ",only get " + size + "(" + (size * 1.0 / body_size) + "%)");
out.flush();
} catch (Exception e) {
throw new Exception("save fail:" + e.getMessage());
}
long body_time = (System.currentTimeMillis() - a) / 1000;
debug("upload success " + body_time + " s");
}
}
<?php
if (!empty($_GET['html'])) {
?>
use php <input type="checkbox" id="php"><br>
<input type="file" id="file">
<input type="button" onclick="upload()" value="upload">
<div id="tip"></div>
<script>
function ajax(data) {
let time_a = +new Date;
let done = false;
let xhr = new window.XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState === 4 && !done) {
done = true;
document.getElementById('tip').innerHTML = xhr.response + '<br>js cost ' + (+new Date - time_a) / 1000;
}
};
xhr.onabort = xhr.onerror = function () {
if (!done) {
done = true;
alert("connect fail:" + xhr.statusText + '[' + xhr.status + ']');
}
};
let url = 'http://';
url += document.getElementById('php').checked ? 'php.local.qidizi.com/i.php?' : '127.0.0.1:11111/apk';
url += '?r=' + +new Date;
xhr.onloadend = function () {
document.title = 'end';
};
document.title = 'request ...';
xhr.upload.addEventListener('progress', function (e) {
document.title = 'upload ' + (e.loaded / e.total).toFixed(2) * 100 + '%';
})
xhr.open('POST', url);
xhr.send(data);
}
function upload() {
let self = this;
let file = document.getElementById('file').files;
if (!file.length) {
alert('select file');
return;
}
file = file[0];
if (!/.apk$/i.test(file.name)) {
alert('only apk');
return;
}
let max_mb = 200;
if (file.size > 1024 * 1024 * max_mb) {
alert('must < ' + max_mb + 'MB');
return;
}
ajax(file);
}
</script>
<?php
exit;
}
$input_size = file_put_contents('a.apk', file_get_contents('php://input'));
echo 'input size ' . $input_size . '<br>';
echo 'a.apk size ' . filesize('a.apk') . '<br>';
D/qidizi_debug: httpd running
accept waiting 1595654479838
RELEASE 7.1.1,model sdk_google_atv_x86,dev generic_x86,brand generic_x86,board unknown,MANUFACTURER unknown
D/: HostConnection::get() New Host Connection established 0x8c1270c0, tid 12435
D/: HostConnection::get() New Host Connection established 0x8c127500, tid 12451
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 1
W/OpenGLRenderer: Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...
D/OpenGLRenderer: Swap behavior 0
D/EGL_emulation: eglCreateContext: 0x985c00e0: maj 3 min 0 rcv 3
D/EGL_emulation: eglMakeCurrent: 0x985c00e0: ver 3 0 (tinfo 0x9858df80)
I/ViewConfigCompat: Could not find method getScaledScrollFactor() on ViewConfiguration
D/EGL_emulation: eglMakeCurrent: 0x985c00e0: ver 3 0 (tinfo 0x9858df80)
D/qidizi_debug: accept receive 1595654479838,cost 116751
D/qidizi_debug: body size: 0,read head cost 4
allow
process cost: 4
D/qidizi_debug: while end,cost 116757
accept waiting 1595654596595
RELEASE 7.1.1,model sdk_google_atv_x86,dev generic_x86,brand generic_x86,board unknown,MANUFACTURER unknown
D/qidizi_debug: accept receive 1595654596595,cost 4
D/qidizi_debug: body size: 6382654,read head cost 5
D/qidizi_debug: 912340 available,read 500000 (size = 6000000),cost 3559
D/qidizi_debug: 941588 available,read 500000 (size = 5500000),cost 4533
D/qidizi_debug: 908948 available,read 500000 (size = 5000000),cost 4571
D/qidizi_debug: 896344 available,read 500000 (size = 4500000),cost 4912
D/qidizi_debug: 948440 available,read 500000 (size = 4000000),cost 4837
D/qidizi_debug: 914712 available,read 500000 (size = 3500000),cost 4630
D/qidizi_debug: 927004 available,read 500000 (size = 3000000),cost 4598
D/qidizi_debug: 896732 available,read 500000 (size = 2500000),cost 4566
D/qidizi_debug: 912480 available,read 500000 (size = 2000000),cost 4539
D/qidizi_debug: 935904 available,read 500000 (size = 1500000),cost 4548
D/qidizi_debug: 902496 available,read 500000 (size = 1000000),cost 4679
D/qidizi_debug: 500000 available,read 500000 (size = 500000),cost 4686
D/qidizi_debug: 0 available,read 500000 (size = 0),cost 4529
D/qidizi_debug: read body cost: 59189
D/qidizi_debug: upload success 59 s
process cost: 59199
D/qidizi_debug: while end,cost 59205
accept waiting 1595654655800
RELEASE 7.1.1,model sdk_google_atv_x86,dev generic_x86,brand generic_x86,board unknown,MANUFACTURER unknown
为什么?
如果仅使用int i = InputSream.read()
(不使用BufferedInputStream
)怎么解决?
谢谢!