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();  // 反初始化自己
    }
  1. 上层需要更新的状态通过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!

应该不会被打吧(/偷笑 /偷笑 /偷笑)?

猜你喜欢

转载自blog.csdn.net/jyt0551/article/details/77943832