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开发
掌握安卓性能优化的秘诀:电池寿命与运行效率的提升
【10月更文挑战第6天】 本文深入探讨了安卓应用开发中的性能优化技巧,重点分析了影响电池寿命和运行效率的关键因素,并提供了针对性的优化策略。通过代码优化、资源管理、后台任务处理等方法,开发者可以显著提升应用的续航能力和流畅度。同时,结合具体案例,展示了如何在实际开发中应用这些技巧,确保应用在各种场景下都能保持高效运行。本文旨在为安卓开发者提供实用的性能优化指导,助力其打造更优质的应用体验。
47 2
|
3月前
|
缓存 监控 Android开发
探索iOS与安卓开发中的性能优化策略
在移动应用开发的竞技场上,iOS和安卓这两大操作系统不断推动着技术的边界。性能优化,作为提升用户体验的关键因素,已成为开发者们关注的焦点。本文将深入探讨两大平台上的性能优化实践,揭示如何通过工具、技术和策略来提升应用的响应速度和流畅度,同时考虑到电池寿命和内存管理等关键指标。
|
4月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【7月更文挑战第28天】在Android开发中,掌握底层机制至关重要。从Dalvik到ART, Android通过采用AOT编译在应用安装时预编译字节码至机器码,显著提升了执行效率。ART还优化了垃圾回收,减少内存占用及停顿。为了优化性能,可减少DEX文件数量、优化代码结构利用内联等技术、合理管理内存避免泄漏,并使用ART提供的调试工具。
117 7
|
11天前
|
Android开发 开发者
Android性能优化——内存管理的艺术
Android性能优化——内存管理的艺术
|
17天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
17天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
36 3
|
25天前
|
程序员 开发工具 Android开发
Android|使用阿里云推流 SDK 实现双路推流不同画面
本文记录了一种使用没有原生支持多路推流的阿里云推流 Android SDK,实现同时推送两路不同画面的流的方法。
44 7
|
1月前
|
存储 缓存 网络协议
5个Android性能优化相关的深度面试题
本文涵盖五个Android面试题及其解答,包括优化应用启动速度、内存泄漏的检测与解决、UI渲染性能优化、减少内存抖动和内存溢出、优化网络请求性能。每个问题都提供了详细的解答和示例代码。
26 2
|
1月前
|
监控 测试技术 Android开发
掌握安卓性能优化的关键策略
【10月更文挑战第7天】 在移动应用开发领域,性能优化是一项至关重要的任务。本文将探讨安卓应用性能优化的重要性、关键策略以及实际操作建议,帮助开发者提升应用的用户体验和竞争力。通过深入浅出的方式,我们将从背景介绍到具体实践,全面解析安卓性能优化的各个维度。
|
2月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【9月更文挑战第12天】在Android开发领域,深入了解其底层机制对提升应用性能至关重要。本文详述了从早期Dalvik虚拟机到现今Android Runtime(ART)的演变过程,揭示了ART通过预编译技术实现更快启动速度和更高执行效率的奥秘。文中还介绍了ART的编译器与运行时环境,并提出了减少DEX文件数量、优化代码结构及合理管理内存等多种性能优化策略。通过掌握这些知识,开发者可以从全新的角度提升应用性能。
65 11