位图创建的优化

时间:2019-05-20 15:09:37

标签: android service bitmap imageview

我正在编写一个应用程序,该应用程序将从远程TCP连接实时接收的图像序列呈现到ImageView元素中。 stream 由以PGM格式编码并以9Hz发送的单帧组成。我坚信,使用背景Service可以轻松处理这样的非常低的帧速率,该背景将完全解码的位图发送到我的MainActivity

这是我的VideoService(我只发布了run()方法,因为我认为这只是一些有趣的方法之一)

    public void run() {
        InetAddress serverAddr = null;

        try {
            serverAddr = InetAddress.getByName(VIDEO_SERVER_ADDR);
        } catch (UnknownHostException e) {
            Log.e(getClass().getName(), e.getMessage());
            e.printStackTrace();
            return;
        }

        Socket socket = null;
        BufferedReader reader = null;

        do {
            try {
                socket = new Socket(serverAddr, VIDEO_SERVER_PORT);

                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                boolean frameStart = false;

                LinkedList<String> frameList = new LinkedList<>();

                while (keepRunning) {
                    final String message = reader.readLine();

                    if (!frameStart && message.startsWith("F"))
                        frameStart = true;
                    else if (frameStart && message.startsWith("EF")) {
                        frameStart = false;

                        final Bitmap bitmap = Bitmap.createBitmap(IR_FRAME_WIDTH, IR_FRAME_HEIGHT, Bitmap.Config.ARGB_8888);
                        final Canvas canvas = new Canvas(bitmap);

                        final String[] data = frameList.toArray(new String[frameList.size()]);

                        canvas.drawBitmap(bitmap, 0, 0, null);

                        //Log.d(this.getClass().getName(), "IR FRAME COLLECTED");

                        if ((data.length - 6) == IR_FRAME_HEIGHT) {
                            float grayScaleRatio = Float.parseFloat(data[2].trim()) / 255.0f;

                            for (int y = 0; y < IR_FRAME_HEIGHT; y++) {
                                final String line = data[y + 3];
                                final String[] points = line.split("\\s+");

                                if (points.length == IR_FRAME_WIDTH) {
                                    for (int x = 0; x < IR_FRAME_WIDTH; x++) {
                                        final float grayLevel = Float.parseFloat(points[x]) / grayScaleRatio;

                                        Paint paint = new Paint();

                                        paint.setStyle(Paint.Style.FILL);

                                        final int level = (int)grayLevel;

                                        paint.setColor(Color.rgb(level, level, level));

                                        canvas.drawPoint(x, y, paint);
                                    }
                                } else
                                    Log.d(this.getClass().getName(), "Malformed line");
                            }

                            final Intent messageIntent = new Intent();

                            messageIntent.setAction(VIDEO_BROADCAST_KEY);

                            ByteArrayOutputStream stream = new ByteArrayOutputStream();

                            bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
                            bitmap.recycle();
                            messageIntent.putExtra(VIDEO_MESSAGE_KEY, stream.toByteArray());
                            stream.close();
                            sendBroadcast(messageIntent);
                        } else
                            Log.d(this.getClass().getName(), "Malformed data");

                        frameList.clear();
                    } else if (frameStart)
                        frameList.add(message);
                }

                Thread.sleep(VIDEO_SERVER_RESPAWN);

            } catch (Throwable e) {
                Log.e(getClass().getName(), e.getMessage());
                e.printStackTrace();
            }
        } while (keepRunning);

        if (socket != null) {
            try {
                socket.close();
            } catch (Throwable e) {
                Log.e(getClass().getName(), e.getMessage());
                e.printStackTrace();
            }
        }
    }

message是一行来自以下文本的行:

F
P2
160 120
1226
193 141 158 152 193 186 171 177 186 160 195 182 ... (160 times)
                         .
                         . (120 lines)
                         .
278 248 253 261 257 284 310 304 304 272 227 208 ... (160 times)


EF

在MainActivity中,我通过以下代码来处理这个问题:

class VideoReceiver extends BroadcastReceiver {
    final public Queue<Bitmap> imagesQueue = new LinkedList<>();

    @Override
    public void onReceive(Context context, Intent intent) {

        try {
            //Log.d(getClass().getName(), "onReceive() called");

            final byte[] data = intent.getByteArrayExtra(VideoService.VIDEO_MESSAGE_KEY);

            final Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);

            imagesQueue.add(bitmap);

            runOnUiThread(updateVideoTask);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

updateVideoTask任务的定义如下:

    updateVideoTask = new Runnable() {
        public void run() {
            if (videoReceiver == null) return;

            if (!videoReceiver.imagesQueue.isEmpty())
            {
                final Bitmap image = videoReceiver.imagesQueue.poll();

                if (image == null) return;

                videoView.setImageBitmap(image);

                Log.d(this.getClass().getName(), "Images to spool: " + videoReceiver.imagesQueue.size());
            }
        }
    };

不幸的是,当我运行该应用程序时,我发现帧速率非常低并且延迟很大。我不能争论发生了什么。 我从 logcat 获得的唯一提示是这些行:

2019-05-20 16:37:08.817 29566-29580/it.tux.gcs I/art: Background sticky concurrent mark sweep GC freed 88152(3MB) AllocSpace objects, 3(52KB) LOS objects, 22% free, 7MB/10MB, paused 3.937ms total 111.782ms
2019-05-20 16:37:08.832 29566-29587/it.tux.gcs D/skia: Encode PNG Singlethread :      13003 us, width=160, height=120

即使有所有这些延迟的总和(140毫秒),应用程序也应保持5Hz以上的帧频,而变得0.25Hz甚至更糟。

经过一番调查,我发现自己动了:

Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);

嵌套循环之外的内容阻止了如此频繁地调用GC,我发现此行中的另一个主要延迟源:

final String[] points = line.split("\\s+");

它每次烧掉2毫秒,所以我决定去一些不太聪明但更快的东西:

final String[] points = line.split(" ");

无论如何还是不够的。.之间的代码:

canvas.drawBitmap(bitmap, 0, 0, null);

sendBroadcast(messageIntent);

仍然消耗200毫秒以上的时间...我该如何做得更好呢?

1 个答案:

答案 0 :(得分:2)

首先,由于某种原因,您正在通过BroadcastReceiver提交具有新图像更改的结果。您可以显着提高整体速度,但是可以删除此逻辑。并通过bound features将通信替换为Service

    // Bind to LocalService
    Intent intent = new Intent(this, LocalService.class);
    bindService(intent, connection, Context.BIND_AUTO_CREATE);

然后接收连接。

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection connection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className,
            IBinder service) {
        // We've bound to LocalService, cast the IBinder and get LocalService instance
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        mBound = false;
    }
};

然后使用Service绑定程序实例来订阅Activity,并在Service中使用callback来发布新的数据字节。