Android的全局键(home键/长按耳机键)详解【android源码解析八】

转自     http://blog.csdn.net/wdaming1986/article/details/7539600

      如果想在Android手机要想扩展一个实体键,就我知道而言有两种方法,基于Android4.0的源码来分析的和2.3的源码有点区别,区别不大,下面分享给大家:

    转载请标明出处:

         (一)可以在frameworks层的KeyEvent.java这个类中定义一个值,在PhoneWindowManager.java这个类中做处理就可以了。(Home键就是这么实现的)。效果图如下

                                                        Android的全局键(home键/长按耳机键)详解【android源码解析八】_第1张图片

         (二)可以利用广播的形式,frameworks层PhoneWindow.java这个类的onKeyDown( )对这个实体键发广播,上层接受这个广播来处理也可以达到这个效果。耳机键就是利用广播来接受的。无论在哪个界面长按耳机键,都会进入到音乐的界面。(长按耳机键的效果图如下

                                                         Android的全局键(home键/长按耳机键)详解【android源码解析八】_第2张图片

下面我详细展开来说明一下:

 

   一、先说Home键的实现的大致流程,即---->为什么点击Home键,都进入到launcher的待机界面;

  (1)Home键的定义在

       step1: frameworks/base/core/java/android/view/KeyEvent.java这个类中,在KeyEvent.java这个类中有个static的静态块:

[java] view plain copy print ?
  1. static {  
  2.       populateKeycodeSymbolicNames();  
  3.   }  

      

       step2: 这个populateKeycodeSymbolicNames()方法其实就是加载了许多键的定义,把这些键对应的值都放到Array数组中。

[java] view plain copy print ?
  1. <span style="font-size: 16px;">private static void populateKeycodeSymbolicNames() {  
  2.         SparseArray<String> names = KEYCODE_SYMBOLIC_NAMES;  
  3.         names.append(KEYCODE_UNKNOWN, "KEYCODE_UNKNOWN");  
  4.         names.append(KEYCODE_SOFT_LEFT, "KEYCODE_SOFT_LEFT");  
  5.         names.append(KEYCODE_SOFT_RIGHT, "KEYCODE_SOFT_RIGHT");  
  6.         names.append(KEYCODE_HOME, "KEYCODE_HOME");  
  7.         names.append(KEYCODE_BACK, "KEYCODE_BACK");  
  8.         names.append(KEYCODE_CALL, "KEYCODE_CALL");  
  9.         names.append(KEYCODE_ENDCALL, "KEYCODE_ENDCALL");  
  10.         names.append(KEYCODE_0, "KEYCODE_0");  
  11.         names.append(KEYCODE_1, "KEYCODE_1");  
  12.         names.append(KEYCODE_2, "KEYCODE_2");  
  13.         names.append(KEYCODE_3, "KEYCODE_3");  
  14.         names.append(KEYCODE_4, "KEYCODE_4");  
  15.         names.append(KEYCODE_5, "KEYCODE_5");  
  16.         names.append(KEYCODE_6, "KEYCODE_6");  
  17.         names.append(KEYCODE_7, "KEYCODE_7");  
  18.         names.append(KEYCODE_8, "KEYCODE_8");  
  19.         names.append(KEYCODE_9, "KEYCODE_9");</span>  

       

       step3: 而Home键对应的值如下:

[java] view plain copy print ?
  1. <span style="font-size: 16px;">/** Key code constant: Home key. 
  2.     * This key is handled by the framework and is never delivered to applications. */  
  3.    public static final int KEYCODE_HOME            = 3;</span>  

 

(2)Home键的处理如下:在
      step1: frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java这个类中:

在这个方法interceptKeyBeforeDispatching(... ... ...)中处理有对Home,Search,menu,音量大小键等等:

[java] view plain copy print ?
  1. <span style="font-size: 16px;">  /** {@inheritDoc} */  
  2.     @Override  
  3.     public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {  
  4.         final boolean keyguardOn = keyguardOn();  
  5.         final int keyCode = event.getKeyCode();  
  6.         final int repeatCount = event.getRepeatCount();  
  7.         final int metaState = event.getMetaState();  
  8.         final int flags = event.getFlags();  
  9.         final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;  
  10.         final boolean canceled = event.isCanceled();  
  11.   
  12.         if (false) {  
  13.             Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="  
  14.                     + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);  
  15.         }  
  16.   
  17.         // If we think we might have a volume down & power key chord on the way  
  18.         // but we're not sure, then tell the dispatcher to wait a little while and  
  19.         // try again later before dispatching.  
  20.         if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {  
  21.             if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) {  
  22.                 final long now = SystemClock.uptimeMillis();  
  23.                 final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;  
  24.                 if (now < timeoutTime) {  
  25.                     return timeoutTime - now;  
  26.                 }  
  27.             }  
  28.             if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN  
  29.                     && mVolumeDownKeyConsumedByScreenshotChord) {  
  30.                 if (!down) {  
  31.                     mVolumeDownKeyConsumedByScreenshotChord = false;  
  32.                 }  
  33.                 return -1;  
  34.             }  
  35.         }  
  36.   
  37.         // First we always handle the home key here, so applications  
  38.         // can never break it, although if keyguard is on, we do let  
  39.         // it handle it, because that gives us the correct 5 second  
  40.         // timeout.  
  41.         if (keyCode == KeyEvent.KEYCODE_HOME) {  
  42.             // If we have released the home key, and didn't do anything else  
  43.             // while it was pressed, then it is time to go home!  
  44.             if (mHomePressed && !down) {  
  45.                 mHomePressed = false;  
  46.                 if (!canceled) {  
  47.                     // If an incoming call is ringing, HOME is totally disabled.  
  48.                     // (The user is already on the InCallScreen at this point,  
  49.                     // and his ONLY options are to answer or reject the call.)  
  50.                     boolean incomingRinging = false;  
  51.                     try {  
  52.                         ITelephony telephonyService = getTelephonyService();  
  53.                         if (telephonyService != null) {  
  54.                             incomingRinging = telephonyService.isRinging();  
  55.                         }  
  56.                     } catch (RemoteException ex) {  
  57.                         Log.w(TAG, "RemoteException from getPhoneInterface()", ex);  
  58.                     }  
  59.   
  60.                     if (incomingRinging) {  
  61.                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");  
  62.                     } else {  
  63.                         launchHomeFromHotKey();  
  64.                     }  
  65.                 } else {  
  66.                     Log.i(TAG, "Ignoring HOME; event canceled.");  
  67.                 }  
  68.                 return -1;  
  69.             }  
  70.   
  71.             // If a system window has focus, then it doesn't make sense  
  72.             // right now to interact with applications.  
  73.             WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;  
  74.             if (attrs != null) {  
  75.                 final int type = attrs.type;  
  76.                 if (type == WindowManager.LayoutParams.TYPE_KEYGUARD  
  77.                         || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {  
  78.                     // the "app" is keyguard, so give it the key  
  79.                     return 0;  
  80.                 }  
  81.                 final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;  
  82.                 for (int i=0; i<typeCount; i++) {  
  83.                     if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {  
  84.                         // don't do anything, but also don't pass it to the app  
  85.                         return -1;  
  86.                     }  
  87.                 }  
  88.             }  
  89.   
  90.             if (down) {  
  91.                 if (repeatCount == 0) {  
  92.                     mHomePressed = true;  
  93.                 } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {  
  94.                     if (!keyguardOn) {  
  95.                         handleLongPressOnHome();  
  96.                     }  
  97.                 }  
  98.             }  
  99.             return -1;  
  100.         } else if (keyCode == KeyEvent.KEYCODE_MENU) {  
  101.            ........</span>  
[java] view plain copy print ?
  1. <span style="font-size: 16px;"></span>  
  2.   
  3.   <span style="font-size: 24px;"> </span><span style="font-size: 18px;">Step2: <span style="color: rgb(153, 0, 0);"><strong>插曲</strong></span>《<span style="color: rgb(0, 102, 0);"><strong>网上有例子说怎么在自己的应用中屏蔽Home键</strong></span>》--->原理:是在你的应用的Activity中加入了锁屏的type,因为系统对锁屏界面,点击Home键失效!网摘代码如下:</span>  
[java] view plain copy print ?
  1. public class DMActivity extends Activity {    
  2.         
  3.     private boolean flag = true;//true位屏蔽,false位不屏蔽     
  4.         
  5.     @Override    
  6.     public void onCreate(Bundle savedInstanceState) {    
  7.         super.onCreate(savedInstanceState);    
  8.         setContentView(R.layout.main);    
  9.     }    
  10.     @Override    
  11.     public void onAttachedToWindow() {    
  12.         if(flag) {    
  13.             this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);    
  14.         }    
  15.         super.onAttachedToWindow();    
  16.     }    
  17.     
  18.     @Override    
  19.     public boolean onKeyDown(int keyCode, KeyEvent event) {    
  20.         if(keyCode == KeyEvent.KEYCODE_HOME){    
  21.             return true;    
  22.         }    
  23.         return super.onKeyDown(keyCode, event);    
  24.     }    
  25. }    

      

       Step3: 真正的原因如下,对锁屏模式的处理:

[java] view plain copy print ?
  1. <span style="font-size: 16px;">            // If a system window has focus, then it doesn't make sense  
  2.             // right now to interact with applications.  
  3.             WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;  
  4.             if (attrs != null) {  
  5.                 final int type = attrs.type;  
  6.                 if (type == WindowManager.LayoutParams.TYPE_KEYGUARD  
  7.                         || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {  
  8.                     // the "app" is keyguard, so give it the key  
  9.                     return 0;  
  10.                 }  
  11.                 final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;  
  12.                 for (int i=0; i<typeCount; i++) {  
  13.                     if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {  
  14.                         // don't do anything, but also don't pass it to the app  
  15.                         return -1;  
  16.                     }  
  17.                 }  
  18.             }</span>  

 

      Step4: 我们来看点击home键,为什么进入到launcher的待机界面:

[java] view plain copy print ?
  1. <span style="font-size: 16px;">// If we have released the home key, and didn't do anything else  
  2. // while it was pressed, then it is time to go home!  
  3. if (mHomePressed && !down) {  
  4.     mHomePressed = false;  
  5.     if (!canceled) {  
  6.         // If an incoming call is ringing, HOME is totally disabled.  
  7.         // (The user is already on the InCallScreen at this point,  
  8.         // and his ONLY options are to answer or reject the call.)  
  9.         boolean incomingRinging = false;  
  10.         try {  
  11.             ITelephony telephonyService = getTelephonyService();  
  12.             if (telephonyService != null) {  
  13.                 incomingRinging = telephonyService.isRinging();  
  14.             }  
  15.         } catch (RemoteException ex) {  
  16.             Log.w(TAG, "RemoteException from getPhoneInterface()", ex);  
  17.         }  
  18.   
  19.         if (incomingRinging) {  
  20.             Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");  
  21.         } else {  
  22.             launchHomeFromHotKey();  
  23.         }  
  24.     } else {  
  25.         Log.i(TAG, "Ignoring HOME; event canceled.");  
  26.     }  
  27.     return -1;  
  28. }</span>  

   

           Step5: 系统会判断,当前点击Home键并且没有电话打入的情况,才对Home键进行处理---->launchHomeFromHotKey();进入到----->launchHomeFromHotKey()方法中:

[java] view plain copy print ?
  1. /** 
  2.  * A home key -> launch home action was detected.  Take the appropriate action 
  3.  * given the situation with the keyguard. 
  4.  */  
  5. void launchHomeFromHotKey() {  
  6.     if (mKeyguardMediator.isShowingAndNotHidden()) {  
  7.         // don't launch home if keyguard showing  
  8.     } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {  
  9.         // when in keyguard restricted mode, must first verify unlock  
  10.         // before launching home  
  11.         mKeyguardMediator.verifyUnlock(new OnKeyguardExitResult() {  
  12.             public void onKeyguardExitResult(boolean success) {  
  13.                 if (success) {  
  14.                     try {  
  15.                         ActivityManagerNative.getDefault().stopAppSwitches();  
  16.                     } catch (RemoteException e) {  
  17.                     }  
  18.                     sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);  
  19.                     startDockOrHome();  
  20.                 }  
  21.             }  
  22.         });  
  23.     } else {  
  24.         // no keyguard stuff to worry about, just launch home!  
  25.         try {  
  26.             ActivityManagerNative.getDefault().stopAppSwitches();  
  27.         } catch (RemoteException e) {  
  28.         }  
  29.         sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);  
  30.         startDockOrHome();  
  31.     }  
  32. }  

    

         Step6: 也是对锁屏模式有个判断,如果不在锁屏模式,就launch Home---->startDockOrHome(),进入——>

[java] view plain copy print ?
  1. <span style="font-size: 16px;">   void startDockOrHome() {  
  2.         Intent dock = createHomeDockIntent();  
  3.         if (dock != null) {  
  4.             try {  
  5.                 mContext.startActivity(dock);  
  6.                 return;  
  7.             } catch (ActivityNotFoundException e) {  
  8.             }  
  9.         }  
  10.         mContext.startActivity(mHomeIntent);  
  11.     }</span>  

     

          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_HOME

       如果是以上模式,车载模式或者桌面模式,就返回dock不为空,否则为空。启动这个mHomeIntent。----->mHomeIntent定义如下

[java] view plain copy print ?
  1. <span style="font-size: 16px;">    mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);  
  2.  mHomeIntent.addCategory(Intent.CATEGORY_HOME);  
  3.  mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK  
  4.          | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);</span>  

       Step 8: 这个mHomeIntent就是启动activity中配置Category属性的值为CATEGORY_HOME,启动的时候新启动一个任务,不是在当前的这个任务中启动launcherHome,而是新建一个task。

       FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。

到这为止,Home键的流程已经分析完了。

 

        二 、下面看看长按耳机键接受的广播的处理方式:
    

       (1)这个长按耳机键捕获是在PhoneWindow.java类的onKeyDown()中,然后发送有序的广播---->如下:

[java] view plain copy print ?
  1. <span xmlns="http://www.w3.org/1999/xhtml" style=""><span xmlns="http://www.w3.org/1999/xhtml" style=""><span xmlns="http://www.w3.org/1999/xhtml" style="">            case KeyEvent.KEYCODE_HEADSETHOOK:                       
  2.             case KeyEvent.KEYCODE_MEDIA_STOP:  
  3.             case KeyEvent.KEYCODE_MEDIA_NEXT:  
  4.             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:  
  5.             case KeyEvent.KEYCODE_MEDIA_REWIND:  
  6.             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {  
  7.                 Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);  
  8.                 intent.putExtra(Intent.EXTRA_KEY_EVENT, event);  
  9.                 getContext().sendOrderedBroadcast(intent, null);  
  10.                 return true;  
  11.             }</span></span></span>  

      接受这个长按耳机键的广播是在Music的app中的----->

   public class MediaButtonIntentReceiver extends BroadcastReceiver{  ... ... }

           , 需要在Manifest.xml中注册这个广播<intent-filter>

               <action android:name="android.intent.action.MEDIA_BUTTON" />

               </intent-filter>。---->注册MediaButtonReceiver这个广播,

              这个类中onReceive()方法定义的:代码如下--->

[java] view plain copy print ?
  1. <span style="font-size: 16px;">    @Override  
  2.     public void onReceive(Context context, Intent intent) {  
  3.         String intentAction = intent.getAction();  
  4.         if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {  
  5.             Intent i = new Intent(MediaPlaybackService.SERVICECMD);  
  6.             i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDPAUSE);  
  7.             context.sendBroadcast(i);  
  8.         } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {  
  9.             KeyEvent event = (KeyEvent)  
  10.                     intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);  
  11.               
  12.             if (event == null) {  
  13.                 return;  
  14.             }  
  15.   
  16.             int keycode = event.getKeyCode();  
  17.             int action = event.getAction();  
  18.             long eventtime = event.getEventTime();  
  19.   
  20.             // single quick press: pause/resume.   
  21.             // double press: next track  
  22.             // long press: start auto-shuffle mode.  
  23.               
  24.             String command = null;  
  25.             switch (keycode) {  
  26.                 case KeyEvent.KEYCODE_MEDIA_STOP:  
  27.                     command = MediaPlaybackService.CMDSTOP;  
  28.                     break;  
  29.                 case KeyEvent.KEYCODE_HEADSETHOOK:  
  30.                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:  
  31.                     command = MediaPlaybackService.CMDTOGGLEPAUSE;  
  32.                     break;  
  33.                 case KeyEvent.KEYCODE_MEDIA_NEXT:  
  34.                     command = MediaPlaybackService.CMDNEXT;  
  35.                     break;  
  36.                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:  
  37.                     command = MediaPlaybackService.CMDPREVIOUS;  
  38.                     break;  
  39.                 case KeyEvent.KEYCODE_MEDIA_PAUSE:  
  40.                     command = MediaPlaybackService.CMDPAUSE;  
  41.                     break;  
  42.                 case KeyEvent.KEYCODE_MEDIA_PLAY:  
  43.                     command = MediaPlaybackService.CMDPLAY;  
  44.                     break;  
  45.             }  
  46.   
  47.             if (command != null) {  
  48.                 if (action == KeyEvent.ACTION_DOWN) {  
  49.                     if (mDown) {  
  50.                         if ((MediaPlaybackService.CMDTOGGLEPAUSE.equals(command) ||  
  51.                                 MediaPlaybackService.CMDPLAY.equals(command))  
  52.                                 && mLastClickTime != 0   
  53.                                 && eventtime - mLastClickTime > LONG_PRESS_DELAY) {  
  54.                             mHandler.sendMessage(  
  55.                                     mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context));  
  56.                         }  
  57.                     } else if (event.getRepeatCount() == 0) {  
  58.                         // only consider the first event in a sequence, not the repeat events,  
  59.                         // so that we don't trigger in cases where the first event went to  
  60.                         // a different app (e.g. when the user ends a phone call by  
  61.                         // long pressing the headset button)  
  62.   
  63.                         // The service may or may not be running, but we need to send it  
  64.                         // a command.  
  65.                         Intent i = new Intent(context, MediaPlaybackService.class);  
  66.                         i.setAction(MediaPlaybackService.SERVICECMD);  
  67.                         if (keycode == KeyEvent.KEYCODE_HEADSETHOOK &&  
  68.                                 eventtime - mLastClickTime < 300) {  
  69.                             i.putExtra(MediaPlaybackService.CMDNAME, MediaPlaybackService.CMDNEXT);  
  70.                             context.startService(i);  
  71.                             mLastClickTime = 0;  
  72.                         } else {  
  73.                             i.putExtra(MediaPlaybackService.CMDNAME, command);  
  74.                             context.startService(i);  
  75.                             mLastClickTime = eventtime;  
  76.                         }  
  77.   
  78.                         mLaunched = false;  
  79.                         mDown = true;  
  80.                     }  
  81.                 } else {  
  82.                     mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);  
  83.                     mDown = false;  
  84.                 }  
  85.                 if (isOrderedBroadcast()) {  
  86.                     abortBroadcast();  
  87.                 }  
  88.             }  
  89.         }  
  90.     }</span>  

    

         step1:   在方法if (action == KeyEvent.ACTION_DOWN) { ... ... }做的处理,event.getRepeatCount() == 0这个判断的意思是“是否长按耳机键?”,如果长按耳机键event.getRepeatCount() 的值就一直增加。

     

      step 2:短按耳机键:播放/暂停 --->音乐;短按启动MediaPlaybackService.java这个类,并且传入参数---->在这个服务类中有个接受广播的内部类:如下-->

[java] view plain copy print ?
  1. <span style="font-size: 16px;">private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {  
  2. @Override  
  3. public void onReceive(Context context, Intent intent) {  
  4.     String action = intent.getAction();  
  5.     String cmd = intent.getStringExtra("command");  
  6.     MusicUtils.debugLog("mIntentReceiver.onReceive " + action + " / " + cmd);  
  7.     if (CMDNEXT.equals(cmd) || NEXT_ACTION.equals(action)) {  
  8.         next(true);  
  9.     } else if (CMDPREVIOUS.equals(cmd) || PREVIOUS_ACTION.equals(action)) {  
  10.         prev();  
  11.     } else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {  
  12.         if (isPlaying()) {  
  13.             pause();  
  14.             mInternalPause = false;  
  15.         } else {  
  16.             play();  
  17.         }  
  18.     } else if (CMDPAUSE.equals(cmd) || PAUSE_ACTION.equals(action)) {  
  19.         pause();  
  20.         mInternalPause = false;  
  21.     } else if (CMDPLAY.equals(cmd)) {  
  22.         play();  
  23.     } else if (CMDSTOP.equals(cmd)) {  
  24.         pause();  
  25.         mInternalPause = false;  
  26.         seek(0);  
  27.     } else if (MediaAppWidgetProvider.CMDAPPWIDGETUPDATE.equals(cmd)) {  
  28.         // Someone asked us to refresh a set of specific widgets, probably  
  29.         // because they were just added.  
  30.         int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);  
  31.         mAppWidgetProvider.performUpdate(MediaPlaybackService.this, appWidgetIds);  
  32.     }  
  33. }  
  34. span>  

         通过:如下方法来控制点击播放音乐,再次点击暂停,如此循环。

 else if (CMDTOGGLEPAUSE.equals(cmd) || TOGGLEPAUSE_ACTION.equals(action)) {

         if (isPlaying()) {

                   pause();

                   mInternalPause = false;

          } else {

                    play();

          }

  }

内部类的广播是在启动MediaPlaybackService.java中注册的,解除注册在onDestroy()的方法中。

[java] view plain copy print ?
  1. <span style="font-size: 18px;">        </span><span style="font-size: 16px;">IntentFilter commandFilter = new IntentFilter();  
  2.         commandFilter.addAction(SERVICECMD);  
  3.         commandFilter.addAction(TOGGLEPAUSE_ACTION);  
  4.         commandFilter.addAction(PAUSE_ACTION);  
  5.         commandFilter.addAction(NEXT_ACTION);  
  6.         commandFilter.addAction(PREVIOUS_ACTION);  
  7.         commandFilter.addAction(PLAYSTATUS_REQUEST);  
  8.         registerReceiver(mIntentReceiver, commandFilter);</span>  

   

      Step 3:  长按耳机键--->发消息给mHandler,

[java] view plain copy print ?
  1. <strong><span style="font-size: 18px;">       mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);</span></strong>  

     在MediaButtonIntentReceiver.java中有个内部类Handler()如下——>

[java] view plain copy print ?
  1. <span style="font-size: 16px; color: rgb(0, 0, 0);">        private static Handler mHandler = new Handler() {  
  2.         @Override  
  3.         public void handleMessage(Message msg) {  
  4.             switch (msg.what) {  
  5.                 case MSG_LONGPRESS_TIMEOUT:  
  6.                     if (!mLaunched) {  
  7.                         Context context = (Context)msg.obj;  
  8.                         Intent i = new Intent();  
  9.                         i.putExtra("autoshuffle""true");  
  10.                         i.setClass(context, MusicBrowserActivity.class);  
  11.                         i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);  
  12.                         context.startActivity(i);  
  13.                         mLaunched = true;  
  14.                     }  
  15.                     break;  
  16.             }  
  17.         }  
  18.     };</span>  

长按耳机后就启动MusicBrowserActivity.java这个音乐播放类。并且传入参数“autoshuffle==true”,这个启动和launcher的启动相似,也是启动一个新的任务task,但是这个后面的标志有不同的地方Intent.FLAG_ACTIVITY_CLEAR_TOP。

到这为止就是实现了在任何界面长按耳机键都能进入到music的主界面。

 

      总结如下:

      其实启动的时候,要注意当前activity的launcherMode是什么,如果是SingleTask,就要小心一下。比如说:想要长按耳机键,进入到launcher的Mainmenu界面,这时候如果单纯的用以上方法套,返回键点击的时候不会回到上个activity中,会有问题。因为launcher是一直启动的运行于每个task之中的,你再次启动Launcher的时候,无论是否设置属性“i.setFlags(Intent.FLAG_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"                                                                                                                           Android的全局键(home键/长按耳机键)详解【android源码解析八】_第3张图片                                Android的全局键(home键/长按耳机键)详解【android源码解析八】_第4张图片

    

         Step 2:先启动launchMode="singleTask"的activity---->点击调用第二个App2的launchMode="standard"---->

点击按钮调用第一个App1的launchMode="singleTask"---->点击返回键。看log分析:

 

      (1)点击启动launchMode="singleTask"的activity的log如下:

        Android的全局键(home键/长按耳机键)详解【android源码解析八】_第5张图片

      

  (2)点击调用第二个App2的launchMode="standard"的activity的log如下图:     Android的全局键(home键/长按耳机键)详解【android源码解析八】_第6张图片
    

        

    (3)点击调用第一个App1launchMode="singleTask"的activity的log如下: Android的全局键(home键/长按耳机键)详解【android源码解析八】_第7张图片
       分析如下:看到这时候App2Activity--22已经执行了onStop()和onDestroy()方法了。验证了我以上的说法。

   

    (4)点击返回键---->直接回到了launcher界面。log如下:

    Android的全局键(home键/长按耳机键)详解【android源码解析八】_第8张图片

  

       备注:要想解决以上问题也是可以的。就是在以上第(2)步:点击调用第二个App2的launchMode="standard"的activity的时候设置flag。Intent.FLAG_ACTIVITY_NEW_TASK。就可以解决以上问题了,每次启动一个新的任务,这样就能返回到App2Activity了

    

       (1)点击启动launchMode="singleTask"的activity的log如下:

    Android的全局键(home键/长按耳机键)详解【android源码解析八】_第9张图片

    

    (2)点击调用第二个App2的launchMode="standard"的activity的log如下图:   

Android的全局键(home键/长按耳机键)详解【android源码解析八】_第10张图片

    

  

     (3)点击调用第一个App1launchMode="singleTask"的activity的log如下:

Android的全局键(home键/长按耳机键)详解【android源码解析八】_第11张图片

      看到如上图:App2Activity--22--->只是onStop()了,没有onDestroy掉。

  

     (4)点击返回键---->直接回到了launcher界面---->log如下:

 Android的全局键(home键/长按耳机键)详解【android源码解析八】_第12张图片

   

   (5)通过log,我们可以看出返回到App2Activity了,我们再次点击返回键,--->返回到Launcher界面的log如下:

   Android的全局键(home键/长按耳机键)详解【android源码解析八】_第13张图片

     通过以上验证说明我的结论是正确的。launcherMode一直是android的核心技术,通过这次我会更加注意到activity的LauncherMode的。



你可能感兴趣的:(Android的全局键(home键/长按耳机键)详解【android源码解析八】)