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 names =

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{......} , 需要在中注册这个广播 。---->注册MediaButtonReceiver这个广播, 这个类中onReceive()方法定义的:代码如下---> @Override public void onReceive(Context context, Intent intent) { String

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]