EasyPusher 结合Android Architecture Component便捷开发二

来源:互联网 发布:烟台市网络党校电脑管 编辑:程序博客网 时间:2024/06/14 17:15

上一篇博客我们简单介绍了一下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!

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

原创粉丝点击