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(); // 反初始化自己 }
- 上层需要更新的状态通过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!
应该不会被打吧(/偷笑 /偷笑 /偷笑)?
- EasyPusher 结合Android Architecture Component便捷开发二
- EasyPusher 结合Android Architecture Component便捷开发一
- 使用EasyPusher SDK进行便捷开发
- Android Architecture Component系列
- Android architecture component架构
- Android architecture component架构集成
- Android Architecture Component -- Lifecycle 浅析
- Android Architecture Component之LiveData
- 使用EasyPusher进行手机低延时直播推流便捷开发
- eclipse中的sca构件(service component architecture)开发过程(二) .
- Android Architecture Component之ViewModel源码分析
- Android Architecture Component之Lifecycle-Aware Components
- Android Studio开发中的便捷方法
- Android 开发中代码便捷处理
- Web Dynpro Component Architecture
- EBS - Architecture and component
- android应用开发入门让Android开发更便捷
- SCA (Service Component Architecture)
- js:参数有空格或者换行时报错
- 在ubutun14.10上安装docker-ce
- CentOS 6.5搭建Mongodb
- Ubutu 更改 子文件或子目录的权限
- Struts2.3.34 升级事项
- EasyPusher 结合Android Architecture Component便捷开发二
- el表达式
- 编写RabbitMQ总结
- 命令行工具使用数据库
- Spark: sortBy sortByKey 二次排序
- 生成caffe.pb.cc和caffe.pb.h文件
- 使用CSS3制作倾斜导航条和毛玻璃效果
- 流复制环境中重启主库后报错WAL被removed的解决方法
- Jaccard相似度在竞品分析中的应用