为什么`android ServerSocket()。accept()。getInputStream()。read()`这么慢?

时间:2020-07-30 06:34:36

标签: android performance inputstream serversocket

提交给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.javai.php

文件内容

MainActivity.java

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");
    }
}

i.php

<?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>';


只需500毫秒即可快速发布到php服务;

enter image description here

发布到android java服务,非常慢,59s

enter image description here

logcat日志

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)怎么解决?
谢谢!

0 个答案:

没有答案