#性能优化之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>