Android源码解析之截屏事件流程
今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程。用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了)。那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程。
我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电源键都会触发系统的截屏处理,所以这里的按键触发逻辑应该是Android系统的全局按键处理逻辑。
在android系统中,由于我们的每一个Android界面都是一个Activity,而界面的显示都是通过Window对象实现的,每个Window对象实际上都是PhoneWindow的实例,而每个PhoneWindow对象都一个PhoneWindowManager对象,当我们在Activity界面执行按键操作的时候,在将按键的处理操作分发到App之前,首先会回调PhoneWindowManager中的dispatchUnhandledKey方法,该方法主要用于执行当前App处理按键之前的操作,我们具体看一下该方法的实现。
/**{@inheritDoc}*/
@Override
publicKeyEventdispatchUnhandledKey(WindowStatewin,KeyEventevent,intpolicyFlags){
...
KeyEventfallbackEvent=null;
if((event.getFlags()&KeyEvent.FLAG_FALLBACK)==0){
finalKeyCharacterMapkcm=event.getKeyCharacterMap();
finalintkeyCode=event.getKeyCode();
finalintmetaState=event.getMetaState();
finalbooleaninitialDown=event.getAction()==KeyEvent.ACTION_DOWN
&&event.getRepeatCount()==0;
//Checkforfallbackactionsspecifiedbythekeycharactermap.
finalFallbackActionfallbackAction;
if(initialDown){
fallbackAction=kcm.getFallbackAction(keyCode,metaState);
}else{
fallbackAction=mFallbackActions.get(keyCode);
}
if(fallbackAction!=null){
...
finalintflags=event.getFlags()|KeyEvent.FLAG_FALLBACK;
fallbackEvent=KeyEvent.obtain(
event.getDownTime(),event.getEventTime(),
event.getAction(),fallbackAction.keyCode,
event.getRepeatCount(),fallbackAction.metaState,
event.getDeviceId(),event.getScanCode(),
flags,event.getSource(),null);
if(!interceptFallback(win,fallbackEvent,policyFlags)){
fallbackEvent.recycle();
fallbackEvent=null;
}
if(initialDown){
mFallbackActions.put(keyCode,fallbackAction);
}elseif(event.getAction()==KeyEvent.ACTION_UP){
mFallbackActions.remove(keyCode);
fallbackAction.recycle();
}
}
}
...
returnfallbackEvent;
}
这里我们关注一下方法体中调用的:interceptFallback方法,通过调用该方法将处理按键的操作下发到该方法中,我们继续看一下该方法的实现逻辑。
privatebooleaninterceptFallback(WindowStatewin,KeyEventfallbackEvent,intpolicyFlags){
intactions=interceptKeyBeforeQueueing(fallbackEvent,policyFlags);
if((actions&ACTION_PASS_TO_USER)!=0){
longdelayMillis=interceptKeyBeforeDispatching(
win,fallbackEvent,policyFlags);
if(delayMillis==0){
returntrue;
}
}
returnfalse;
}
然后我们看到在interceptFallback方法中我们调用了interceptKeyBeforeQueueing方法,通过阅读我们我们知道该方法主要实现了对截屏按键的处理流程,这样我们继续看一下interceptKeyBeforeWueueing方法的处理:
@Override
publicintinterceptKeyBeforeQueueing(KeyEventevent,intpolicyFlags){
if(!mSystemBooted){
//Ifwehavenotyetbooted,don'tletkeyeventsdoanything.
return0;
}
...
//Handlespecialkeys.
switch(keyCode){
caseKeyEvent.KEYCODE_VOLUME_DOWN:
caseKeyEvent.KEYCODE_VOLUME_UP:
caseKeyEvent.KEYCODE_VOLUME_MUTE:{
if(mUseTvRouting){
//OnTVsvolumekeysnevergototheforegroundapp
result&=~ACTION_PASS_TO_USER;
}
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();
}
}
...
returnresult;
}
可以发现这里首先判断当前系统是否已经boot完毕,若尚未启动完毕,则所有的按键操作都将失效,若启动完成,则执行后续的操作,这里我们只是关注音量减少按键和电源按键组合的处理事件。另外这里多说一句想安卓系统的HOME按键事件,MENU按键事件,进程列表按键事件等等都是在这里实现的,后续中我们会陆续介绍这方面的内容。
回到我们的interceptKeyBeforeQueueing方法,当我用按下音量减少按键的时候回进入到:caseKeyEvent.KEYCODE_VOLUME_MUTE分支并执行相应的逻辑,然后同时判断用户是否按下了电源键,若同时按下了电源键,则执行:
if(interactive&&!mScreenshotChordVolumeDownKeyTriggered
&&(event.getFlags()&KeyEvent.FLAG_FALLBACK)==0){
mScreenshotChordVolumeDownKeyTriggered=true;
mScreenshotChordVolumeDownKeyTime=event.getDownTime();
mScreenshotChordVolumeDownKeyConsumed=false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
}
可以发现这里的interceptScreenshotChrod方法就是系统准备开始执行截屏操作的开始,我们继续看一下interceptcreenshotChord方法的实现。
privatevoidinterceptScreenshotChord(){
if(mScreenshotChordEnabled
&&mScreenshotChordVolumeDownKeyTriggered&&mScreenshotChordPowerKeyTriggered
&&!mScreenshotChordVolumeUpKeyTriggered){
finallongnow=SystemClock.uptimeMillis();
if(now<=mScreenshotChordVolumeDownKeyTime+SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&&now<=mScreenshotChordPowerKeyTime
+SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS){
mScreenshotChordVolumeDownKeyConsumed=true;
cancelPendingPowerKeyAction();
mHandler.postDelayed(mScreenshotRunnable,getScreenshotChordLongPressDelay());
}
}
}
在方法体中我们最终会执行发送一个延迟的异步消息,请求执行截屏的操作而这里的延时时间,若当前输入框是打开状态,则延时时间为输入框关闭时间加上系统配置的按键超时时间,若当前输入框没有打开则直接是系统配置的按键超时处理时间,可看一下getScreenshotChordLongPressDelay方法的具体实现。
privatelonggetScreenshotChordLongPressDelay(){
if(mKeyguardDelegate.isShowing()){
//Doublethetimeittakestotakeascreenshotfromthekeyguard
return(long)(KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER*
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
}
returnViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout();
}
回到我们的interceptScreenshotChord方法,发送了异步消息之后系统最终会被我们发送的Runnable对象的run方法执行,这里关于异步消息的逻辑可参考:android源码解析之(二)–>异步消息机制
这样我们看一下Runnable类型的mScreenshotRunnable的run方法的实现:
privatefinalRunnablemScreenshotRunnable=newRunnable(){
@Override
publicvoidrun(){
takeScreenshot();
}
};
好吧,方法体中并未执行其他操作,直接就是调用了takeScreenshot方法,这样我们继续看一下takeScreenshot方法的实现。
privatevoidtakeScreenshot(){
synchronized(mScreenshotLock){
if(mScreenshotConnection!=null){
return;
}
ComponentNamecn=newComponentName("com.android.systemui",
"com.android.systemui.screenshot.TakeScreenshotService");
Intentintent=newIntent();
intent.setComponent(cn);
ServiceConnectionconn=newServiceConnection(){
@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
synchronized(mScreenshotLock){
if(mScreenshotConnection!=this){
return;
}
Messengermessenger=newMessenger(service);
Messagemsg=Message.obtain(null,1);
finalServiceConnectionmyConn=this;
Handlerh=newHandler(mHandler.getLooper()){
@Override
publicvoidhandleMessage(Messagemsg){
synchronized(mScreenshotLock){
if(mScreenshotConnection==myConn){
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection=null;
mHandler.removeCallbacks(mScreenshotTimeout);
}
}
}
};
msg.replyTo=newMessenger(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(RemoteExceptione){
}
}
}
@Override
publicvoidonServiceDisconnected(ComponentNamename){}
};
if(mContext.bindServiceAsUser(
intent,conn,Context.BIND_AUTO_CREATE,UserHandle.CURRENT)){
mScreenshotConnection=conn;
mHandler.postDelayed(mScreenshotTimeout,10000);
}
}
}
可以发现这里通过反射机制创建了一个TakeScreenshotService对象然后调用了bindServiceAsUser,这样就创建了TakeScreenshotService服务并在服务创建之后发送了一个异步消息。好了,我们看一下TakeScreenshotService的实现逻辑。
publicclassTakeScreenshotServiceextendsService{
privatestaticfinalStringTAG="TakeScreenshotService";
privatestaticGlobalScreenshotmScreenshot;
privateHandlermHandler=newHandler(){
@Override
publicvoidhandleMessage(Messagemsg){
switch(msg.what){
case1:
finalMessengercallback=msg.replyTo;
if(mScreenshot==null){
mScreenshot=newGlobalScreenshot(TakeScreenshotService.this);
}
mScreenshot.takeScreenshot(newRunnable(){
@Overridepublicvoidrun(){
Messagereply=Message.obtain(null,1);
try{
callback.send(reply);
}catch(RemoteExceptione){
}
}
},msg.arg1>0,msg.arg2>0);
}
}
};
@Override
publicIBinderonBind(Intentintent){
returnnewMessenger(mHandler).getBinder();
}
}
可以发现在在TakeScreenshotService类的定义中有一个Handler成员变量,而我们在启动TakeScreentshowService的时候回发送一个异步消息,这样就会执行mHandler的handleMessage方法,然后在handleMessage方法中我们创建了一个GlobalScreenshow对象,然后执行了takeScreenshot方法,好吧,继续看一下takeScreentshot方法的执行逻辑。
/**
*Takesascreenshotofthecurrentdisplayandshowsananimation.
*/
voidtakeScreenshot(Runnablefinisher,booleanstatusBarVisible,booleannavBarVisible){
//Weneedtoorientthescreenshotcorrectly(andtheSurfaceapiseemstotakescreenshots
//onlyinthenaturalorientationofthedevice:!)
mDisplay.getRealMetrics(mDisplayMetrics);
float[]dims={mDisplayMetrics.widthPixels,mDisplayMetrics.heightPixels};
floatdegrees=getDegreesForRotation(mDisplay.getRotation());
booleanrequiresRotation=(degrees>0);
if(requiresRotation){
//Getthedimensionsofthedeviceinitsnativeorientation
mDisplayMatrix.reset();
mDisplayMatrix.preRotate(-degrees);
mDisplayMatrix.mapPoints(dims);
dims[0]=Math.abs(dims[0]);
dims[1]=Math.abs(dims[1]);
}
//Takethescreenshot
mScreenBitmap=SurfaceControl.screenshot((int)dims[0],(int)dims[1]);
if(mScreenBitmap==null){
notifyScreenshotError(mContext,mNotificationManager);
finisher.run();
return;
}
if(requiresRotation){
//Rotatethescreenshottothecurrentorientation
Bitmapss=Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels,Bitmap.Config.ARGB_8888);
Canvasc=newCanvas(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);
//Recyclethepreviousbitmap
mScreenBitmap.recycle();
mScreenBitmap=ss;
}
//Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
//Startthepost-screenshotanimation
startAnimation(finisher,mDisplayMetrics.widthPixels,mDisplayMetrics.heightPixels,
statusBarVisible,navBarVisible);
}
可以看到这里后两个参数:statusBarVisible,navBarVisible是否可见,而这两个参数在我们PhoneWindowManager.takeScreenshot方法传递的:
if(mStatusBar!=null&&mStatusBar.isVisibleLw()) msg.arg1=1; if(mNavigationBar!=null&&mNavigationBar.isVisibleLw()) msg.arg2=1;
可见若果mStatusBar可见,则传递的statusBarVisible为true,若mNavigationBar可见,则传递的navBarVisible为true。然后我们在截屏的时候判断nStatusBar是否可见,mNavigationBar是否可见,若可见的时候则截屏同样将其截屏出来。继续回到我们的takeScreenshot方法,然后调用了:
//Takethescreenshot mScreenBitmap=SurfaceControl.screenshot((int)dims[0],(int)dims[1]);
方法,看注释,这里就是执行截屏事件的具体操作了,然后我看一下SurfaceControl.screenshot方法的具体实现,另外这里需要注意的是,截屏之后返回的是一个Bitmap对象,其实熟悉android绘制机制的童鞋应该知道android中所有显示能够显示的东西,在内存中表现都是Bitmap对象。
publicstaticBitmapscreenshot(intwidth,intheight){
//TODO:shouldtakethedisplayasaparameter
IBinderdisplayToken=SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
returnnativeScreenshot(displayToken,newRect(),width,height,0,0,true,
false,Surface.ROTATION_0);
}
好吧,这里调用的是nativeScreenshot方法,它是一个native方法,具体的实现在JNI层,这里就不做过多的介绍了。继续回到我们的takeScreenshot方法,在调用了截屏方法screentshot之后,判断是否截屏成功:
if(mScreenBitmap==null){
notifyScreenshotError(mContext,mNotificationManager);
finisher.run();
return;
}
若截屏之后,截屏的bitmap对象为空,这里判断截屏失败,调用了notifyScreenshotError方法,发送截屏失败的notification通知。
staticvoidnotifyScreenshotError(Contextcontext,NotificationManagernManager){
Resourcesr=context.getResources();
//Clearallexistingnotification,composethenewnotificationandshowit
Notification.Builderb=newNotification.Builder(context)
.setTicker(r.getString(R.string.screenshot_failed_title))
.setContentTitle(r.getString(R.string.screenshot_failed_title))
.setContentText(r.getString(R.string.screenshot_failed_text))
.setSmallIcon(R.drawable.stat_notify_image_error)
.setWhen(System.currentTimeMillis())
.setVisibility(Notification.VISIBILITY_PUBLIC)//oktoshowoutsidelockscreen
.setCategory(Notification.CATEGORY_ERROR)
.setAutoCancel(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
Notificationn=
newNotification.BigTextStyle(b)
.bigText(r.getString(R.string.screenshot_failed_text))
.build();
nManager.notify(R.id.notification_screenshot,n);
}
然后继续看takeScreenshot方法,判断截屏的图像是否需要旋转,若需要的话,则旋转图像:
if(requiresRotation){
//Rotatethescreenshottothecurrentorientation
Bitmapss=Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels,Bitmap.Config.ARGB_8888);
Canvasc=newCanvas(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);
//Recyclethepreviousbitmap
mScreenBitmap.recycle();
mScreenBitmap=ss;
}
在takeScreenshot方法的最后若截屏成功,我们调用了:
//Startthepost-screenshotanimation startAnimation(finisher,mDisplayMetrics.widthPixels,mDisplayMetrics.heightPixels, statusBarVisible,navBarVisible);
开始截屏的动画,好吧,看一下动画效果的实现:
/**
*Startstheanimationaftertakingthescreenshot
*/
privatevoidstartAnimation(finalRunnablefinisher,intw,inth,booleanstatusBarVisible,
booleannavBarVisible){
//Addtheviewfortheanimation
mScreenshotView.setImageBitmap(mScreenBitmap);
mScreenshotLayout.requestFocus();
//Setuptheanimationwiththescreenshotjusttaken
if(mScreenshotAnimation!=null){
mScreenshotAnimation.end();
mScreenshotAnimation.removeAllListeners();
}
mWindowManager.addView(mScreenshotLayout,mWindowLayoutParams);
ValueAnimatorscreenshotDropInAnim=createScreenshotDropInAnimation();
ValueAnimatorscreenshotFadeOutAnim=createScreenshotDropOutAnimation(w,h,
statusBarVisible,navBarVisible);
mScreenshotAnimation=newAnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim,screenshotFadeOutAnim);
mScreenshotAnimation.addListener(newAnimatorListenerAdapter(){
@Override
publicvoidonAnimationEnd(Animatoranimation){
//Savethescreenshotoncewehaveabitoftimenow
saveScreenshotInWorkerThread(finisher);
mWindowManager.removeView(mScreenshotLayout);
//Clearanyreferencestothebitmap
mScreenBitmap=null;
mScreenshotView.setImageBitmap(null);
}
});
mScreenshotLayout.post(newRunnable(){
@Override
publicvoidrun(){
//Playtheshuttersoundtonotifythatwe'vetakenascreenshot
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE,null);
mScreenshotView.buildLayer();
mScreenshotAnimation.start();
}
});
}
好吧,经过着一些列的操作之后我们实现了截屏之后的动画效果了,这里暂时不分析动画效果,我们看一下动画效果之后做了哪些?还记不记的一般情况下我们截屏之后都会收到一个截屏的notification通知?这里应该也是在其AnimatorListenerAdapter的onAnimationEnd方法中实现的,也就是动画执行完成之后,我们看一下其saveScreenshotInWorkerThread方法的实现:
/**
*Createsanewworkerthreadandsavesthescreenshottothemediastore.
*/
privatevoidsaveScreenshotInWorkerThread(Runnablefinisher){
SaveImageInBackgroundDatadata=newSaveImageInBackgroundData();
data.context=mContext;
data.image=mScreenBitmap;
data.iconSize=mNotificationIconSize;
data.finisher=finisher;
data.previewWidth=mPreviewWidth;
data.previewheight=mPreviewHeight;
if(mSaveInBgTask!=null){
mSaveInBgTask.cancel(false);
}
mSaveInBgTask=newSaveImageInBackgroundTask(mContext,data,mNotificationManager,
R.id.notification_screenshot).execute(data);
}
好吧,这里主要逻辑就是构造了一个SaveImageInBackgroundTask对象,看样子发送截屏成功的通知应该是在这里实现的,我们看一下SaveImageInBackgroundTask构造方法的实现逻辑:
SaveImageInBackgroundTask(Contextcontext,SaveImageInBackgroundDatadata,
NotificationManagernManager,intnId){
...
//Showtheintermediatenotification
mTickerAddSpace=!mTickerAddSpace;
mNotificationId=nId;
mNotificationManager=nManager;
finallongnow=System.currentTimeMillis();
mNotificationBuilder=newNotification.Builder(context)
.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)
.setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color));
mNotificationStyle=newNotification.BigPictureStyle()
.bigPicture(picture.createAshmemBitmap());
mNotificationBuilder.setStyle(mNotificationStyle);
//For"public"situationswewanttoshowallthesameinfobut
//omittheactualscreenshotimage.
mPublicNotificationBuilder=newNotification.Builder(context)
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(R.drawable.stat_notify_image)
.setCategory(Notification.CATEGORY_PROGRESS)
.setWhen(now)
.setColor(r.getColor(
com.android.internal.R.color.system_notification_accent_color));
mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());
Notificationn=mNotificationBuilder.build();
n.flags|=Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(nId,n);
//Onthetablet,thelargeiconmakesthenotificationappearasifitisclickable(and
//onsmalldevices,thelargeiconisnotshown)sodefershowingthelargeiconuntil
//wecomposethefinalpost-savenotificationbelow.
mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
//Butwestilldon'tsetitfortheexpandedview,allowingthesmallIcontoshowhere.
mNotificationStyle.bigLargeIcon((Bitmap)null);
}
可以发现在构造方法的后面狗仔了一个NotificationBuilder对象,然后发送了一个截屏成功的Notification,
这样我们在截屏动画之后就收到了Notification的通知了。
总结:
在PhoneWindowManager的dispatchUnhandledKey方法中处理App无法处理的按键事件,当然也包括音量减少键和电源按键的组合按键
通过一系列的调用启动TakeScreenshotService服务,并通过其执行截屏的操作。
具体的截屏代码是在native层实现的。
截屏操作时候,若截屏失败则直接发送截屏失败的notification通知。
截屏之后,若截屏成功,则先执行截屏的动画,并在动画效果执行完毕之后,发送截屏成功的notification的通知
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。