上周六加班在解决一个关于SystemUI内嵌的DVR录像与系统截屏操作冲突的问题,介于问题的复杂性,所以我把这个分享出来便

于以后自己更加的理解,又方便以后遇到此问题的同行能够提供一些帮助,若有疑问可向鄙人的博客提供你的宝贵意见!

首先我们需要找到系统截屏的按键定义,并且知道它在哪里执行的,先摈弃从硬件底层的协议,我们直接从framework层开始

讲,因为底层底层硬件返回的结果由.c.o.h这些文件,再由Binder aidl将结果给到framework,所以我们就从开始从framework

开始,如果有兴趣的可以下载源码查看整个流程的实现过程。

首先我们查看 frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

在这个类下面,有一个方法 interceptKeyBeforeQueueing这个方法来自interface WindowManagerPolicy,而interface 

WindowManagerPolicy 的回调结果经过几多辗转最终由底层给到framework层interface WindowManagerPolicy,让

PhoneWindowManager来处理,因为底层返回的那流程涉及的文件和协议比较复杂,即使说了,不懂的也很难一下子掌握和理

解,所以笔者从framework开始作介绍,因为最后截屏的操作也会通过 native 由更底层的C来实现

下面继续看到 interceptKeyBeforeQueueing 这个函数,在这个函数下有一个switch (keyCode)  里面有一个按键监听,其实在

这个PhoneWindowManager下面截屏的方法被调用了2次,相信到这里大家都应该明白了吧?因为安卓系统原生的截屏操作是一

个按键组合,即 KeyEvent.KEYCODE_POWER | KeyEvent.KEYCODE_VOLUME_DOWN) 这两个按键,当我们按下这两个按键

系统会调一个函数做一个判断处理

<span style="font-size:14px;color:#3333ff;"><strong>        switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_DOWN:case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_MUTE: {if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {if (down) {if (interactive && !mScreenshotChordVolumeDownKeyTriggered&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {mScreenshotChordVolumeDownKeyTriggered = true;mScreenshotChordVolumeDownKeyTime = event.getDownTime();mScreenshotChordVolumeDownKeyConsumed = false;cancelPendingPowerKeyAction();interceptScreenshotChord();}} else {mScreenshotChordVolumeDownKeyTriggered = false;cancelPendingScreenshotChordAction();}}</strong></span>


这个函数就是interceptScreenshotChord()

<span style="font-size:14px;color:#3333ff;"><strong>    private void interceptScreenshotChord() {if (mScreenshotChordEnabled&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered&& !mScreenshotChordVolumeUpKeyTriggered) {final long now = SystemClock.uptimeMillis();// 按键组合按下的误差小于 150 毫秒视为截图操作if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS&& now <= mScreenshotChordPowerKeyTime+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {mScreenshotChordVolumeDownKeyConsumed = true;cancelPendingPowerKeyAction();mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());}}}</strong></span>


这个函数或启动一个Runable ,执行takeScreenshot()函数

<span style="font-size:14px;color:#3333ff;"><strong>    private final Runnable mScreenshotRunnable = new Runnable() {@Overridepublic void run() {takeScreenshot();}};</strong></span>

takeScreenshot()函数

<span style="font-size:14px;color:#3333ff;"><strong>  private void takeScreenshot() {synchronized (mScreenshotLock) {if (mScreenshotConnection != null) {return;}ComponentName cn = new ComponentName("com.android.systemui","com.android.systemui.screenshot.TakeScreenshotService");Intent intent = new Intent();intent.setComponent(cn);ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mScreenshotLock) {if (mScreenshotConnection != this) {return;}Messenger messenger = new Messenger(service);Message msg = Message.obtain(null, 1);final ServiceConnection myConn = this;Handler h = new Handler(mHandler.getLooper()) {@Overridepublic void handleMessage(Message msg) {synchronized (mScreenshotLock) {if (mScreenshotConnection == myConn) {mContext.unbindService(mScreenshotConnection);mScreenshotConnection = null;mHandler.removeCallbacks(mScreenshotTimeout);}}}};msg.replyTo = new Messenger(h);msg.arg1 = msg.arg2 = 0;if (mStatusBar != null && mStatusBar.isVisibleLw())msg.arg1 = 1;if (mNavigationBar != null && mNavigationBar.isVisibleLw())msg.arg2 = 1;try {messenger.send(msg);} catch (RemoteException e) {}}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};if (mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {mScreenshotConnection = conn;mHandler.postDelayed(mScreenshotTimeout, 10000);}}}</strong></span>


该函数bind TakeScreenshotService 

<span style="font-size:14px;color:#3333ff;"><strong>public class TakeScreenshotService extends Service {private static final String TAG = "TakeScreenshotService";private static GlobalScreenshot mScreenshot;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:final Messenger callback = msg.replyTo;if (mScreenshot == null) {mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);}mScreenshot.takeScreenshot(new Runnable() {@Override public void run() {Message reply = Message.obtain(null, 1);try {callback.send(reply);} catch (RemoteException e) {}}}, msg.arg1 > 0, msg.arg2 > 0);}}};@Overridepublic IBinder onBind(Intent intent) {return new Messenger(mHandler).getBinder();}</strong></span>


GlobalScreenshot  takeScreenshot 就是开始截屏framework执行截屏的那个动画,正在意义的截屏最后丢给了 native 去执行了


<span style="font-size:14px;color:#3333ff;"><strong>    /*** Takes a screenshot of the current display and shows an animation.*/void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {// We need to orient the screenshot correctly (and the Surface api seems to take screenshots// only in the natural orientation of the device :!)mDisplay.getRealMetrics(mDisplayMetrics);float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};/// M: [SystemUI] Support Smartbook Feature. @{boolean isPlugIn =com.mediatek.systemui.statusbar.util.SIMHelper.isSmartBookPluggedIn(mContext);if (isPlugIn) {dims[0] = mDisplayMetrics.heightPixels;dims[1] = mDisplayMetrics.widthPixels;}/// @}float degrees = getDegreesForRotation(mDisplay.getRotation());Xlog.d("takeScreenshot", "dims = " + dims[0] + "," + dims[1] + " of " + degrees);boolean requiresRotation = (degrees > 0);if (requiresRotation) {// Get the dimensions of the device in its native orientationmDisplayMatrix.reset();mDisplayMatrix.preRotate(-degrees);mDisplayMatrix.mapPoints(dims);dims[0] = Math.abs(dims[0]);dims[1] = Math.abs(dims[1]);Xlog.d("takeScreenshot", "reqRotate, dims = " + dims[0] + "," + dims[1]);}// Take the screenshot/// M: [SystemUI] Support Smartbook Feature. @{if (isPlugIn) {mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1],SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI);degrees = 270f - degrees;}/// @}else {mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);}if (mScreenBitmap == null) {Xlog.d("takeScreenshot", "mScreenBitmap == null, " + dims[0] + "," + dims[1]);notifyScreenshotError(mContext, mNotificationManager);finisher.run();return;}if (requiresRotation) {// Rotate the screenshot to the current orientationBitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);Canvas c = new Canvas(ss);c.translate(ss.getWidth() / 2, ss.getHeight() / 2);c.rotate(degrees);c.translate(-dims[0] / 2, -dims[1] / 2);c.drawBitmap(mScreenBitmap, 0, 0, null);c.setBitmap(null);// Recycle the previous bitmapmScreenBitmap.recycle();mScreenBitmap = ss;}// OptimizationsmScreenBitmap.setHasAlpha(false);mScreenBitmap.prepareToDraw();// Start the post-screenshot animationstartAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,statusBarVisible, navBarVisible);}</strong></span>


于是我在debug的时候将截屏的操作屏蔽掉了,测试看这个系统的BUG是否受到截屏的影响

<span style="font-size:14px;color:#3333ff;"><strong>public class TakeScreenshotService extends Service {private static final String TAG = "TakeScreenshotService";private static GlobalScreenshot mScreenshot;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {
//                case 1:
//                    final Messenger callback = msg.replyTo;
//                    if (mScreenshot == null) {
//                        mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
//                    }
//                    mScreenshot.takeScreenshot(new Runnable() {
//                        @Override public void run() {
//                            Message reply = Message.obtain(null, 1);
//                            try {
//                                callback.send(reply);
//                            } catch (RemoteException e) {
//                            }
//                        }
//                    }, msg.arg1 > 0, msg.arg2 > 0);}}};@Overridepublic IBinder onBind(Intent intent) {return new Messenger(mHandler).getBinder();}
}</strong></span>


屏蔽之后,我在make 一把debug,本地跟踪打印,发现这个截屏操作虽然没有做截屏操作了,但是BUG依旧还在,排除了BUG

是由截屏本身引起的,有可能是截屏的需要某个组件导致他跟DVR冲突,DVR在open camera的时候 Faild ,因为BUG只是在每

一次的重启截屏才会出现,我又不想深入去追到底是那个组件被多次调用,因为这个操作不需要同步,而是用户手动去操作,而

DVR必须在开机的时候由SystemUI开启,所以加 synchronized 也无济于事,所以只能走兼容的处理了,于是我在截屏操作的时

候做了一个简单的结果通知,即inient receiver 我在 SystemUI得到结果并作标记处理

<span style="font-size:14px;color:#3333ff;"><strong>    private void interceptScreenshotChord() {if (mScreenshotChordEnabled&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered&& !mScreenshotChordVolumeUpKeyTriggered) {final long now = SystemClock.uptimeMillis();if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS&& now <= mScreenshotChordPowerKeyTime+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {mScreenshotChordVolumeDownKeyConsumed = true;cancelPendingPowerKeyAction();mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());// engineer-jsp add methodScreenshotNotifyTion();}}}// engineer-jsp add methodprivate void ScreenshotNotifyTion(){Intent intent = new Intent("rmt.screenshot.notifytion.action");mContext.sendBroadcast(intent);}</strong></span>


在SystemUI接收,但是这个接收必须存一个比 static 更持久,但是在BUG逻辑块程序执行完后我需要update这个flags,因为我

在SystemUI注册BroadcastReceiver收到onReceiver 保存的标志位即使存全局 application也会改变,因为截屏的这个操作导致

某个组件跟SystemUI的 DVR 冲突,所以SystemUI会被重启很多次,最终application下的这个用来标记截屏的flags也会被重

置,所以static也是无济于事的,这时候我想到了利用file 节点和sqllite和SharedPreferences 方案,最终选定轻量级的

SharedPreferences,将BroadcaseReceiver 注册在了

frameworks\base\packages\SystemUI\src\com\cars\recorder\media\RecorderStateManager.java

<span style="font-size:14px;color:#3333ff;"><strong>// engineer-jsp add method
private void LoadScreenShotReceiver(){mReceiver = new ScreenShotReceiver();IntentFilter filter = new IntentFilter("rmt.screenshot.notifytion.action");filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);mContext.registerReceiver(mReceiver, filter);}GotoStealStateReceiver mGotoStealStateReceiver;CrashControlReceiver mCrashControlReceiver;ScreenShotReceiver mReceiver;Context mContext;public RecorderStateManager(SurfaceHolder holder,Context context) {mContext = context;// engineer-jsp add methodLoadScreenShotReceiver();mRecorder = new RecordSurfaceThread(holder);......</strong></span>


在 onReceive 由ContextUtil 单例调用自定义类 ScreenShotUtil 单例,执行flags标记 ,ContextUtil extends Application

<span style="font-size:14px;color:#3333ff;"><strong>  class ScreenShotReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context arg0, Intent arg1) {ContextUtil.getInstance().setScreenShotFlags(true);}}</strong></span>


<span style="font-size:14px;color:#3333ff;"><strong>public class ContextUtil extends Application {......public boolean getScreenShotFlags(){return ScreenShotUtil.getScreenShotInstance(this).getScreenShotFlags();}public void setScreenShotFlags(boolean flags){ScreenShotUtil.getScreenShotInstance(this).setScreenShotFlags(flags);}......    </strong></span>


ScreenShotUtil 自定义类

<span style="font-size:14px;color:#3333ff;"><strong>package com.cars.recorder.media;
/*** @author engineer-jsp* @date 2016.06.18* ScreenShotUtil* */
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;public class ScreenShotUtil {private static ScreenShotUtil mScreenShotUtil = null;private static SharedPreferences mSharedPreferences = null;private static String SCREENSHOT_FILE = "screenshot_file";private static String SCREENSHOT_FLAGS = "screenshot_flags";public static ScreenShotUtil getScreenShotInstance(Context context) {if (mScreenShotUtil == null) {mScreenShotUtil = new ScreenShotUtil();}if (mSharedPreferences == null) {mSharedPreferences = context.getSharedPreferences(SCREENSHOT_FILE,Context.MODE_PRIVATE);}return mScreenShotUtil;}public void setScreenShotFlags(boolean flags) {if (mSharedPreferences == null) {return;}mSharedPreferences.edit().putString(SCREENSHOT_FLAGS, String.valueOf(flags)).commit();}public boolean getScreenShotFlags() {return isScreenShotFlags();}public boolean isScreenShotFlags() {if (mSharedPreferences == null) {return false;}if (TextUtils.isEmpty(mSharedPreferences.getString(SCREENSHOT_FLAGS, ""))) {return false;} else {return Boolean.valueOf(mSharedPreferences.getString(SCREENSHOT_FLAGS, ""));}}
}
</strong></span>


本来考虑到两个影响因素,但是这两个因素都被我排除了,即开始截屏跟冲突导致SystemUI最后一次重新启动的误差,还有一个

是截屏冲突导致SystemUI最后一次重新启动flags在截屏前重置,因为笔者做的是MTK的方案,6735的平台,加载4G网络没那么

快,如果用户在网络不正常的情况下截屏的话,我会存下这个时间戳,然后等待冲突导致SystemUI重启再次获取时间戳,存在轻

量级下,但是这个可能是ANT在网络正常的情况下获取的,所以这两个时间戳根本无法比较,因为ANT在没有网络的情况下默认

是节点文件下的默认时间,是不标准的,一旦加载了网络,时间就会从google获取北京时间,所以这个想法不成立,还有一个就

是刚说的第二个方案,即在收到截屏广播通知我就存下标记,执行完我就设为 false,不成功默认false,如果在截屏的中途断点

或ACC断开,执行了按键处逻辑,没有执行截屏,这时候flags视为true,我只需要在每次SystemUI重启的时候设为原始默认的

false即可,即使在SystemUI重启的那几次继续截屏也会适用,所以选择第二个方案是非常可行的!

<span style="font-size:14px;color:#3333ff;"><strong>public class NoCameraState extends RecorderState {......@Overridepublic boolean canChangeTo(RecorderState state) {if(ContextUtil.getInstance().getSleepAndZdfdValues()){return true;} else {// engineer-jsp add method// 根据标志位执行如下逻辑,执行完后还原,中途失败,会在SystemUI下次init重置,所以不冲突// ContextUtil.getInstance().getScreenShotFlags()?截屏:没有截屏if(!ContextUtil.getInstance().getScreenShotFlags()){TTSHelper.ttsReport(TheRecorderPlugin.getInstacne().getPluginContext().getString(R.string.carmerafailed), 0, TTSHelper.mExprieForever);}// 重置ContextUtil.getInstance().setScreenShotFlags(false);return false;}}......</strong></span>


修改完之后make烧录新的固件,测试了N次,OK!没任何问题!

Android 系统截屏与系统内置DVR录像冲突,导致SystemUI重启的问题解决与分享相关推荐

  1. Android系统截屏的实现(附代码)

    1.背景 写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_shot就会找到很对办法,但那些都是很多 ...

  2. Android系统截屏监听工具

    Android系统截屏监听工具 做系统截屏通知的时候,找了很多资料,测试后发现要做挺多机型适配的,而且有时候会没有监听到,有时候又监听到多次,不能实现想要的效果.一下是最终的解决方案,已在足够多的手机 ...

  3. Android 系统截屏实现

    在AndroidManifest.xml文件里面填入以下权限 <!--系统截屏权限--> <uses-permission android:name="android.pe ...

  4. 监听Android系统截屏

    1. 原理 因为Android系统没有提供截屏的相关API,所以需要我们自己去实现.国内的Android手机都是使用定制系统的,截图方式五花八门,采用对截图按键的监听的方案并不合适.Android系统 ...

  5. android 系统截屏原理,Android 系统副屏截屏功能

    收到一个客户需求,要求对双屏设备的副屏进行截图.查询资料后发现,系统截图有两种方法,一种是通过SurfaceControl.screenshot提供的接口调用,还有一种是通过screencap 命令获 ...

  6. Android系统截屏功能提取

    Android在4.0版本之后同时按电源键和音量键可以截取当前屏幕,截图后会有一个过渡动画效果,这里提取了将效果这部分提取出来,可以用于应用截图分享功能. 截图功能在源码中的位置是com.androi ...

  7. android 4.3 操作源码实现系统截屏(暂无移植性)

    感谢网友cjd6568358的帮助,新版的Android系统截屏功能已经实现,需要的朋友请移步项目主页:https://github.com/Android-ScreenShot/AndroidScr ...

  8. android后台截屏实现(2)--screencap源码修改

    首先找到screencap类在Android源码中的位置,/442/frameworks/base/cmds/screencap/screencap.cpp. 源码如下: [cpp] view pla ...

  9. Android平台监听系统截屏方案预研及相关知识点

    最近有个针对系统截屏的需求,所以预研了Android平台上捕获系统截屏的方案. 最直接的方式就是监听手机的系统截屏组合键(电源键+音量下键),但是这种方式实现难度大,且有的机型使用特殊手势进行截屏,兼 ...

最新文章

  1. android studio 怎么运行java
  2. 在线作图|如何绘制一张好看的点棒图
  3. python把矩阵存为文件_python 读取文件并把矩阵转成numpy的两种方法
  4. 爬虫好学吗python-python爬虫容易学吗
  5. Macbook air 键盘标点符号怎么输出?
  6. 《大话数据结构》第1章 数据结构绪论 1.1 开场白
  7. SAP HANA ANY和ALL关键字
  8. sun game server (sgs)初探
  9. 初中数学四十二个几何模型_陪伴成长的初中数学几何模型大全
  10. Oracle 查看表空间、数据文件的大小及使用情况
  11. 供应商寄售库存管理_【论文解读】物流联合外包下库存管理模式对供应链运作的影响...
  12. 网络连接的netstat命令
  13. Maven学习总结(38)——Maven下载失败文件夹批量删除问题的两种解决方法
  14. Python周刊518期
  15. java 麻将小程序_麻将小程序麻将这么玩
  16. 光滑噪声数据常用的方法_几种常见的数据变换方法
  17. 如何把一个数字x最右侧那个1拿出来,变成00...10..的格式
  18. JS 实现网站简繁体切换
  19. 民锋国际期货:期货交易 | 博弈之道,遵守法则
  20. imu oracle,IMU技术

热门文章

  1. vue+elementui系统利用Export2Excel.js将表格内容导出到excel,并设置表头文字水平垂直居中
  2. Unity扩展GameObject等类中的方法
  3. 大数据史记2013:盘点中国2013行业数据量
  4. 基于FPGA的目标颜色识别追踪一——图像预处理
  5. PCL学习记录---pcd_wirte
  6. 浙江大学计算机学霸作息,浙大学霸作息表曝光:你有多努力,就有多特殊!
  7. Java 注解说的明明白白,清清楚楚,记得点赞关注
  8. SpringBoot整合activiti7二:模型列表(创建,删除,修改,查询),直接上代码,清清楚楚
  9. android获取已配对的蓝牙列表和已连接蓝牙设备
  10. C语言strcat函数和strncat的区别