上一篇博客我们简单介绍了一下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!
应该不会被打吧(/偷笑 /偷笑 /偷笑)?