当前位置: 萬仟网 > IT编程>移动开发>Android > Android8.1 MTK平台 截屏功能分析

Android8.1 MTK平台 截屏功能分析

2019年08月17日 17:41  | 萬仟网IT编程  | 我要评论

前言

涉及到的源码有

frameworks\base\services\core\java\com\android\server\policy\phonewindowmanager.java

vendor\mediatek\proprietary\packages\apps\systemui\src\com\android\systemui\screenshot\takescreenshotservice.java
vendor\mediatek\proprietary\packages\apps\systemui\src\com\android\systemui\screenshot\globalscreenshot.java

按键处理都是在 phonewindowmanager 中,真正截屏的功能实现在 globalscreenshot 中, phonewindowmanager 和 systemui 通过 bind takescreenshotservice 来实现截屏功能

流程

一般未经过特殊定制的 android 系统,截屏都是通过同时按住音量下键和电源键来截屏,后来我们使用的一些华为、oppo等厂商的系统你会发现可以通过三指滑动来截屏,下一篇我们会定制此功能,而且截屏显示风格类似 iphone 在左下角显示截屏缩略图,点击可跳转放大查看,3s 无操作后向左自动滑动消失。

好了,现在我们先来理一下系统截屏的流程

    system_process d/windowmanager: interceptkeyti keycode=25 down=true repeatcount=0 keyguardon=false mhomepressed=false canceled=false metastate:0
    system_process d/windowmanager: interceptkeytq keycode=25 interactive=true keyguardactive=false policyflags=22000000 down =false canceled = false iswakekey=false mvolumedownkeytriggered =true result = 1 usehapticfeedback = false isinjected = false
    system_process d/windowmanager: interceptkeyti keycode=25 down=false repeatcount=0 keyguardon=false mhomepressed=false canceled=false metastate:0
    system_process d/windowmanager: interceptkeytq keycode=26 interactive=true keyguardactive=false policyflags=22000000 down =false canceled = false iswakekey=false mvolumedownkeytriggered =false result = 1 usehapticfeedback = false isinjected = false

上面是按下音量下键和电源键的日志,音量下键对应 keycode=25 ,电源键对应 keycode=26,来看到 phonewindowmanager 中的 interceptkeybeforequeueing() 方法,在此处处理按键操作

    /** {@inheritdoc} */
    @override
    public int interceptkeybeforequeueing(keyevent event, int policyflags) {
        if (!msystembooted) {
            // if we have not yet booted, don't let key events do anything.
            return 0;
        }

        .....

        if (debug_input) {
            log.d(tag, "interceptkeytq keycode=" + keycode
                    + " interactive=" + interactive + " keyguardactive=" + keyguardactive
                    + " policyflags=" + integer.tohexstring(policyflags));
        }

      .....

        // handle special keys.
        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();
                            interceptaccessibilityshortcutchord();
                        }
                    } else {
                        mscreenshotchordvolumedownkeytriggered = false;
                        cancelpendingscreenshotchordaction();
                        cancelpendingaccessibilityshortcutaction();
                    }
                } 
        ....
    }

看到 keycode_volume_down 中,记录当前按下音量下键的时间 mscreenshotchordvolumedownkeytime,cancelpendingpowerkeyaction() 移除电源键长按消息 msg_power_long_press,来看下核心方法 interceptscreenshotchord()

// time to volume and power must be pressed within this interval of each other.
private static final long screenshot_chord_debounce_delay_millis = 150;

private void interceptscreenshotchord() {
        if (mscreenshotchordenabled
                && mscreenshotchordvolumedownkeytriggered && mscreenshotchordpowerkeytriggered
                && !ma11yshortcutchordvolumeupkeytriggered) {
            final long now = systemclock.uptimemillis();
            if (now <= mscreenshotchordvolumedownkeytime + screenshot_chord_debounce_delay_millis
                    && now <= mscreenshotchordpowerkeytime
                            + screenshot_chord_debounce_delay_millis) {
                mscreenshotchordvolumedownkeyconsumed = true;
                cancelpendingpowerkeyaction();
                mscreenshotrunnable.setscreenshottype(take_screenshot_fullscreen);
                mhandler.postdelayed(mscreenshotrunnable, getscreenshotchordlongpressdelay());
            }
        }
    }

只有当电源键按下时 mscreenshotchordpowerkeytriggered 才为 true, 当两个按键的按下时间都大于 150 时,延时执行截屏任务 mscreenshotrunnable

private long getscreenshotchordlongpressdelay() {
        if (mkeyguarddelegate.isshowing()) {
            // double the time it takes to take a screenshot from the keyguard
            return (long) (keyguard_screenshot_chord_delay_multiplier *
                    viewconfiguration.get(mcontext).getdeviceglobalactionkeytimeout());
        }
        return viewconfiguration.get(mcontext).getdeviceglobalactionkeytimeout();
    }

若当前输入框是打开状态,则延时时间为输入框关闭时间加上系统配置的按键超时时间,若当前输入框没有打开则直接是系统配置的按键超时处理时间

紧接着看下 mscreenshotrunnable 都做了什么操作

private class screenshotrunnable implements runnable {
        private int mscreenshottype = take_screenshot_fullscreen;

        public void setscreenshottype(int screenshottype) {
            mscreenshottype = screenshottype;
        }

        @override
        public void run() {
            takescreenshot(mscreenshottype);
        }
    }

private final screenshotrunnable mscreenshotrunnable = new screenshotrunnable();

可以看到在线程中调用了 takescreenshot(),默认不设置截屏类型就是全屏,截屏类型有 take_screenshot_selected_region 选定的区域 和 take_screenshot_fullscreen 全屏两种类型

// assume this is called from the handler thread.
    private void takescreenshot(final int screenshottype) {
        synchronized (mscreenshotlock) {
            if (mscreenshotconnection != null) {
                return;
            }
            final componentname servicecomponent = new componentname(sysui_package,
                    sysui_screenshot_service);
            final intent serviceintent = new intent();
            serviceintent.setcomponent(servicecomponent);
            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, screenshottype);
                        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) {
                    synchronized (mscreenshotlock) {
                        if (mscreenshotconnection != null) {
                            mcontext.unbindservice(mscreenshotconnection);
                            mscreenshotconnection = null;
                            mhandler.removecallbacks(mscreenshottimeout);
                            notifyscreenshoterror();
                        }
                    }
                }
            };
            if (mcontext.bindserviceasuser(serviceintent, conn,
                    context.bind_auto_create | context.bind_foreground_service_while_awake,
                    userhandle.current)) {
                mscreenshotconnection = conn;
                mhandler.postdelayed(mscreenshottimeout, 10000);
            }
        }
    }

takescreenshot 中通过 bind systemui中的 takescreenshotservice 建立连接,连接成功后通过 messenger 在两个进程中传递消息通行,有点类似 aidl,关于 messenger 的介绍可参考 android进程间通讯之 messenger messenger 主要传递当前的 mstatusbar 和 mnavigationbar 是否可见,再来看 takescreenshotservice 中如何接收处理

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) {
            final messenger callback = msg.replyto;
            runnable finisher = new runnable() {
                @override
                public void run() {
                    message reply = message.obtain(null, 1);
                    try {
                        callback.send(reply);
                    } catch (remoteexception e) {
                    }
                }
            };

            // if the storage for this user is locked, we have no place to store
            // the screenshot, so skip taking it instead of showing a misleading
            // animation and error notification.
            if (!getsystemservice(usermanager.class).isuserunlocked()) {
                log.w(tag, "skipping screenshot because storage is locked!");
                post(finisher);
                return;
            }

            if (mscreenshot == null) {
                mscreenshot = new globalscreenshot(takescreenshotservice.this);
            }

            switch (msg.what) {
                case windowmanager.take_screenshot_fullscreen:
                    mscreenshot.takescreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
                    break;
                case windowmanager.take_screenshot_selected_region:
                    mscreenshot.takescreenshotpartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
                    break;
            }
        }
    };

    @override
    public ibinder onbind(intent intent) {
        return new messenger(mhandler).getbinder();
    }

    @override
    public boolean onunbind(intent intent) {
        if (mscreenshot != null) mscreenshot.stopscreenshot();
        return true;
    }
}

可以看到通过 mhandler 接收传递的消息,获取截屏类型和是否要包含状态栏、导航栏,通过创建 globalscreenshot 对象(真正干活的来了),调用 takescreenshot 执行截屏操作,继续跟进

    void takescreenshot(runnable finisher, boolean statusbarvisible, boolean navbarvisible) {
        mdisplay.getrealmetrics(mdisplaymetrics);
        takescreenshot(finisher, statusbarvisible, navbarvisible, 0, 0, mdisplaymetrics.widthpixels,
                mdisplaymetrics.heightpixels);
    }

    /**
     * takes a screenshot of the current display and shows an animation.
     */
    void takescreenshot(runnable finisher, boolean statusbarvisible, boolean navbarvisible,
            int x, int y, int width, int height) {
        // 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());
        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 = surfacecontrol.screenshot((int) dims[0], (int) dims[1]);
        if (mscreenbitmap == null) {
            notifyscreenshoterror(mcontext, mnotificationmanager,
                    r.string.screenshot_failed_to_capture_text);
            finisher.run();
            return;
        }

        if (requiresrotation) {
            // rotate the screenshot to the current orientation
            bitmap ss = bitmap.createbitmap(mdisplaymetrics.widthpixels,
                    mdisplaymetrics.heightpixels, bitmap.config.argb_8888,
                    mscreenbitmap.hasalpha(), mscreenbitmap.getcolorspace());
            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;
        }

        if (width != mdisplaymetrics.widthpixels || height != mdisplaymetrics.heightpixels) {
            // crop the screenshot to selected region
            bitmap cropped = bitmap.createbitmap(mscreenbitmap, x, y, width, height);
            mscreenbitmap.recycle();
            mscreenbitmap = cropped;
        }

        // optimizations
        mscreenbitmap.sethasalpha(false);
        mscreenbitmap.preparetodraw();

        // start the post-screenshot animation
        startanimation(finisher, mdisplaymetrics.widthpixels, mdisplaymetrics.heightpixels,
                statusbarvisible, navbarvisible);
    }

获取屏幕的宽高和当前屏幕方向以确定是否需要旋转图片,然后通过 surfacecontrol.screenshot 截屏,好吧,再继续往下看到

public static bitmap screenshot(int width, int height) {
    // todo: should take the display as a parameter
    ibinder displaytoken = surfacecontrol.getbuiltindisplay(
            surfacecontrol.built_in_display_id_main);
    return nativescreenshot(displaytoken, new rect(), width, height, 0, 0, true,
            false, surface.rotation_0);
}

这里调用的是 nativescreenshot 方法,它是一个 native 方法,具体的实现在jni层,这里就不做过多的介绍了。继续回到我们的 takescreenshot 方法,在调用了截屏方法 screentshot 之后,判断是否截屏成功:
截屏失败则调用 notifyscreenshoterror 发送通知。截屏成功,则调用 startanimation 播放动画,来分析下动画,后面我们会改这个动画的效果

    /**
     * starts the animation after taking the screenshot
     */
    private void startanimation(final runnable finisher, int w, int h, boolean statusbarvisible,
            boolean navbarvisible) {
        // if power save is on, show a toast so there is some visual indication that a screenshot
        // has been taken.
        powermanager powermanager = (powermanager) mcontext.getsystemservice(context.power_service);
        if (powermanager.ispowersavemode()) {
            toast.maketext(mcontext, r.string.screenshot_saved_title, toast.length_short).show();
        }

        // add the view for the animation
        mscreenshotview.setimagebitmap(mscreenbitmap);
        mscreenshotlayout.requestfocus();

        // setup the animation with the screenshot just taken
        if (mscreenshotanimation != null) {
            if (mscreenshotanimation.isstarted()) {
                mscreenshotanimation.end();
            }
            mscreenshotanimation.removealllisteners();
        }

        mwindowmanager.addview(mscreenshotlayout, mwindowlayoutparams);
        valueanimator screenshotdropinanim = createscreenshotdropinanimation();
        valueanimator screenshotfadeoutanim = createscreenshotdropoutanimation(w, h,
                statusbarvisible, navbarvisible);
        mscreenshotanimation = new animatorset();
        mscreenshotanimation.playsequentially(screenshotdropinanim, screenshotfadeoutanim);
        mscreenshotanimation.addlistener(new animatorlisteneradapter() {
            @override
            public void onanimationend(animator animation) {
                // save the screenshot once we have a bit of time now
                savescreenshotinworkerthread(finisher);
                mwindowmanager.removeview(mscreenshotlayout);

                // clear any references to the bitmap
                mscreenbitmap = null;
                mscreenshotview.setimagebitmap(null);
            }
        });
        mscreenshotlayout.post(new runnable() {
            @override
            public void run() {
                // play the shutter sound to notify that we've taken a screenshot
                mcamerasound.play(mediaactionsound.shutter_click);

                mscreenshotview.setlayertype(view.layer_type_hardware, null);
                mscreenshotview.buildlayer();
                mscreenshotanimation.start();
            }
        });
    }

先判断是否是低电量模式,若是发出已抓取屏幕截图的 toast,然后通过 windowmanager 在屏幕中间添加一个装有截屏缩略图的 view,同时创建两个动画组合,通过 mcamerasound 播放截屏咔嚓声并执行动画,动画结束后移除刚刚添加的 view,同时调用 savescreenshotinworkerthread 保存图片到媒体库,我们直接来看 saveimageinbackgroundtask

class saveimageinbackgroundtask extends asynctask<void, void, void> {
    .....

    saveimageinbackgroundtask(context context, saveimageinbackgrounddata data,
            notificationmanager nmanager) {
       ......

        mnotificationbuilder = new notification.builder(context, notificationchannels.screenshots)
            .setticker(r.getstring(r.string.screenshot_saving_ticker)
                    + (mtickeraddspace ? " " : ""))
            .setcontenttitle(r.getstring(r.string.screenshot_saving_title))
            .setcontenttext(r.getstring(r.string.screenshot_saving_text))
            .setsmallicon(r.drawable.stat_notify_image)
            .setwhen(now)
            .setshowwhen(true)
            .setcolor(r.getcolor(com.android.internal.r.color.system_notification_accent_color))
            .setstyle(mnotificationstyle)
            .setpublicversion(mpublicnotificationbuilder.build());
        mnotificationbuilder.setflag(notification.flag_no_clear, true);
        systemui.overridenotificationappname(context, mnotificationbuilder);

        mnotificationmanager.notify(systemmessage.note_global_screenshot,
                mnotificationbuilder.build());
    }

    @override
    protected void doinbackground(void... params) {
        if (iscancelled()) {
            return null;
        }

        // by default, asynctask sets the worker thread to have background thread priority, so bump
        // it back up so that we save a little quicker.
        process.setthreadpriority(process.thread_priority_foreground);

        context context = mparams.context;
        bitmap image = mparams.image;
        resources r = context.getresources();

        try {
            // create screenshot directory if it doesn't exist
            mscreenshotdir.mkdirs();

            // media provider uses seconds for date_modified and date_added, but milliseconds
            // for date_taken
            long dateseconds = mimagetime / 1000;

            // save
            outputstream out = new fileoutputstream(mimagefilepath);
            image.compress(bitmap.compressformat.png, 100, out);
            out.flush();
            out.close();

            // save the screenshot to the mediastore
            contentvalues values = new contentvalues();
            contentresolver resolver = context.getcontentresolver();
            values.put(mediastore.images.imagecolumns.data, mimagefilepath);
            values.put(mediastore.images.imagecolumns.title, mimagefilename);
            values.put(mediastore.images.imagecolumns.display_name, mimagefilename);
            values.put(mediastore.images.imagecolumns.date_taken, mimagetime);
            values.put(mediastore.images.imagecolumns.date_added, dateseconds);
            values.put(mediastore.images.imagecolumns.date_modified, dateseconds);
            values.put(mediastore.images.imagecolumns.mime_type, "image/png");
            values.put(mediastore.images.imagecolumns.width, mimagewidth);
            values.put(mediastore.images.imagecolumns.height, mimageheight);
            values.put(mediastore.images.imagecolumns.size, new file(mimagefilepath).length());
            uri uri = resolver.insert(mediastore.images.media.external_content_uri, values);

            // create a share intent
            string subjectdate = dateformat.getdatetimeinstance().format(new date(mimagetime));
            string subject = string.format(screenshot_share_subject_template, subjectdate);
            intent sharingintent = new intent(intent.action_send);
            sharingintent.settype("image/png");
            sharingintent.putextra(intent.extra_stream, uri);
            sharingintent.putextra(intent.extra_subject, subject);

            // create a share action for the notification. note, we proxy the call to sharereceiver
            // because remoteviews currently forces an activity options on the pendingintent being
            // launched, and since we don't want to trigger the share sheet in this case, we will
            // start the chooser activitiy directly in sharereceiver.
            pendingintent shareaction = pendingintent.getbroadcast(context, 0,
                    new intent(context, globalscreenshot.sharereceiver.class)
                            .putextra(sharing_intent, sharingintent),
                    pendingintent.flag_cancel_current);
            notification.action.builder shareactionbuilder = new notification.action.builder(
                    r.drawable.ic_screenshot_share,
                    r.getstring(com.android.internal.r.string.share), shareaction);
            mnotificationbuilder.addaction(shareactionbuilder.build());

            // create a delete action for the notification
            pendingintent deleteaction = pendingintent.getbroadcast(context, 0,
                    new intent(context, globalscreenshot.deletescreenshotreceiver.class)
                            .putextra(globalscreenshot.screenshot_uri_id, uri.tostring()),
                    pendingintent.flag_cancel_current | pendingintent.flag_one_shot);
            notification.action.builder deleteactionbuilder = new notification.action.builder(
                    r.drawable.ic_screenshot_delete,
                    r.getstring(com.android.internal.r.string.delete), deleteaction);
            mnotificationbuilder.addaction(deleteactionbuilder.build());

            mparams.imageuri = uri;
            mparams.image = null;
            mparams.errormsgresid = 0;
        } catch (exception e) {
            // ioexception/unsupportedoperationexception may be thrown if external storage is not
            // mounted
            slog.e(tag, "unable to save screenshot", e);
            mparams.clearimage();
            mparams.errormsgresid = r.string.screenshot_failed_to_save_text;
        }

        // recycle the bitmap data
        if (image != null) {
            image.recycle();
        }

        return null;
    }

    @override
    protected void onpostexecute(void params) {
        if (mparams.errormsgresid != 0) {
            // show a message that we've failed to save the image to disk
            globalscreenshot.notifyscreenshoterror(mparams.context, mnotificationmanager,
                    mparams.errormsgresid);
        } else {
            // show the final notification to indicate screenshot saved
            context context = mparams.context;
            resources r = context.getresources();

            // create the intent to show the screenshot in gallery
            intent launchintent = new intent(intent.action_view);
            launchintent.setdataandtype(mparams.imageuri, "image/png");
            launchintent.setflags(
                    intent.flag_activity_new_task | intent.flag_grant_read_uri_permission);

            final long now = system.currenttimemillis();

            // update the text and the icon for the existing notification
            mpublicnotificationbuilder
                    .setcontenttitle(r.getstring(r.string.screenshot_saved_title))
                    .setcontenttext(r.getstring(r.string.screenshot_saved_text))
                    .setcontentintent(pendingintent.getactivity(mparams.context, 0, launchintent, 0))
                    .setwhen(now)
                    .setautocancel(true)
                    .setcolor(context.getcolor(
                            com.android.internal.r.color.system_notification_accent_color));
            mnotificationbuilder
                .setcontenttitle(r.getstring(r.string.screenshot_saved_title))
                .setcontenttext(r.getstring(r.string.screenshot_saved_text))
                .setcontentintent(pendingintent.getactivity(mparams.context, 0, launchintent, 0))
                .setwhen(now)
                .setautocancel(true)
                .setcolor(context.getcolor(
                        com.android.internal.r.color.system_notification_accent_color))
                .setpublicversion(mpublicnotificationbuilder.build())
                .setflag(notification.flag_no_clear, false);

            mnotificationmanager.notify(systemmessage.note_global_screenshot,
                    mnotificationbuilder.build());
        }
        mparams.finisher.run();
        mparams.clearcontext();
    }

    @override
    protected void oncancelled(void params) {
        // if we are cancelled while the task is running in the background, we may get null params.
        // the finisher is expected to always be called back, so just use the baked-in params from
        // the ctor in any case.
        mparams.finisher.run();
        mparams.clearimage();
        mparams.clearcontext();

        // cancel the posted notification
        mnotificationmanager.cancel(systemmessage.note_global_screenshot);
    }
}

简单说下, saveimageinbackgroundtask 构造方法中做了大量的准备工作,截屏图片的时间命名格式、截屏通知对象创建,在 doinbackground 中将截屏图片通过 contentresolver 存储至 mediastore,再创建两个 pendingintent,用于分享和删除截屏图片,在 onpostexecute 中发送刚刚创建的 notification 至 statubar 显示,到此截屏的流程就结束了。

其它

我们再回到 phonewindowmanager 中看下,通过上面我们知道要想截屏只需通过如下两行代码即可

mscreenshotrunnable.setscreenshottype(take_screenshot_fullscreen);
mhandler.post(mscreenshotrunnable);

通过搜索上面的关键代码,我们发现还有另外两处也调用了截屏的代码,一起来看下

@override
public long interceptkeybeforedispatching(windowstate win, keyevent event, int policyflags) {
    final boolean keyguardon = keyguardon();
    final int keycode = event.getkeycode();
    .....
    
    else if (keycode == keyevent.keycode_s && event.ismetapressed()
                && event.isctrlpressed()) {
            if (down && repeatcount == 0) {
                int type = event.isshiftpressed() ? take_screenshot_selected_region
                        : take_screenshot_fullscreen;
                mscreenshotrunnable.setscreenshottype(type);
                mhandler.post(mscreenshotrunnable);
                return -1;
            }
    }
    ....

    else if (keycode == keyevent.keycode_sysrq) {
        if (down && repeatcount == 0) {
            mscreenshotrunnable.setscreenshottype(take_screenshot_fullscreen);
            mhandler.post(mscreenshotrunnable);
        }
        return -1;
    }
    ......

}

也是在拦截按键消息分发之前的方法中,查看 keyevent 源码,第一种情况大概网上搜索了下,应该是接外设时,同时按下 s 键 + meta键 + ctrl键即可截屏,关于 meta 介绍可参考meta键始末 第二种情况是按下截屏键时,对应 keycode 为 120,可以用 adb shell input keyevent 120 模拟发现也能截屏

 /** key code constant: 's' key. */
    public static final int keycode_s               = 47;

/** key code constant: system request / print screen key. */
    public static final int keycode_sysrq           = 120;

常用按键对应值

ebfz5r.png

这样文章开头提到的三指截屏操作,我们就可以加在 phonewindowmanager 中,当手势监听获取到三指时,只需调用截屏的两行代码即可

总结

  • 在 phonewindowmanager 的 dispatchunhandledkey 方法中处理app无法处理的按键事件,当然也包括音量减少键和电源按键的组合按键

  • 通过一系列的调用启动 takescreenshotservice 服务,并通过其执行截屏的操作。

  • 具体的截屏代码是在 native 层实现的。

  • 截屏操作时候,若截屏失败则直接发送截屏失败的 notification 通知。

  • 截屏之后,若截屏成功,则先执行截屏的动画,并在动画效果执行完毕之后,发送截屏成功的 notification 的通知。

参考文章

android 截屏方法总结
android keycode列表

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

◎已有 0 人评论

Copyright © 2019  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com