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>
相关文章
|
20天前
|
算法 数据处理 Android开发
掌握安卓性能优化的秘诀:电池寿命与运行效率的提升
【10月更文挑战第6天】 本文深入探讨了安卓应用开发中的性能优化技巧,重点分析了影响电池寿命和运行效率的关键因素,并提供了针对性的优化策略。通过代码优化、资源管理、后台任务处理等方法,开发者可以显著提升应用的续航能力和流畅度。同时,结合具体案例,展示了如何在实际开发中应用这些技巧,确保应用在各种场景下都能保持高效运行。本文旨在为安卓开发者提供实用的性能优化指导,助力其打造更优质的应用体验。
32 2
|
3月前
|
缓存 监控 Android开发
探索iOS与安卓开发中的性能优化策略
在移动应用开发的竞技场上,iOS和安卓这两大操作系统不断推动着技术的边界。性能优化,作为提升用户体验的关键因素,已成为开发者们关注的焦点。本文将深入探讨两大平台上的性能优化实践,揭示如何通过工具、技术和策略来提升应用的响应速度和流畅度,同时考虑到电池寿命和内存管理等关键指标。
|
4月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【7月更文挑战第28天】在Android开发中,掌握底层机制至关重要。从Dalvik到ART, Android通过采用AOT编译在应用安装时预编译字节码至机器码,显著提升了执行效率。ART还优化了垃圾回收,减少内存占用及停顿。为了优化性能,可减少DEX文件数量、优化代码结构利用内联等技术、合理管理内存避免泄漏,并使用ART提供的调试工具。
110 7
|
6天前
|
程序员 开发工具 Android开发
Android|使用阿里云推流 SDK 实现双路推流不同画面
本文记录了一种使用没有原生支持多路推流的阿里云推流 Android SDK,实现同时推送两路不同画面的流的方法。
24 7
|
15天前
|
存储 缓存 网络协议
5个Android性能优化相关的深度面试题
本文涵盖五个Android面试题及其解答,包括优化应用启动速度、内存泄漏的检测与解决、UI渲染性能优化、减少内存抖动和内存溢出、优化网络请求性能。每个问题都提供了详细的解答和示例代码。
18 2
|
19天前
|
监控 测试技术 Android开发
掌握安卓性能优化的关键策略
【10月更文挑战第7天】 在移动应用开发领域,性能优化是一项至关重要的任务。本文将探讨安卓应用性能优化的重要性、关键策略以及实际操作建议,帮助开发者提升应用的用户体验和竞争力。通过深入浅出的方式,我们将从背景介绍到具体实践,全面解析安卓性能优化的各个维度。
|
2月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【9月更文挑战第12天】在Android开发领域,深入了解其底层机制对提升应用性能至关重要。本文详述了从早期Dalvik虚拟机到现今Android Runtime(ART)的演变过程,揭示了ART通过预编译技术实现更快启动速度和更高执行效率的奥秘。文中还介绍了ART的编译器与运行时环境,并提出了减少DEX文件数量、优化代码结构及合理管理内存等多种性能优化策略。通过掌握这些知识,开发者可以从全新的角度提升应用性能。
59 11
|
3月前
|
人工智能 缓存 数据库
安卓应用开发中的性能优化技巧AI在医疗诊断中的应用
【8月更文挑战第29天】在安卓开发的广阔天地里,性能优化是提升用户体验、确保应用流畅运行的关键所在。本文将深入浅出地探讨如何通过代码优化、资源管理和异步处理等技术手段,有效提升安卓应用的性能表现。无论你是初学者还是资深开发者,这些实用的技巧都将为你的安卓开发之路增添光彩。
|
3月前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
60 1
|
2月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
137 0