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

上周六加班在解决一个关于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() {
        @Override
        public 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() {
                @Override
                public 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()) {
                            @Override
                            public 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) {
                        }
                    }
                }
                @Override
                public 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() {
        @Override
        public 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);
            }
        }
    };

    @Override
    public 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 orientation
            mDisplayMatrix.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 orientation
            Bitmap 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 bitmap
            mScreenBitmap.recycle();
            mScreenBitmap = ss;
        }

        // Optimizations
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        // Start the post-screenshot animation
        startAnimation(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() {
        @Override
        public 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);
            }
        }
    };

    @Override
    public 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 method
                ScreenshotNotifyTion();
            }
        }
    }

	// engineer-jsp add method
	private 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 节点和mysql
和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 method
		LoadScreenShotReceiver();
		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 {
		@Override
		public 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 {

......
	@Override
	public 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!没任何问题!

时间: 2024-10-10 14:38:41

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

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

1.背景 写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_shot就会找到很对办法,但那些都是很多年前的了,在android4.*版本后,android对于源码进行了更正,使得以前的方法都不能够使用. 感谢cjd6568358这名网友,我们一起讨论,最终由他实现了android系统截屏功能,为了让以后想要这个功能的coder可以少走一些弯路,我们整理的代码做成开源项目. 2.思路 其实主要思路还

监听Android系统截屏

公司的项目由于安全需要,对某一特定的页面需要监听是否被用户截屏了. 简单搜了一下,很少有这方面的问题,没办法,只能自己折腾了. 目前想到三种思路: 1.监听广播 当然,前提是系统在截屏的时候发送某一广播,然而并没有. 2.监听按键 Android手机按下“电源键+音量减”会进行截屏,此外大部分手机状态栏下拉的页面中也会有截屏按钮.遗憾的是,监听这两处的操作并不是一件让人开心的事儿~~. 3.监听手机中图片的变化 开始只想到了MediaStore这个类,可以通过它拿到手机中的所有图片,每隔一段时间

Android系统截屏功能提取

Android在4.0版本之后同时按电源键和音量键可以截取当前屏幕,截图后会有一个过渡动画效果,这里提取了将效果这部分提取出来,可以用于应用截图分享功能. 截图功能在源码中的位置是com.android.systemui.screenshot,下面有四个类 其中主要工作都在GlobalScreenshot中,包括截图后的动画效果.保存到本地和显示到通知栏.为了简单,下面的代码只保留了过渡动画部分 class GlobalScreenshot { private static final Stri

Android 长截屏原理

https://android-notes.github.io/2016/12/03/android%E9%95%BF%E6%88%AA%E5%B1%8F%E5%8E%9F%E7%90%86/   android长截屏原理 小米系统自带的长截屏应该很多人都用过,效果不错.当长截屏时listview就会自动滚动,当按下停止截屏时,就会得到一张完整的截屏. 该篇就介绍一下长截屏的原理 上篇中介绍了android屏幕共享实现方式,该篇的原理和上一篇基本一致. 获取view影像 当我们想得到一个view

Android长截屏-- ScrollView,ListView及RecyclerView截屏

http://blog.csdn.net/wbwjx/article/details/46674157       Android长截屏-- ScrollView,ListView及RecyclerView截屏 https://github.com/BoBoMEe/AndroidDev/blob/master/common/common/src/main/java/com/bobomee/android/common/util/ScreenUtil.java /* * Copyright (C)

Android自动截屏小脚本

Android自动截屏小脚本(脱离手工操作,自动保存到PC上) @echo offecho * 截图文件将保存在 E:\takeshont下,以当前日期+时间命名.echo =================================================echo * 如果在停止截图后,无法删除或者上传,可到任务管理器中删除adb.exe进程echo =================================================================

Android 7.1.1 系统截屏

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java TakeScreenshotService.java package com.android.systemui.screenshot; import android.app.Service; import android.content.Intent; import android.os.Handler;

解决Android studio截屏出错

我的系统是w8.1 64,在eclipse上截屏不会出错,到了android studio上结果出错了 输出: Unexpected error while obtaining screenshot: java.lang.IllegalStateException: @NotNull method com/android/tools/idea/ddms/screenshot/DeviceArtDescriptor.getArtDescriptor must not return null 谷歌之

Android自定义截屏功能,类似QQ截屏

因为公司业务需求 需要对一个屏幕进行截屏,但自带的截屏功能是远远不够项目的功能需求 ,我们是做一个画板软件 ,需要的像QQ那样截屏之后 ,可以看到我们自定义的工具,有画笔,按钮等等 .android自带的功能非常简单,只需要Intent隐式调用就完全足够了,但他是系统的应用 ,界面固定,无法定制修改.实现方法跟办法有很多种,下面记录下我实现的方法 .我是这样一个思路 ,重写一个View组件 ,在OnDraw里面只负责不画图形(包括半透明的四个矩形,亮框矩形,亮框上的四个小圆点),Ontouch方