EasyPusher 結合Android Architecture Component便捷開發二
上一篇部落格我們簡單介紹了一下Android Architecture Component的相關概念與知識點,這篇部落格我們將介紹一下如何根據其改造EasyPusher.
EasyPusher的業務邏輯模組是MediaStream類,該類實現攝像頭的開啟關閉,音訊採集的開啟關閉,推送的開始和停止的功能.
我們先看看EasyPusher主介面原來的一些關鍵處理邏輯:
1. onCreate裡面進行許可權檢查,如果尚未獲取許可權,那彈出獲取視窗;
2. onResume裡面,檢查許可權,如果未獲取,什麼也不做;否則檢查Texture,如果有效,開啟預覽,否則什麼也不做;
3. onTextureAvailable ,檢查是否有許可權,如果沒有,什麼也不做,否則開啟攝像頭
4. onPause裡面,如果之前開啟了預覽,那關閉預覽;
5. onRequestPermissionsResult,如果許可權獲取到了,那麼再嘗試開啟攝像頭.
6. onDestory裡面,檢查是否已經開啟了.如果沒有,什麼也不做,否則釋放相關資源.
7. 對MediaStream設定一些回撥,或者捕獲事件,進行MediaStream的狀態更新.
綜上,可以看到,為了開啟攝像頭,我們真是煞費苦心,不得不在若干地方進行若干種不同的條件檢查.同時,在銷燬的時候,也得做出許多非空判斷.這導致我們的上層邏輯比較複雜,主Activity的程式碼裡有將近800行.
甚至,之前的Pusher還不支援橫豎屏切換的功能,如果支援了,在Activity recreate的時候,要保持攝像頭和推送的狀態,可能又會帶來更多的程式碼和BUG隱患.
這麼多狀態檢查很痛苦,我們極度期望當我們呼叫了開始預覽後,
==MediaStream能夠在內部進行狀態檢查,在狀態都準備好後,自動啟動.==
同時,在我們退出Activity後,
==MediaStream能夠自動進行資源釋放並優雅退出==
基於Architecture Component,MediaStream便可滿足上面的期望.我們將對MediaStream做出如下改動:
1. 繼承ViewModel.這樣使得MediaStream的在Activity的狀態更改(比如橫豎屏切換)時能自動維護狀態;並且在Activity 銷燬時,自動得知,從而自己反初始化自己.
// MediaStream.java
// 在Activity Destory的時候,onCleared會自動被呼叫.這裡進行資源釋放和自我反初始化
@Override
protected void onCleared() {
super .onCleared();
stopStream(); // 停止推流
stopPreview(); // 停止預覽
destroyCamera(); // 關閉攝像頭
release(); // 反初始化自己
}
- 上層需要更新的狀態通過LiveData進行通知.這裡主要有攝像頭的當前解析度\推送開啟狀態\推送狀態\需要監聽.因此我們在MediaStream裡定義三個LiveData.分別用來表示攝像頭解析度\推送開始狀態\推送狀態的觀察者.
private final CameraPreviewResolutionLiveData cameraPreviewResolution;
private final PushingStateLiveData pushingStateLiveData;
private final StreamingStateLiveData streamingStateLiveData;
同時給上層匯出三個LiveData的觀察介面:
@MainThread
public void observeCameraPreviewResolution(LifecycleOwner owner, Observer<int[]> observer) {
cameraPreviewResolution.observe(owner, observer);
}
@MainThread
public void observePushingState(LifecycleOwner owner, Observer<PushingState> observer) {
pushingStateLiveData.observe(owner, observer);
}
@MainThread
public void observeStreamingState(LifecycleOwner owner, Observer<Boolean> observer) {
streamingStateLiveData.observe(owner, observer);
}
這樣上層Activity可觀察這些介面,並進行處理.
觀察解析度更改:
model.observeCameraPreviewResolution(this, new Observer<int[]>() {
@Override
public void onChanged(@Nullable int[] size) {
Toast.makeText(MainActivity.this,"當前攝像頭解析度為:" + size[0] + "*" + size[1], Toast.LENGTH_SHORT).show();
}
});
觀察推送狀態更改:
model.observePushingState(this, new Observer<MediaStream.PushingState>(){
@Override
public void onChanged(@Nullable MediaStream.PushingState pushingState) {
pushingStateText.setText(pushingState.msg);
if (pushingState.state > 0){
pushingStateText.append(String.format("rtsp://cloud.easydarwin.org:554/test123456.sdp"));
}
}
});
觀察推送使能啟停狀態更改:
model.observeStreamingState(this, new Observer<Boolean>(){
@Override
public void onChanged(@Nullable Boolean aBoolean) {
pushingBtn.setText(aBoolean ? "停止推送":"開始推送");
}
});
相比普通回撥而言,LiveData是’粘性(sticky)’的,也就是在上層開始註冊它的時候,它會把自己的最新狀態回撥一次.因此就沒必要再主動查詢一次了(相信大家都比較討厭這種模式).這樣是不是很方便?
3. 實現LifecyclerObserver.這個介面並沒有任何實現方法,而是用annotation進行資料同步的.其原始碼如下:
/**
* Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on
* {@link OnLifecycleEvent} annotated methods.
* <p>
* @see Lifecycle Lifecycle - for samples and usage patterns.
*/
@SuppressWarnings("WeakerAccess")
public interface LifecycleObserver {
}
MediaStream通過一個介面openCameraPreview開啟攝像頭,該介面根據當前時機判斷是否可以開啟攝像頭了.如果可以,就開啟;如果不可以,留個開啟標識,後面時機成熟了,再開啟.
@MainThread
public void openCameraPreview(){
cameraOpened = true;
if (cameraCanOpenNow()) {
createCamera();
startPreview();
}
}
// 判斷攝像頭當前是否可以開啟了?
// 條件:Activity Started,許可權允許,Texture有效
private boolean cameraCanOpenNow() {
if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
if (ActivityCompat.checkSelfPermission(getApplication(), android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(getApplication(), android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
// connect if not connected
if (mSurfaceHolderRef != null && mSurfaceHolderRef.get() != null) {
return true;
}
}
}
return false;
}
那有哪些地方檢查”時機成熟”呢?
根據檢查條件,應該有三個:1 Activity Started的時候;2 設定了SurfaceTexture的時候;3 許可權獲取成功的時候;我們逐一說明:
MediaStream實現了LifecycleObserver後,需要關注Activity的啟動和終止,增加這樣兩個註解函式(annotated method),這裡我們可以監控到Activity Start,可以看到,在start的時候,再嘗試開啟一次攝像頭.同理,在onStop的時候,嘗試關閉攝像頭.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void start() {
if (cameraOpened) openCameraPreview();
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stop() {
if (cameraOpened) closeCameraPreview();
}
我們還需要一個設定SurfaceTexture的介面,這裡再嘗試開啟一次攝像頭.
@MainThread
public void setSurfaceTexture(SurfaceTexture texture) {
if (texture == null) {
stopPreview();
mSurfaceHolderRef = null;
}else {
mSurfaceHolderRef = new WeakReference<SurfaceTexture>(texture);
stopPreview();
if (cameraOpened) openCameraPreview();
}
}
最後,就是上層獲取到許可權的時候,通知MediaStream,再嘗試一次.
@MainThread
public void notifyPermissionGranted(){
if (cameraOpened) openCameraPreview();
}
實現了LifecyclerObserver後,我們需要將Observer註冊到Lifecycle,Activity層在獲取到MediaStream後,需要呼叫setLifecycle.使得MediaStream能夠監聽到其狀態變更.
public void setLifecycle(Lifecycle lifecycle){
this.lifecycle = lifecycle;
lifecycle.addObserver(this);
}
至此,我們將MediaStream改造完成.然後我們再看看上層該如何呼叫吧!
現在,上層呼叫實在太簡單了.
- 在onCreate裡面獲取MediaStream例項, 呼叫setLifecycle將自己的生命週期註冊給MediaStream這個觀察者;
- 觀察MediaStream裡面的一些狀態更改並作出UI提示;
- 嘗試啟動攝像頭;
- 檢查並獲取攝像頭許可權,並且在許可權獲取完成時,告知MediaStream;
- 在SurfaceTexture準備好時,設定給MediaStream
- 一個按鈕,控制推送開始\停止
整個Activity的程式碼如下,清晰又簡潔:
package com.example.myapplication;
import android.arch.lifecycle.LifecycleActivity;
import android.arch.lifecycle.Observer;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.support.annotation.Nullable;
import android.os.Bundle;
import android.arch.lifecycle.ViewModelProviders;
import android.support.v4.app.ActivityCompat;
import android.view.TextureView;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.easydarwin.push.MediaStream;
public class MainActivity extends LifecycleActivity {
private static final int REQUEST_CAMERA_PERMISSION = 1000;
private MediaStream mediaStream;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mediaStream = ViewModelProviders.of(this).get(MediaStream.class);
mediaStream.setLifecycle(getLifecycle());
mediaStream.openCameraPreview();
mediaStream.observeCameraPreviewResolution(this, new Observer<int[]>() {
@Override
public void onChanged(@Nullable int[] size) {
Toast.makeText(MainActivity.this,"當前攝像頭解析度為:" + size[0] + "*" + size[1], Toast.LENGTH_SHORT).show();
}
});
final TextView pushingStateText = findViewById(R.id.pushing_state);
final TextView pushingBtn = findViewById(R.id.pushing);
mediaStream.observePushingState(this, new Observer<MediaStream.PushingState>(){
@Override
public void onChanged(@Nullable MediaStream.PushingState pushingState) {
pushingStateText.setText(pushingState.msg);
if (pushingState.state > 0){
pushingStateText.append(String.format("rtsp://cloud.easydarwin.org:554/test123456.sdp"));
}
}
});
mediaStream.observeStreamingState(this, new Observer<Boolean>(){
@Override
public void onChanged(@Nullable Boolean aBoolean) {
pushingBtn.setText(aBoolean ? "停止推送":"開始推送");
}
});
TextureView textureView = findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(new SurfaceTextureListenerWrapper() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
mediaStream.setSurfaceTexture(surfaceTexture);
}
});
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA,android.Manifest.permission.RECORD_AUDIO}, REQUEST_CAMERA_PERMISSION);
}
}
public void onPushing(View view) {
MediaStream.PushingState state = mediaStream.getPushingState();
if (state != null && state.state > 0){
mediaStream.stopStream();
}else {
mediaStream.startStream("cloud.easydarwin.org", "554", "test123456");
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CAMERA_PERMISSION: {
if (grantResults.length > 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
mediaStream.notifyPermissionGranted();
} else {
finish();
}
break;
}
}
}
}
大家可以發現,關鍵程式碼其實就那麼幾行.
最後,作者喊出這句話:
十行程式碼行程式碼實現一個Android Pusher!
應該不會被打吧(/偷笑 /偷笑 /偷笑)?