Android性能优化:直播推流使用对象池

简介: Android性能优化:直播推流使用对象池

#性能优化之Android应用内存优化实战#

根据Android Handler里面的Message实现思想,实现自定义的对象池

public class RTMPPackage {
    private static final String TAG = "RTMPPackage";
 
    public static final int RTMP_PACKET_TYPE_VIDEO = 0;
    public static final int RTMP_PACKET_TYPE_AUDIO_HEAD = 1;
    public static final int RTMP_PACKET_TYPE_AUDIO_DATA = 2;
 
    private byte[] buffer; // 图像数据
    private int type;
    private long tms;
    private int size;
 
    public static RTMPPackage EMPTY_PACKAGE = new RTMPPackage();
 
 
    public byte[] getBuffer() {
        return buffer;
    }
 
    public void setBuffer(byte[] buffer) {
        this.buffer = buffer;
    }
 
    public int getType() {
        return type;
    }
 
    public void setType(int type) {
        this.type = type;
    }
 
    public void setTms(long tms) {
        this.tms = tms;
    }
 
    public long getTms() {
        return tms;
    }
 
    public int getSize() {
        return size;
    }
 
    public void setSize(int size) {
        this.size = size;
    }
 
    RTMPPackage next;
    //同步对象
    public static final Object sPoolSync = new Object();
    private static RTMPPackage sPool; // 链表第一个元素(对象池)
    private static int sPoolSize = 0; //对象池中对象的个数
 
    private static final int MAX_POOL_SIZE = 50;
 
 
    public static RTMPPackage obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                RTMPPackage m = sPool; //链表第一个元素,返回出去 复用
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new RTMPPackage();
    }
 
 
    public void recycle() {
        buffer = null;
        type = -1;
        tms = -1;
        size = 0;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
 
}

相关类:

VideoCodec.java

public class VideoCodec extends Thread {
 
    private ArrayPool arrayPool;
    private ScreenLive screenLive;
    private MediaCodec mediaCodec;
    private VirtualDisplay virtualDisplay;
    private boolean isLiving;
    private long timeStamp;
    private long startTime;
    private MediaProjection mediaProjection;
 
    public VideoCodec(ScreenLive screenLive, ArrayPool arrayPool) {
        this.screenLive = screenLive;
        this.arrayPool = arrayPool;
    }
 
 
    @Override
    public void run() {
        isLiving = true;
        mediaCodec.start();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
 
        while (isLiving) {
            if (timeStamp != 0) {
                //2000毫秒后 mediacodec虽然设置了关键帧间隔,但是没用 需要手动强制请求
                if (System.currentTimeMillis() - timeStamp >= 2_000) {
                    Bundle params = new Bundle();
                    //立即刷新 让下一帧是关键帧
                    params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
                    mediaCodec.setParameters(params);
                    timeStamp = System.currentTimeMillis();
                }
            } else {
                timeStamp = System.currentTimeMillis();
            }
            int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10);
            if (index >= 0) {
                ByteBuffer buffer = mediaCodec.getOutputBuffer(index);
                byte[] outData = arrayPool.get(bufferInfo.size);
                buffer.get(outData, 0, bufferInfo.size);
                //sps pps
//                ByteBuffer sps = mediaCodec.getOutputFormat().getByteBuffer
//                        ("csd-0");
//                ByteBuffer pps = mediaCodec.getOutputFormat().getByteBuffer
//                        ("csd-1");
                if (startTime == 0) {
                    // 微妙转为毫秒
                    startTime = bufferInfo.presentationTimeUs / 1000;
                }
 
                RTMPPackage rtmpPackage = RTMPPackage.obtain();
                rtmpPackage.setBuffer(outData);
                rtmpPackage.setSize(bufferInfo.size);
                rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_VIDEO);
                long tms = (bufferInfo.presentationTimeUs / 1000) - startTime;
                rtmpPackage.setTms(tms);
                screenLive.addPackage(rtmpPackage); //入队列
                mediaCodec.releaseOutputBuffer(index, false);
            }
        }
        isLiving = false;
        startTime = 0;
        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec = null;
        virtualDisplay.release();
        virtualDisplay = null;
        mediaProjection.stop();
        mediaProjection = null;
    }
 
 
    public void startLive(MediaProjection mediaProjection) {
        this.mediaProjection = mediaProjection;
        // 配置编码参数
        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,
                640,
                480);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        format.setInteger(MediaFormat.KEY_BIT_RATE, 500_000);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
        try {
            // 创建编码器
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            // 从编码器创建一个画布, 画布上的图像会被编码器自动编码
            Surface surface = mediaCodec.createInputSurface();
 
 
            virtualDisplay = mediaProjection.createVirtualDisplay(
                    "screen-codec",
                    640, 480, 1,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                    surface, null, null);
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
 
        start();
    }
 
    public void stopLive() {
        isLiving = false;
        try {
            join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

AudioCodec.java

public class AudioCodec extends Thread {
 
    private final ScreenLive screenLive;
    private final ArrayPool arrayPool;
 
 
    private MediaCodec mediaCodec;
    private AudioRecord audioRecord;
    private boolean isRecoding;
 
    private long startTime;
    private int minBufferSize;
 
    public AudioCodec(ScreenLive screenLive, ArrayPool arrayPool) {
        this.screenLive = screenLive;
        this.arrayPool = arrayPool;
    }
 
 
    @Override
    public void run() {
        isRecoding = true;
        RTMPPackage rtmpPackage = new RTMPPackage();
        byte[] audioDecoderSpecificInfo = {0x12, 0x08};
        rtmpPackage.setBuffer(audioDecoderSpecificInfo);
        rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_HEAD);
        screenLive.addPackage(rtmpPackage);
        audioRecord.startRecording();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        byte[] buffer = new byte[minBufferSize];
 
        while (isRecoding) {
            int len = audioRecord.read(buffer, 0, buffer.length);
            if (len <= 0) {
                continue;
            }
            //立即得到有效输入缓冲区
            int index = mediaCodec.dequeueInputBuffer(0);
            if (index >= 0) {
                ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
                inputBuffer.clear();
                inputBuffer.put(buffer, 0, len);
                //填充数据后再加入队列
                mediaCodec.queueInputBuffer(index, 0, len,
                        System.nanoTime() / 1000, 0);
            }
            index = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
 
            while (index >= 0 && isRecoding) {
                ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(index);
                byte[] outData = arrayPool.get(bufferInfo.size);
                outputBuffer.get(outData, 0, bufferInfo.size);
 
                if (startTime == 0) {
                    startTime = bufferInfo.presentationTimeUs / 1000;
                }
                rtmpPackage = RTMPPackage.obtain();
                rtmpPackage.setBuffer(outData);
                rtmpPackage.setSize(bufferInfo.size);
                rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_DATA);
                long tms = (bufferInfo.presentationTimeUs / 1000) - startTime;
                rtmpPackage.setTms(tms);
                screenLive.addPackage(rtmpPackage);
                mediaCodec.releaseOutputBuffer(index, false);
                index = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            }
        }
        audioRecord.stop();
        audioRecord.release();
        audioRecord = null;
 
        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec = null;
        startTime = 0;
        isRecoding = false;
    }
 
    public void startLive() {
 
        try {
 
            /**
             * 获得创建AudioRecord所需的最小缓冲区
             * 采样+单声道+16位pcm
             */
            minBufferSize = AudioRecord.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);
            /**
             * 创建录音对象
             * 麦克风+采样+单声道+16位pcm+缓冲区大小
             */
            audioRecord = new AudioRecord(
                    MediaRecorder.AudioSource.MIC, 44100,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, minBufferSize);
 
            MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100,
                    1);
            format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel
                    .AACObjectLC);
            format.setInteger(MediaFormat.KEY_BIT_RATE, 64_000);
            format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, minBufferSize * 2);
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mediaCodec.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        start();
    }
 
    public void stopLive() {
        isRecoding = false;
        try {
            join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
}

ScreenLive.java

public class ScreenLive implements Runnable {
 
    static {
        System.loadLibrary("native-lib");
    }
 
    private String url;
    private MediaProjectionManager mediaProjectionManager;
    private boolean isLiving;
    private LinkedBlockingQueue<RTMPPackage> queue = new LinkedBlockingQueue<>();
    private MediaProjection mediaProjection;
 
 
    public void startLive(Activity activity, String url) {
        this.url = url;
        // 投屏管理器
        this.mediaProjectionManager = (MediaProjectionManager) activity
                .getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        // 创建截屏请求intent
        Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
        activity.startActivityForResult(captureIntent, 100);
    }
 
    public void stoptLive() {
        addPackage(RTMPPackage.EMPTY_PACKAGE);
        isLiving = false;
    }
 
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 用户授权
        if (requestCode == 100 && resultCode == Activity.RESULT_OK) {
            // 获得截屏器
            mediaProjection = mediaProjectionManager.getMediaProjection
                    (resultCode, data);
            LiveTaskManager.getInstance().execute(this);
        }
    }
 
    public void addPackage(RTMPPackage rtmpPackage) {
        if (!isLiving) {
            return;
        }
        queue.add(rtmpPackage);
    }
 
    private static final String TAG = "ScreenLive";
 
    @Override
    public void run() {
        //1、连接服务器  斗鱼rtmp服务器
        if (!connect(url)) {
            disConnect();
            return;
        }
        Log.i(TAG, "连接成功 准备推流 ==========================");
        isLiving = true;
        ArrayPool arrayPool = new LruArrayPool();
        VideoCodec videoCodec = new VideoCodec(this,arrayPool);
        videoCodec.startLive(mediaProjection);
        AudioCodec audioCodec = new AudioCodec(this,arrayPool);
        audioCodec.startLive();
        boolean isSend = true;
 
        while (isLiving && isSend) {
            RTMPPackage rtmpPackage = null;
            try {
                checkDrop();
                rtmpPackage = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (null == rtmpPackage) {
                break;
            }
            byte[] buffer = rtmpPackage.getBuffer();
            if (buffer != null && buffer.length != 0) {
                isSend = sendData(buffer, rtmpPackage.getSize(),
                        rtmpPackage.getType(), rtmpPackage.getTms());
//                Log.i(TAG, "sendData: ==========================" + isSend);
            }
            arrayPool.put(buffer);
            rtmpPackage.recycle();
        }
        isLiving = false;
        videoCodec.stopLive();
        audioCodec.stopLive();
        queue.clear();
        disConnect();
    }
 
    private void checkDrop() throws InterruptedException {
        while (queue.size() > 200) {
            queue.take();
        }
    }
 
    /**
     * 与斗鱼服务器建立rtmp连接
     *
     * @param url
     * @return
     */
    private native boolean connect(String url);
 
    private native void disConnect();
 
    /**
     * 发送音视频数据到c
     *
     * @param data
     * @param len
     * @param type
     * @param tms
     * @return
     */
    private native boolean sendData(byte[] data, int len, int type, long tms);
 
}

自定义的对象池关键接口和实现:

public interface ArrayPool {
 
    byte[] get(int len);
 
    void put(byte[] data);
 
}
 
public class LruArrayPool implements ArrayPool {
 
    private static final String TAG = "ArrayPool";
 
    public static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;
    private int maxSize;
 
    private LruMap<byte[]> lruMap = new LruMap<>();
 
    // key:byte[]长度  value:个数!
    private TreeArray map = new TreeArray();
    private int currentSize;
 
    public LruArrayPool() {
        this(ARRAY_POOL_SIZE_BYTES);
    }
 
    public LruArrayPool(int maxSize) {
        this.maxSize = maxSize;
        currentSize = 0;
    }
 
    /**
     * @param len
     * @return
     */
    @Override
    public synchronized byte[] get(int len) {
        //获得等于或大于比len大同时最接近len的key
        int key = map.ceilingKey(len);
        if (key != 0) {
            byte[] bytes = lruMap.get(key);
            if (bytes != null) {
                currentSize -= bytes.length;
                //计数器-1
                decrementArrayOfSize(key);
                return bytes;
            }
        }
        Log.i(TAG, "get: new");
        return new byte[len];
    }
 
    @Override
    public synchronized void put(byte[] data) {
        if (data == null || data.length == 0 || data.length > maxSize) return;
        int length = data.length;
        lruMap.put(length, data);
        //value :个数
        int current = map.get(length);
        //计数器+1
        map.put(length, current == 0 ? 1 : current + 1);
 
        currentSize += length;
        evict();
    }
 
    private void evict() {
        while (currentSize > maxSize) {
            byte[] evicted = lruMap.removeLast();
            currentSize -= evicted.length;
            //计数器-1
            decrementArrayOfSize(evicted.length);
        }
    }
 
    private void decrementArrayOfSize(int key) {
        int current = map.get(key);
        if (current == 1) {
            map.delete(key);
        } else {
            map.put(key, current - 1);
        }
    }
 
    @Override
    public String toString() {
        return "ArrayPoolCustom{" +
                "lruMap=" + lruMap +
                '}';
    }
}
 
public class LruMap<V> {
    private LinkedEntry<V> head = new LinkedEntry<>(0);
    private final SparseArray<LinkedEntry<V>> keyToEntry = new SparseArray<>();
 
    public void put(int key, V value) {
        LinkedEntry<V> entry = keyToEntry.get(key);
        if (entry == null) {
            entry = new LinkedEntry<>(key);
            keyToEntry.put(key, entry);
        }
        makeHead(entry);
        entry.add(value);
    }
 
    public V get(int key) {
        LinkedEntry<V> entry = keyToEntry.get(key);
        if (entry != null) {
            makeHead(entry);
            return entry.removeLast();
        }
        return null;
    }
 
 
    private void makeHead(LinkedEntry<V> entry) {
        // 把自己前面和后面的连接到一起
        entry.prev.next = entry.next;
        entry.next.prev = entry.prev;
        //把自己放到 head 后面第一个
        entry.prev = head;
        entry.next = head.next;
 
        entry.next.prev = entry;
        entry.prev.next = entry;
    }
 
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("GroupedLinkedMap( ");
        LinkedEntry<V> current = head.next;
        boolean hadAtLeastOneItem = false;
        while (!current.equals(head)) {
            hadAtLeastOneItem = true;
            sb.append('{').append(current.key).append(':').append(current.size()).append("}, ");
            current = current.next;
        }
        if (hadAtLeastOneItem) {
            sb.delete(sb.length() - 2, sb.length());
        }
        return sb.append(" )").toString();
    }
 
    public V removeLast() {
        LinkedEntry<V> last = head.prev;
 
        while (!last.equals(head)) {
            //移除链表最末尾的LinkedEntry中的一个byte数组
            V removed = last.removeLast();
            //如果最后一个LinkedEntry没有byte数组缓存,则将最后一个LinkedEntry移除然后循环
            if (removed != null) {
                return removed;
            } else {
                last.prev.next = last.next;
                last.next.prev = last.prev;
                keyToEntry.remove(last.key);
            }
            last = last.prev;
        }
        return null;
    }
 
    private static class LinkedEntry<V> {
        private final int key;
        private List<V> values;
        LinkedEntry<V> next;
        LinkedEntry<V> prev;
 
        LinkedEntry(int key) {
            this.key = key;
            prev = next = this;
        }
 
        @Nullable
        public V removeLast() {
            final int valueSize = size();
            return valueSize > 0 ? values.remove(valueSize - 1) : null;
        }
 
        public int size() {
            return values != null ? values.size() : 0;
        }
 
        public void add(V value) {
            if (values == null) {
                values = new ArrayList<>();
            }
            values.add(value);
        }
    }
}
 
public class TreeArray {
 
    public static final int[] INT = new int[0];
 
    private int[] mKeys;
    private int[] mValues;
    private int mSize;
 
    public TreeArray() {
        this(10);
    }
 
 
    public TreeArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = INT;
            mValues = INT;
        } else {
            mKeys = new int[initialCapacity];
            mValues = new int[mKeys.length];
        }
        mSize = 0;
    }
 
    @Override
    public TreeArray clone() {
        TreeArray clone = null;
        try {
            clone = (TreeArray) super.clone();
            clone.mKeys = mKeys.clone();
            clone.mValues = mValues.clone();
        } catch (CloneNotSupportedException cnse) {
        }
        return clone;
    }
 
 
    public int get(int key) {
        return get(key, 0);
    }
 
    static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;
 
        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];
 
            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;
            }
        }
        return ~lo;
    }
 
 
    public int get(int key, int valueIfKeyNotFound) {
        int i = binarySearch(mKeys, mSize, key);
 
        if (i < 0) {
            return valueIfKeyNotFound;
        } else {
            return mValues[i];
        }
    }
 
 
    public void delete(int key) {
        int i = binarySearch(mKeys, mSize, key);
 
        if (i >= 0) {
            removeAt(i);
        }
    }
 
 
    public void removeAt(int index) {
        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
        mSize--;
    }
 
    public static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
    }
 
    public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;
 
        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
 
        int[] newArray = new int[growSize(currentSize)];
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }
 
    public void put(int key, int value) {
        int i = binarySearch(mKeys, mSize, key);
 
        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;
 
            mKeys = insert(mKeys, mSize, i, key);
            mValues = insert(mValues, mSize, i, value);
            mSize++;
        }
    }
 
 
    public int size() {
        return mSize;
    }
 
 
    public int keyAt(int index) {
        return mKeys[index];
    }
 
 
    public int valueAt(int index) {
        return mValues[index];
    }
 
 
    @Override
    public String toString() {
        if (size() <= 0) {
            return "{}";
        }
 
        StringBuilder buffer = new StringBuilder(mSize * 28);
        buffer.append('{');
        for (int i = 0; i < mSize; i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            int key = keyAt(i);
            buffer.append(key);
            buffer.append('=');
            int value = valueAt(i);
            buffer.append(value);
        }
        buffer.append('}');
        return buffer.toString();
    }
 
 
    public int ceilingKey(int key) {
        int r = -1;
        for (int mKey : mKeys) {
            if (mKey >= key) {
                return mKey;
            }
        }
        return r;
    }
}

自定义线程池:

public class LiveTaskManager {
 
 
    private static volatile LiveTaskManager instance;
 
    private ThreadPoolExecutor THREAD_POOL_EXECUTOR;
 
 
    private LiveTaskManager() {
        THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }
 
    public static LiveTaskManager getInstance() {
        if (instance == null) {
            synchronized (LiveTaskManager.class) {
                if (instance == null) {
                    instance = new LiveTaskManager();
                }
            }
        }
        return instance;
    }
 
 
    public void execute(Runnable runnable) {
        THREAD_POOL_EXECUTOR.execute(runnable);
    }
 
}

在MainActivity中使用如下:

public class MainActivity extends AppCompatActivity {
 
    private ScreenLive mScreenLive;
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.RECORD_AUDIO}, 10);
 
        Handler handler = new Handler();
//        Message message1 = new Message();
        Message message = handler.obtainMessage(); // what = 1, what =2 ,what = 2
//        message.what = 2;
        handler.sendMessage(message);
 
 
    }
 
 
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100) {
            mScreenLive.onActivityResult(requestCode, resultCode, data);
        }
    }
 
    public void startLive(View view) {
        mScreenLive = new ScreenLive();
        mScreenLive.startLive(this,
                "rtmp://sendtc3a.douyu.com/live/6918788rd3WwQNSV?wsSecret=e4661e772c05bc58c8f08b13eb6df517&wsTime=6319e048&wsSeek=off&wm=0&tw=0&roirecognition=0&record=flv&origin=tct");
    }
 
    public void stopLive(View view) {
        mScreenLive.stoptLive();
    }
 
}

布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="startLive"
        android:text="开始直播"
        />
 
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="stopLive"
        android:text="停止直播"
        />
 
 
</LinearLayout>
相关文章
|
1月前
|
设计模式 算法 前端开发
Android面经分享,失业两个月,五一节前拿到Offer,设计思想与代码质量优化+程序性能优化+开发效率优化
Android面经分享,失业两个月,五一节前拿到Offer,设计思想与代码质量优化+程序性能优化+开发效率优化
|
1月前
|
缓存 Java Android开发
安卓应用性能优化实践
【5月更文挑战第9天】在移动开发领域,应用的性能是决定用户体验的关键因素之一。特别是对于安卓平台,由于设备的多样性,性能优化变得尤为重要。本文将深入探讨针对安卓应用的性能优化策略,从内存管理、多线程处理到布局优化等方面提供实用的技术建议和最佳实践,帮助开发者提升应用的流畅度与响应速度。
|
16天前
|
Java Android开发 iOS开发
深入探讨移动操作系统的性能优化:安卓与iOS的对比分析
在现代移动设备中,操作系统的性能优化至关重要。本文从系统架构、内存管理、电池续航和应用程序运行效率等多个维度,深入探讨了安卓(Android)和iOS两大主流移动操作系统的优化策略及其实际效果,旨在为开发者和用户提供更清晰的了解和选择依据。
28 0
|
1月前
|
测试技术 Android开发
Android开发规范,性能优化,Android面试2024
Android开发规范,性能优化,Android面试2024
|
1月前
|
Android开发
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
Android WindowFeature小探究,Android客户端Web页面通用性能优化实践
|
7天前
|
缓存 JSON 网络协议
Android面试题:App性能优化之电量优化和网络优化
这篇文章讨论了Android应用的电量和网络优化。电量优化涉及Doze和Standby模式,其中应用可能需要通过用户白名单或电池广播来适应限制。Battery Historian和Android Studio的Energy Profile是电量分析工具。建议减少不必要的操作,延迟非关键任务,合并网络请求。网络优化包括HTTPDNS减少DNS解析延迟,Keep-Alive复用连接,HTTP/2实现多路复用,以及使用protobuf和gzip压缩数据。其他策略如使用WebP图像格式,按网络质量提供不同分辨率的图片,以及启用HTTP缓存也是有效手段。
29 9
|
8天前
|
XML 监控 安全
Android App性能优化之卡顿监控和卡顿优化
本文探讨了Android应用的卡顿优化,重点在于布局优化。建议包括将耗时操作移到后台、使用ViewPager2实现懒加载、减少布局嵌套并利用merge标签、使用ViewStub减少资源消耗,以及通过Layout Inspector和GPU过度绘制检测来优化。推荐使用AsyncLayoutInflater异步加载布局,但需注意线程安全和不支持特性。卡顿监控方面,提到了通过Looper、ChoreographerHelper、adb命令及第三方工具如systrace和BlockCanary。总结了Choreographer基于掉帧计算和BlockCanary基于Looper监控的原理。
18 3
|
30天前
|
编解码 缓存 数据库
构建高效Android应用:从性能优化到用户体验
【5月更文挑战第29天】 在移动开发领域,打造一个流畅且响应迅速的Android应用对于保持用户忠诚度和市场份额至关重要。本文将深入探讨如何通过细致的性能优化措施和关注用户体验设计,来提升Android应用的整体质量。我们将透过代码层面的实践技巧、资源管理和系统机制的优化,以及用户界面和交互设计的改良,共同构建起一个既快速又吸引人的应用程序。
|
5天前
|
Java Android开发 Kotlin
Android面试题:App性能优化之Java和Kotlin常见的数据结构
Java数据结构摘要:ArrayList基于数组,适合查找和修改;LinkedList适合插入删除;HashMap1.8后用数组+链表/红黑树,初始化时预估容量可避免扩容。SparseArray优化查找,ArrayMap减少冲突。 Kotlin优化摘要:Kotlin的List用`listOf/mutableListOf`,Map用`mapOf/mutableMapOf`,支持操作符重载和扩展函数。序列提供懒加载,解构用于遍历Map,扩展函数默认参数增强灵活性。
14 0
|
30天前
|
存储 缓存 Java
安卓应用性能优化实战
【5月更文挑战第29天】随着智能手机的普及,移动应用已成为人们日常生活中不可或缺的一部分。在众多操作系统中,安卓系统以其开放性和灵活性占据了大量的市场份额。然而,应用的性能问题却时常影响着用户体验。本文将深入探讨针对安卓平台进行应用性能优化的策略与实践,从内存管理到多线程处理,再到布局渲染,旨在为开发者提供全面的优化指导,以期打造出更流畅、高效的安卓应用。