#性能优化之Android应用内存优化实战#
对应视频:3. 为什么要学习数据结构与算法_哔哩哔哩_bilibili
根据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>