2023年12月23日发(作者:)
Android的全局键(home键/长按耳机键)详解【android源码解析八】Android的全局键(home键/长按耳机键)详解【android源码解析八】 如果想在Android手机要想扩展一个实体键,就我知道而言有两种方法,基于Android4.0的源码来分析的和2.3的源码有点区别,区别不大,下面分享给大家: 转载请标明出处: (一)可以在frameworks层的这个类中定义一个值,在这个类中做处理就可以了。(Home键就是这么实现的)。效果图如下:(二)可以利用广播的形式,frameworks层这个类的onKeyDown( )对这个实体键发广播,上层接受这个广播来处理也可以达到这个效果。耳机键就是利用广播来接受的。无论在哪个界面长按耳机键,都会进入到音乐的界面。(长按耳机键的)效果图如下:下面我详细展开来说明一下:一、先说Home键的实现的大致流程,即---->为什么点击Home键,都进入到launcher的待机界面; (1)Home键的定义在 step1: frameworks/base/core/java/android/view/这个类中,在这个类中有个static的静态块: static { populateKeycodeSymbolicNames(); } step2: 这个populateKeycodeSymbolicNames()方法其实就是加载了许多键的定义,把这些键对应的值都放到Array数组中。private static void populateKeycodeSymbolicNames() { SparseArray
KEYCODE_SYMBOLIC_NAMES; (KEYCODE_UNKNOWN, "KEYCODE_UNKNOWN");
(KEYCODE_SOFT_LEFT, "KEYCODE_SOFT_LEFT"); (KEYCODE_SOFT_RIGHT,
"KEYCODE_SOFT_RIGHT"); (KEYCODE_HOME, "KEYCODE_HOME");
(KEYCODE_BACK, "KEYCODE_BACK"); (KEYCODE_CALL,
"KEYCODE_CALL"); (KEYCODE_ENDCALL, "KEYCODE_ENDCALL");
(KEYCODE_0, "KEYCODE_0"); (KEYCODE_1, "KEYCODE_1");
(KEYCODE_2, "KEYCODE_2"); (KEYCODE_3, "KEYCODE_3");
(KEYCODE_4, "KEYCODE_4"); (KEYCODE_5, "KEYCODE_5");
file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11]
(KEYCODE_6, "KEYCODE_6"); (KEYCODE_7, "KEYCODE_7");
(KEYCODE_8, "KEYCODE_8"); (KEYCODE_9, "KEYCODE_9");
step3: 而Home键对应的值如下: /** Key code constant: Home key. * This key is handled by the framework and is never
delivered to applications. */ public static final int KEYCODE_HOME = 3;(2)Home键的处理如下:在 step1:
frameworks/base/policy/src/com/android/internal/policy/impl/这个类中:在这个方法interceptKeyBeforeDispatching(... ... ...)中处理有对Home,Search,menu,音量大小键等等: /** {@inheritDoc} */ @Override public long interceptKeyBeforeDispatching(WindowState
win, KeyEvent event, int policyFlags) { final boolean keyguardOn = keyguardOn();
final int keyCode = Code(); final int repeatCount =
eatCount(); final int metaState = aState(); final int
flags = gs(); final boolean down = ion() ==
_DOWN; final boolean canceled = eled(); if (false) {
Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed); }
// If we think we might have a volume down & power key chord on the way // but we're
not sure, then tell the dispatcher to wait a little while and // try again later
before dispatching. if ((flags & _FALLBACK) == 0) { if
(mVolumeDownKeyTriggered && !mPowerKeyTriggered) { final long now =
Millis(); final long timeoutTime = mVolumeDownKeyTime +
SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS; if (now < timeoutTime) {
return timeoutTime - now; } } if (keyCode ==
E_VOLUME_DOWN && mVolumeDownKeyConsumedByScreenshotChord) {
if (!down) { mVolumeDownKeyConsumedByScreenshotChord = false;
} return -1; } } // First we always handle the home
key here, so applications // can never break it, although if keyguard is on, we do let
// it handle it, because that gives us the correct 5 second // timeout. if
(keyCode == E_HOME) { // If we have released the home key, and
didn't do anything else // while it was pressed, then it is time to go home!
file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11]
if (mHomePressed && !down) { mHomePressed = false; if
(!canceled) { // If an incoming call is ringing, HOME is totally disabled.
// (The user is already on the InCallScreen at this point, // and his ONLY
options are to answer or reject the call.) boolean incomingRinging =
false; try { ITelephony telephonyService =
getTelephonyService(); if (telephonyService != null) {
incomingRinging = ing(); } }
catch (RemoteException ex) { Log.w(TAG, "RemoteException from
getPhoneInterface()", ex); } if (incomingRinging) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); } else {
launchHomeFromHotKey(); } } else {
Log.i(TAG, "Ignoring HOME; event canceled."); } return -1;
} // If a system window has focus, then it doesn't make sense // right
now to interact with applications. Params attrs = win != null
rs() : null; if (attrs != null) { final int type =
; if (type == _KEYGUARD
|| type == _KEYGUARD_DIALOG) { // the "app"
is keyguard, so give it the key return 0; }
final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_; for (int
i=0; i WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) { // don't do anything, but also don't pass it to the app return -1; } } } if (down) { if (repeatCount == 0) { mHomePressed = true; } else if ((gs() & _LONG_PRESS) != 0) { if (!keyguardOn) { handleLongPressOnHome(); } } } return -1; } else if (keyCode == E_MENU) { ........ Step2: 插曲《网上有例子说怎么在自己的应用中屏蔽Home键》--->原理:是在你的应用的Activity中加入了锁屏的type,因为系统对锁屏界面,点击Home键失效!网摘代码如下:public class DMActivity extends Activity { private boolean flag = true;//true位屏蔽,false位不屏蔽 @Override public void onCreate(Bundle savedInstanceState) { te(savedInstanceState); file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] setContentView(); } @Override public void onAttachedToWindow() { if(flag) { dow().setType(_KEYGUARD); } chedToWindow(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == E_HOME){ return true; } return own(keyCode, event); } } Step3: 真正的原因如下,对锁屏模式的处理: // If a system window has focus, then it doesn't make sense // right now to interact with applications. Params attrs = win != null rs() : null; if (attrs != null) { final int type = ; if (type == _KEYGUARD || type == _KEYGUARD_DIALOG) { // the "app" is keyguard, so give it the key return 0; } final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_; for (int i=0; i WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) { // don't do anything, but also don't pass it to the app return -1; } } } Step4: 我们来看点击home键,为什么进入到launcher的待机界面: // If we have released the home key, and didn't do anything else // while it was pressed, then it is time to go home! if (mHomePressed && !down) { mHomePressed = false; if (!canceled) { // If an incoming call is ringing, HOME is totally disabled. // (The user is already on the InCallScreen at this point, // and his ONLY options are to answer or reject the call.) boolean incomingRinging = false; try { ITelephony telephonyService = getTelephonyService(); if (telephonyService != null) { incomingRinging = ing(); } } catch (RemoteException ex) { Log.w(TAG, "RemoteException from getPhoneInterface()", ex); } if (incomingRinging) { Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); } else { launchHomeFromHotKey(); } } else { file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] Log.i(TAG, "Ignoring HOME; event canceled."); } return -1; } Step5: 系统会判断,当前点击Home键并且没有电话打入的情况,才对Home键进行处理---->launchHomeFromHotKey();进入到----->launchHomeFromHotKey()方法中: /** * A home key -> launch home action was detected. Take the appropriate action * given the situation with the keyguard. */ void launchHomeFromHotKey() { if (ingAndNotHidden()) { // don't launch home if keyguard showing } else if (!mHideLockScreen && tRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home Unlock(new OnKeyguardExitResult() { public void onKeyguardExitResult(boolean success) { if (success) { try { ault().stopAppSwitches(); } catch (RemoteException e) { } sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(); } } }); } else { // no keyguard stuff to worry about, just launch home! try { ault().stopAppSwitches(); } catch (RemoteException e) { } sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(); } } Step6: 也是对锁屏模式有个判断,如果不在锁屏模式,就launch Home---->startDockOrHome(),进入——> void startDockOrHome() { Intent dock = createHomeDockIntent(); if (dock != null) { try { ctivity(dock); return; } catch (ActivityNotFoundException e) { } } ctivity(mHomeIntent); } Step 7: 其实这个createHomeDockIntent()方法就是对android手机的几种模式进行判断,The device is not in either car mode or desk mode The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOMEfile:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] 如果是以上模式,车载模式或者桌面模式,就返回dock不为空,否则为空。启动这个mHomeIntent。----->mHomeIntent定义如下: mHomeIntent = new Intent(_MAIN, null); egory(RY_HOME); gs(_ACTIVITY_NEW_TASK | _ACTIVITY_RESET_TASK_IF_NEEDED); Step 8: 这个mHomeIntent就是启动activity中配置Category属性的值为CATEGORY_HOME,启动的时候新启动一个任务,不是在当前的这个任务中启动launcherHome,而是新建一个task。 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。到这为止,Home键的流程已经分析完了。 二 、下面看看长按耳机键接受的广播的处理方式: (1)这个长按耳机键捕获是在类的onKeyDown()中,然后发送有序的广播---->如下: case E_HEADSETHOOK: case E_MEDIA_STOP: case E_MEDIA_NEXT: case E_MEDIA_PREVIOUS: case E_MEDIA_REWIND: case E_MEDIA_FAST_FORWARD: { Intent intent = new Intent(_MEDIA_BUTTON, null); ra(_KEY_EVENT, event); getContext().sendOrderedBroadcast(intent, null); return true; } 接受这个长按耳机键的广播是在Music的app中的-----> publicclassMediaButtonIntentReceiverextendsBroadcastReceiver{......} , 需要在中注册这个广播 intentAction = ion(); if file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] (_AUDIO_BECOMING_(intentAction)) { Intent i = new Intent(ECMD); ra(E, SE); oadcast(i); } else if (_MEDIA_(intentAction)) { KeyEvent event = (KeyEvent) celableExtra(_KEY_EVENT); if (event == null) { return; } int keycode = Code(); int action = ion(); long eventtime = ntTime(); // single quick press: pause/resume. // double press: next track // long press: start auto-shuffle mode. String command = null; switch (keycode) { case E_MEDIA_STOP: command = P; break; case E_HEADSETHOOK: case E_MEDIA_PLAY_PAUSE: command = GLEPAUSE; break; case E_MEDIA_NEXT: command = T; break; case E_MEDIA_PREVIOUS: command = VIOUS; break; case E_MEDIA_PAUSE: command = SE; break; case E_MEDIA_PLAY: command = Y; break; } if (command != null) { if (action == _DOWN) { if (mDown) { if (((command) || (command)) && mLastClickTime != 0 && eventtime - mLastClickTime > LONG_PRESS_DELAY) { ssage( Message(MSG_LONGPRESS_TIMEOUT, context)); } } else if (eatCount() == 0) { // only consider the first event in a sequence, not the repeat events, // so that we don't trigger in cases where the first event went to // a different app (e.g. when the user ends a phone call by // long pressing the headset button) // The service may or may not be running, but we need to send it // a command. Intent i = new Intent(context, file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] ); ion(ECMD); if (keycode == E_HEADSETHOOK && eventtime - mLastClickTime < 300) { ra(E, T); ervice(i); mLastClickTime = 0; } else { ra(E, command); ervice(i); mLastClickTime = eventtime; } mLaunched = false; mDown = true; } } else { Messages(MSG_LONGPRESS_TIMEOUT); mDown = false; } if (isOrderedBroadcast()) { abortBroadcast(); } } } } step1: 在方法if (action == _DOWN) { ... ... }做的处理,eatCount() == 0这个判断的意思是“是否长按耳机键?”,如果长按耳机键eatCount() 的值就一直增加。 step 2:短按耳机键:播放/暂停 --->音乐;短按启动这个类,并且传入参数---->在这个服务类中有个接受广播的内部类:如下--> private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = ion(); String cmd = ingExtra("command"); og("ive " + action + " / " + cmd); if ((cmd) || NEXT_(action)) { next(true); } else if ((cmd) || PREVIOUS_(action)) { prev(); } else if ((cmd) || TOGGLEPAUSE_(action)) { if (isPlaying()) { pause(); mInternalPause = false; } else { play(); } } else if ((cmd) || PAUSE_(action)) { pause(); mInternalPause = false; } else if ((cmd)) { play(); } else if ((cmd)) { pause(); mInternalPause = false; seek(0); } else if file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] ((cmd)) { // Someone asked us to refresh a set of specific widgets, probably // because they were just added. int[] appWidgetIds = ArrayExtra(_APPWIDGET_IDS); mUpdate(, appWidgetIds); } } }; 通过:如下方法来控制点击播放音乐,再次点击暂停,如此循环。else if ((cmd) || TOGGLEPAUSE_(action)) { if (isPlaying()) { pause(); mInternalPause = false; } else { play();}}内部类的广播是在启动中注册的,解除注册在onDestroy()的方法中。 IntentFilter commandFilter = new IntentFilter(); ion(SERVICECMD); ion(TOGGLEPAUSE_ACTION); ion(PAUSE_ACTION); ion(NEXT_ACTION); ion(PREVIOUS_ACTION); ion(PLAYSTATUS_REQUEST); registerReceiver(mIntentReceiver, commandFilter); Step 3: 长按耳机键--->发消息给mHandler, Messages(MSG_LONGPRESS_TIMEOUT); 在中有个内部类Handler()如下——> private static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch () { case MSG_LONGPRESS_TIMEOUT: if (!mLaunched) { Context context = (Context); Intent i = new Intent(); ra("autoshuffle", "true"); ss(context, ); gs(_ACTIVITY_NEW_TASK | _ACTIVITY_CLEAR_TOP); ctivity(i); mLaunched = true; } break; } } };file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] 长按耳机后就启动这个音乐播放类。并且传入参数“autoshuffle==true”,这个启动和launcher的启动相似,也是启动一个新的任务task,但是这个后面的标志有不同的地方_ACTIVITY_CLEAR_TOP。到这为止就是实现了在任何界面长按耳机键都能进入到music的主界面。 总结如下: 其实启动的时候,要注意当前activity的launcherMode是什么,如果是SingleTask,就要小心一下。比如说:想要长按耳机键,进入到launcher的Mainmenu界面,这时候如果单纯的用以上方法套,返回键点击的时候不会回到上个activity中,会有问题。因为launcher是一直启动的运行于每个task之中的,你再次启动Launcher的时候,无论是否设置属性“gs(_ACTIVITY_NEW_TASK)”,都会把前一个activity给finish掉,launcher会在栈顶也是栈底。因为launcher的launcherMode=singleTask。我们可以做个实验;三、例子写两个app,一个属性为singletask,一个为standard。应singletask的启动standard的activity,然后在再次基础上启动singletask的activity,看standard的activity是否会destory掉。step1:先看截图:launchMode="singleTask"launchMode="standard" Step 2:先启动launchMode="singleTask"的activity---->点击调用第二个App2的launchMode="standard"---->点击按钮调用第一个App1的launchMode="singleTask"---->点击返回键。看log分析:(1)点击启动launchMode="singleTask"的activity的log如下: (2)点击调用第二个App2的launchMode="standard"的activity的log如下图: (3)点击调用第一个App1launchMode="singleTask"的activity的log如下: 分析如下:看到这时候App2Activity--22已经执行了onStop()和onDestroy()方法了。验证了我以上的说法。 (4)点击返回键---->直接回到了launcher界面。log如下: file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] 备注:要想解决以上问题也是可以的。就是在以上第(2)步:点击调用第二个App2的launchMode="standard"的activity的时候设置flag。_ACTIVITY_NEW_TASK。就可以解决以上问题了,每次启动一个新的任务,这样就能返回到App2Activity了。 (1)点击启动launchMode="singleTask"的activity的log如下: (2)点击调用第二个App2的launchMode="standard"的activity的log如下图: (3)点击调用第一个App1launchMode="singleTask"的activity的log如下: 看到如上图:App2Activity--22--->只是onStop()了,没有onDestroy掉。 (4)点击返回键---->直接回到了launcher界面---->log如下: (5)通过log,我们可以看出返回到App2Activity了,我们再次点击返回键,--->返回到Launcher界面的log如下: 通过以上验证说明我的结论是正确的。launcherMode一直是android的核心技术,通过这次我会更加注意到activity的LauncherMode的。 欢迎各界人士留言,讨论,拍砖!转载请标明出处!file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11] file:///E|/世界五百强企业绝密文件请勿外传/Android的全局键(home键_长按耳机键)详解【android源码解析八】.txt[2016/11/7 13:44:11]


发布评论