android4.2在导航栏上增加截屏快捷键

    这段时间在公司工作,由于客户的需求,需要在android4.2导航栏上加一个截屏图标。上面界面都好实现,在\\192.168.1.9\builder1\a31s\android4.2\frameworks\base\packages\SystemUI\res\layout-sw600dp\navigation_bar.xml里修改布局文件,加一个截屏图标。下面是我项目中git日志。(+:表示增加的代码 -:表示删掉的代码
横屏时增加截屏图标:

竖屏时增加截屏图标:

可是截屏如何实现呢 ? 手机一般都有按键组合来截屏,如:音量下键+电源键。那么我们肯定可以仿照它自己的源码来实现这个功能 了。下面就追踪下源码截屏功能的实现。
在android中 所有对按键的捕获都是在PhoneWindowManager.java中。下面是搜索出来的路径

在这个类中,处理各种各样的事情,包括按键,常用按键处理事件是在 interceptKeyBeforeQueueing()方法中。 这方法代码很多,处理各种事件。
在这个方法中,我们可以看到有这样了一个方法:
switch (keyCode) {
    ..........................
    case KeyEvent.KEYCODE_POWER: {          //按下电源键
                result &= ~ACTION_PASS_TO_USER;
                if (down) {              //down在上面定义的  final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;  判断是否 按下按键-
                    if (isScreenOn && !mPowerKeyTriggered
                            && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                        mPowerKeyTriggered = true;
                        mPowerKeyTime = event.getDownTime();      //获取电源按键按下时的时间,主要是判断组合组的时间差。
                        interceptScreenshotChord();           //截屏方法   
                    }

                    ITelephony telephonyService = getTelephonyService();
                    boolean hungUp = false;
                    if (telephonyService != null) {
                        try {
                            if (telephonyService.isRinging()) {
                                // Pressing Power while there's a ringing incoming
                                // call should silence the ringer.
                                telephonyService.silenceRinger();
                            } else if ((mIncallPowerBehavior
                                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                                    && telephonyService.isOffhook()) {
                                // Otherwise, if "Power button ends call" is enabled,
                                // the Power button will hang up any current active call.
                                hungUp = telephonyService.endCall();
                            }
                        } catch (RemoteException ex) {
                            Log.w(TAG, "ITelephony threw RemoteException", ex);
                        }
                    }
                    interceptPowerKeyDown(!isScreenOn || hungUp
                            || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
                } else {
                    mPowerKeyTriggered = false;
                    cancelPendingScreenshotChordAction();
                    if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
                        result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP;
                    }
                    mPendingPowerKeyUpCanceled = false;
                }
                break;
            }
    ............................

在这个方法, 它进行了按键的判断,是不是电源加音量-这个组件键,如果想要修改这个组合键,就可以在这里实现,只需要底层提供接口,获取按键的接口。从上面代码看出主要调用了interceptScreenshotChord();这个方法。
interceptScreenshotChord();方法代码如下:
private void interceptScreenshotChord() {
        if (mScreenshotChordEnabled
                && mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {       
            final long now = SystemClock.uptimeMillis();             //获取现在的时间
            if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
                    && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {          //判断时间差,有没有超过SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS这个毫秒数
                mVolumeDownKeyConsumedByScreenshotChord = true;
                cancelPendingPowerKeyAction();

                mHandler.postDelayed(mScreenshotChordLongPress, getScreenshotChordLongPressDelay());   //通过handler异步去处理
            }
        }
    }
在代码中继续追踪mScreenshotChordLongPress这个对象作了什么操作:
private final Runnable mScreenshotChordLongPress = new Runnable() {
        public void run() {
            takeScreenshot();     //开一个线程去处理截屏
        }
    };
这个方法很简单,直接创建了一个Runnable对象去处理截屏,继续追踪takeScreenshot()这个方法:
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);    //messager不熟 以后记得研究
                        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.bindService(
                    intent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) {   //绑定服务
                mScreenshotConnection = conn;
                mHandler.postDelayed(mScreenshotTimeout, 10000);
            }
        }
    }

    从上面代码可以看出,截屏它是调用了一个服务去操作了,同样可以继续追踪到TakeScreenshotService.java这个服务类里,看在服务代码中,它是作了什么样的操作的。
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);    //创建一个GlobalScreenshot对象
                    }
                    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();
    }

上面的服务也比较简单,主要是调用了GlobalScreenshot 的takeScreenshot()方法,同样的可以追踪到GlobalScreenshot.java里面去。
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};    //获取屏幕像素
        float degrees = getDegreesForRotation(mDisplay.getRotation());            //获取屏幕的rotation是多少?rotation主要是控制屏幕的方法的 像我这个机器默认就是180
        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]);
        }

        // Take the screenshot
        mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);       //通过像素宽,高获取一个bitmap对象 这就是屏幕的图片
        if (mScreenBitmap == null) {
            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);
            mScreenBitmap = ss;
        }

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

        // Start the post-screenshot animation
        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
                statusBarVisible, navBarVisible);
    }
由上看出,这个方法主要是对屏幕操作了,它判断了一系列参数,包括roation,像素等等,然而真正去截屏获取bitmap对象的还是一个Surface类,它是截屏的底层实现 ,通过jni调用。其他的都 是一些辅助设置,如设置透明度,动画等。

上面是对截屏的一系列的分析 ,如果来真正实现 自己的导航栏截屏呢 ?
加图标上面 已经介绍。如果加功能实现。对源码有一定了解的都 知道,肯定是在framework里面的SystemUI里面。路径:\\192.168.1.9\builder1\a31s\android4.2\frameworks\base\packages\SystemUI  我们都 知道 在android4.0后,平板跟手机同一套代码了。如果要是平板就找到\\192.168.1.9\builder1\a31s\android4.2\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\tablet\TabletStatusBar.java这个类,如果要是手机 就找\\192.168.1.9\builder1\a31s\android4.2\frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\PhoneStatusBar.java这个类,但是如果是平板4.2的系统请注意,它调用的代码也是调用PhoneStatusBar.java,4.2后的版就不清楚 没做过。这个问题纠结了我半天 ,改了TabletStatusBar.java硬是没有作用。这个方法的具体作用,就去看SystemUI详解吧!!这里主要讲下截屏。
其实我们完全可以把组合按键调用的代码也就是takeScreenshot()这个方法复制到这里来,然后对图标进行监听,就可以了。
首先在这个类中找到prepareNavigationBarView()这个方法,它对所有图标进行监听,我们只需要加上,mNavigationBarView.getScreenShotButton().setOnClickListener(mScreenShotClickListener);监听 。
然后对mScreenShotClickListener这个对象初始化。
定义这样一个全局变量
private View.OnClickListener mScreenShotClickListener = new View.OnClickListener() {
        public void onClick(View v) {
takeScreenshot(); 
        }
    };
takeScreenshot(); 这个方法就可以参考源代码所给的方法了,这样,我们就可以实现导航栏功能实现了。





















    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值