Google最新截屏案例详解
Google从Android 5.0 开始,给出了截屏案例ScreenCapture,在同版本的examples的Media类别中可以找到。给需要开发手机或平板截屏应用的小伙伴提供了非常有意义的参考资料,由于以前版本的API是隐藏的,要想开发一个截屏应用需要费一番心思且有局限性。当然了,这里说的截屏不是应用程序本身,而是包括状态栏在内的整个屏幕,不管当前运行的是什么程序,效果同按下手机自带截屏快捷键一样。
整个案例的源码就不在这里显摆了,相信感兴趣的朋友一定能找得到,其实整个工程很简单,而且在AndroidManifest.xml中也不需要添加什么用户权限。因为该案例并没有将屏幕数据转化为某一种类型图片并保存,而只是将信息显示在界面上的某一个组件中,注意是实时显示,即不断地在播放屏幕上的内容。不过这不是什么问题,我们只要在此基础上稍加改进就能读取出屏幕信息,生成图片保存下来,这时必须记得添加存储卡读写的权限。
下面对案例中关键的代码进行解析,一方面是想在学习与总结的过程中巩固知识,更重要的是希望有大神指出讲解错误或不足的地方。
1、在布局上,采取的方式是将主界面MainActivity中的FrameLayout布局组件替换为视图显示类ScreenCaptureFragment的LinearLayout布局组件,其中MainActivity继承自SampleActivityBase(SampleActivityBase继承自FragmentActivity),ScreenCaptureFragment类继承自Fragment ,负责显示屏幕信息的组件是SurfaceView,和控制是否显示屏幕信息的Button组件一起包含在上述的LinearLayout中。即布局文件有两个,分别作为MainActivity类和ScreenCaptureFragment类的显示视图(View)。
2、对于Java文件,真正发挥作用的是ScreenCaptureFragment类,因为SampleActivityBase类做的事情是继承FragmentActivity和添加日志,MainActivity类在完成布局设置及组件替换之后,便将控制权交给了ScreenCaptureFragment类。那究竟是怎么做到将屏幕信息取出,并不断地进行显示的呢?
3、先来看MainActivity中的onCreate()方法,可以说除了默认生成的代码,该类只做了下面if块中的事情。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
ScreenCaptureFragment fragment = new ScreenCaptureFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
9 transaction.commit();
}
}
经测试发现,利用FragmentTransaction类事务对象transaction的方法替换主界面中的FrameLayout显示内容时,replace()和add()的效果没有明显的区别。下面是Google官方给出的函数原型及解释:
public abstract FragmentTransaction add (int containerViewId, Fragment fragment, String tag)
Add a fragment to the activity state. This fragment may optionally also have its view (if Fragment.onCreateView returns non-null) into a container view of the activity.
add是把一个fragment添加到一个容器container里。
public abstract FragmentTransaction replace (int containerViewId, Fragment fragment, String tag)
Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.
replace是先remove掉相同id的所有fragment,然后在add当前的这个fragment。
值得注意的是,add和replace影响的只是界面,而控制回退的,是事务。
4、每一个事务都是同时要执行的一套变化。可以在一个给定的事务中设置你想执行的所有变化,使用诸如add()、remove()及 replace()。然后,要给activity应用事务,必须调用 commit()。在调用commit()之前,你可能想调用addToBackStack(),将事务添加到一个fragment事务的back stack。这个back stack由activity管理,并允许用户通过按下 BACK 按键返回到前一个fragment状态。
如果添加多个变化到事务(例如add()或remove())并调用addToBackStack(), 然后在你调用commit()之前的所有应用的变化会被作为一个单个事务添加到后台堆栈,BACK按键会将它们一起回退。调用commit()并不立即执行事务。恰恰相反,它将事务安排排期, 一旦准备好, 就在activity的UI线程上运行(主线程)。如果有必要,无论如何, 你可以从你的UI线程调用 executePendingTransactions()来立即执行由commit()提交的事务。但这么做通常不必要,除非事务是其他线程中的job的一个从属。
警告:你只能在activity保存它的状态(当用户离开activity)之前使用commit()提交事务。
在事务提交,即transaction.commit()执行之后,控制权就交给了MainActivity的主UI。在此案例中,其实就是将控制权交给了新添加的Fragment成分,即LinearLayout布局,其中包括一个SurfaceView和Button,具体操作为按下Button按钮即开始在SurfaceView中显示屏幕信息,再次按下停止显示。
5、对于ScreenCaptureFragment类,做的事情主要包括对继承方法进行重载,新定义手机屏幕获取与显示方法。在MainActivity启动及进行事务提交这段时间里面,其实ScreenCaptureFragment已经完成的操作包括onCreate(),onCreateView(),onViewCreated(),onActivityCreated(),它们都是在类对象构建过程中自动执行的,但要想在其中实现额外的功能,必须进行重载,案例中比较关键的是onCreateView(),onViewCreated(),onActivityCreated(),实现方式分别如下。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_screen_capture, container, false);
}
该方法是要告诉主程序类MainActivity存在可以用来替换其组件sample_content_fragment的xml文件fragment_screen_capture。
读取布局中的SurfaceView组件赋给mSurfaceView,并将其Surface成员取出赋给mSurface,将Button组件赋给mButton。
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mSurfaceView = (SurfaceView) view.findViewById(R.id.surface);
mSurface = mSurfaceView.getHolder().getSurface();
mButton = (Button) view.findViewById(R.id.button);
mButton.setOnClickListener(this);
}
这段代码主要作用是提取手机屏幕的分辨率大小矩阵metrics、像素深度mScreenDensity及定义了MediaProjectionManager类对象mMediaProjectionManager。由于ScreenCaptureFragment类并没有继承与Activity相关的类,所以在获取WindowManager类对象及进行后续操作时需要getActivity的协助。
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Activity activity = getActivity();
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
mScreenDensity = metrics.densityDpi;
mMediaProjectionManager = (MediaProjectionManager)activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
}
在Android 5.0以前,如果开发者要对Android屏幕进行手机全屏的单帧或实时截图,往往比较困难。Android 5.0的出现改变了这种现状,其新增了MediaProjectionManager管理器,可以非常方便地实现屏幕捕捉功能。
6、上面提及界面上还有一个按钮,利用其来控制屏幕信息的显示,第一次按是开始,接着是停止,后续操作如此反复。首先看按钮的按键捕获与执行代码,注意开始和停止操作是由一个按钮实现,其text内容会随着点击在START与STOP之间切换。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
if (mVirtualDisplay == null) {
startScreenCapture();
} else {
stopScreenCapture();
}
break;
}
}
接下来看看开始显示屏幕信息函数startScreenCapture()做了什么。
private void startScreenCapture() {
Activity activity = getActivity();
if (mSurface == null || activity == null) {
return;
}
if (mMediaProjection != null) {
setUpVirtualDisplay();
} else if (mResultCode != 0 && mResultData != null) {
setUpMediaProjection();
setUpVirtualDisplay();
} else {
// This initiates a prompt dialog for the user to confirm screen projection.
startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(),REQUEST_MEDIA_PROJECTION);
}
}
通过以上代码可以看出,在刚开始,mSurface和activity已经定义,所以程序会继续往下执行;而MediaProjection对象mMediaProjection没有定义,为null;同样的startActivityForResult()方法还没有执行,不存在用户返回数据,故mResultCode(int型,记录返回值,同意或拒绝)为0,mResultData(Intent型,记录返回后的Intent对象)为null;所以,按照逻辑,会执行最后一个代码块,即创建并启动一个屏幕捕捉的Intent对象。
注意第二个参数为人为设定的请求码(整型),数值不限定,主要作用是对用户操作后的返回值进行判断。因为发起第一次在手机上发起屏幕截取请求,会弹出用户授权对话框,具体返回值见下面分析。请求对话框如下:
当用户选择拒绝时,应用程序自然就结束了;而选择同意时,便开始屏幕信息的获取与显示了。注意,对话框中间还有一个选项是设置要不要再次提示此请求,如果不够上,那么每次打开应用请求截屏时均会弹出此对话框。而该机制的实现方式很多,比如用一个配置文件,在配置文件中用一个变量控制是否弹出窗口,比如有一个config文件,里面有一个变量:showDialog=true,如果用户选择不在弹出,则复写config文件为showDialog=false,然后程序每次运行时检测,为true就显示,false就不弹窗。
7、那么用户选择同意之后,为什么就可以实时获取屏幕信息了?关键在于后面的方法onActivityResult(),来看其代码。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_MEDIA_PROJECTION) {
if (resultCode != Activity.RESULT_OK) {
return;
}
Activity activity = getActivity();
if (activity == null) {
return;
}
mResultCode = resultCode;
mResultData = data;
setUpMediaProjection();
setUpVirtualDisplay();
}
}
可以看到,当初设定的请求码在此处发挥作用了,requestCode == REQUEST_MEDIA_PROJECTION,而用户选择同意之后的结果值为Activity.RESULT_OK,看一下其在源码中(Activity.java)的定义:同意之后的返回值为-1,拒绝的话就为0。
/** Standard activity result: operation canceled. */
public static final int RESULT_CANCELED = 0;
/** Standard activity result: operation succeeded. */
public static final int RESULT_OK = -1;
8、给mResultCode与mResultData赋于返回结果值之后,主角真正登场了,首先是方法setUpMediaProjection()。
private void setUpMediaProjection() {
mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
}
虽然只有一句代码,但定义了在外漂泊已久的mMediaProjection,有了该对象才可以获取被捕获的屏幕信息。
9、然后是方法setUpVirtualDisplay(),从名字也可以看出屏幕信息终于要dispaly了。
private void setUpVirtualDisplay() {
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
"ScreenCapture",mSurfaceView.getWidth(), mSurfaceView.getHeight(), mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mSurface, null, null);
mButton.setText(R.string.stop);
}
总共两句代码,第一句实现了将屏幕信息显示在SurfaceView组件中,第二句将按钮文本设为"STOP",因为开始时是"START",文本内容表示的含义是点击后会进行的操作,而不是程序当前的执行状态。到此时,界面上左下角的组件SurfaceView应该在实时显示屏幕信息,若状态栏的时间等信息变化了,可以达到流畅的播放效果。当然,由于不断地截取屏幕信息,会出现双面镜反射效果,即自身无限包含直到一个点(在理发店也可以体验^_^)。
实时显示屏幕界面如下:
10、要让其停止,只需要再次点击按钮,这回轮到stopScreenCapture()方法表现了。
private void stopScreenCapture() {
if (mVirtualDisplay == null) {
return;
}
mVirtualDisplay.release();
mVirtualDisplay = null;
mButtonToggle.setText(R.string.start);
}
代码很简单,将对象mVirtualDisplay释放,同时将按钮文本设置为"START"。
11、停止获取屏幕信息后,若结束应用,在完全退出之前要让其自动完成一项任务——停止对象mMediaProjection,需要重载onDestory()方法。
@Override
public void onDestroy() {
super.onDestroy();
tearDownMediaProjection();
}
内部调用的方法tearDownMediaProjection()实现如下。
private void tearDownMediaProjection() {
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
}
12、最后介绍两个重载函数onCreate()与onPause(),先看后者。
@Override
public void onPause() {
super.onPause();
stopScreenCapture();
}
可以看出,在应用停止(比如其他应用突然在activity顶层运行、按了Back或Home键等)时,若当前正处在获取屏幕信息的状态下,调用此函数可以先将当前获取操纵停止,避免不必要的资源浪费。操作过就会发现,若在截屏请求时同意并勾选不再弹出对话框,那之后运行(包括从activity队列中重获新生与重新运行程序)就不会出现了。
来看onCreate()做了什么。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mResultCode = savedInstanceState.getInt(STATE_RESULT_CODE);
mResultData =savedInstanceState.getParcelable(STATE_RESULT_DATA);
}
}
刚开始就会判断数据承载对象savedInstanceState是否为空,若不是则取出mResultCode与mResultData。而这两个值前面已经说明,会记录用户的选择结果。
回到截屏开始函数,startScreenCapture(),其中有一个分支如下。
if (mResultCode != 0 && mResultData != null) {
setUpMediaProjection();
setUpVirtualDisplay();
}
即如果条件成立,就直接进行屏幕信息的获取并显示,不再需要发送带屏幕信息获取请求的Intent对象了,也就不会有那个对话框了。
对案例的分析就到这里了,欢迎感兴趣的朋友一起交流,共同进步!!!
Google最新截屏案例详解的更多相关文章
- 第7.20节 案例详解:Python抽象类之真实子类
第7.20节 案例详解:Python抽象类之真实子类 上节介绍了Python抽象基类相关概念,并介绍了抽象基类实现真实子类的步骤和语法,本节结合一个案例进一步详细介绍. 一. 案例说明 本节定义 ...
- 第7.18节 案例详解:Python类中装饰器@staticmethod定义的静态方法
第7.18节 案例详解:Python类中装饰器@staticmethod定义的静态方法 上节介绍了Python中类的静态方法,本节将结合案例详细说明相关内容. 一. 案例说明 本节定义了类Sta ...
- 第7.13节 案例详解:Python类变量
第7.13节 案例详解:Python类变量 上节介绍了类变量的定义和使用方法,并举例进行了说明.本节将通过一个更完整的例子来说明. 一. 定义函数dirp def dirp(iter): ret ...
- 第7.24节 Python案例详解:使用property函数定义属性简化属性访问代码实现
第7.24节 Python案例详解:使用property函数定义属性简化属性访问代码实现 一. 案例说明 本节将通过一个案例介绍怎么使用property定义快捷的属性访问.案例中使用Rectan ...
- spring的IOC,DI及案例详解
一:spring的基本特征 Spring是一个非常活跃的开源框架:它是一个基于Core来架构多层JavaEE系统的框架,它的主要目的是简化企业开发.Spring以一种非侵入式的方式来管理你的代码,Sp ...
- 深入浅出 spring-data-elasticsearch - 基本案例详解(三
『 风云说:能分享自己职位的知识的领导是个好领导. 』运行环境:JDK 7 或 8,Maven 3.0+技术栈:SpringBoot 1.5+, Spring Data Elasticsearch ...
- 用一个开发案例详解Oracle临时表
用一个开发案例详解Oracle临时表 2016-11-14 bisal ITPUB  一.开发需求 最近有一个开发需求,大致需要先使用主表,或主表和几张子表关联查询出ID(主键)及一些主表字段 ...
- http500:服务器内部错误案例详解(服务器代码语法错误或者逻辑错误)
http500:服务器内部错误案例详解(服务器代码语法错误或者逻辑错误) 一.总结 服务器内部错误可能是服务器中代码运行的时候的语法错误或者逻辑错误 二.http500:服务器内部错误案例详解 只是一 ...
- spss进行判别分析步骤_spss判别分析结果解释_spss判别分析案例详解
spss进行判别分析步骤_spss判别分析结果解释_spss判别分析案例详解 1.Discriminant Analysis判别分析主对话框 如图 1-1 所示 图 1-1 Discriminant ...
随机推荐
- sql server 有关锁的视图说明 syslockinfo
ransact-SQL 参考 https://msdn.microsoft.com/zh-cn/library/ms179881.aspx syslockinfo 包含有关所有已授权.正在转换和 ...
- Python基础s14-day1
2016年7月23日"Python基础s14-Day1" Python是什么? Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/),是一种面向对象.直译式 ...
- Redhat Linux 修改主机名(HOSTNAME)
hostname #查看当前主机的主机名hostname NEWHOSTNAME #临时修改当前主机名 修改主机名vi /etc/sysconfig/network #通过配置文件修改主机名NETWO ...
- Ubuntu14.02.2下安装JDK并配置Jetty服务器
首先第一步先取得JDK的安装文件,由于我的系统是64位的,所以安装包是jdk-7u80-linux-x64.gz 上传到unbuntu服务器下 执行tar -xvf jdk-7u80-linux-x6 ...
- HashMap和HashSet
Java使用Set接口来描述集合,而Set中每一个数据元素都是唯一的. HashSet散列集合 Hash算法:把任意长度输入,通过散列算法,变换成固定长度的输出即散列值.对不同类型信息,散列值公式也是 ...
- Ganglia安装扩容
现有的环境中Hbase集群的机器需要安装ganglia,遂采取了以下步骤. 查看机器的信息, uname –a cat /etc/issue 查看当前环境是x86的,安装的是red hat 6.4 之 ...
- 【转载】阎焱:90后创业是扯淡 大量O2O和P2P公司濒临倒闭
真正创业成功的大部分是年龄在30岁到38岁之间,很多90后基本什么都不懂.从历史来看,在这样的人口大国,集体性行为,无论是政治的还是经济的,基本都是导致灾难性后果. 10月14日消息,赛富基金创始首席 ...
- 结对编程项目——四则运算vs版
结对编程项目--四则运算vs版 1)小伙伴信息: 学号:130201238 赵莹 博客地址:点我进入 小伙伴的博客 2)实现的功能: 实现带有用户界面的四则运算:将原只能在 ...
- C++11 之 override
1 公有继承 派生类公有继承自 (public inheritance) 基类,继承包含两部分:一是函数的 "接口" (interface),二是函数的 "实现&quo ...
- LoadRunner11.0下载及安装链接~(By网络)
Download and install O(∩_∩)O: http://www.jb51.net/softjc/71256.html