美文网首页Android开发入门进阶Android开发程序员
那些不太热门的Android知识——RemoteControll

那些不太热门的Android知识——RemoteControll

作者: Yankee1967 | 来源:发表于2016-12-21 21:07 被阅读0次

    前言

    要不是团队中有这么个需求,我估计永远也不会去接触这么个东西。首先要从需求说起,需求是通过自己的控件来控制第三方的播放器,市面上的音乐播放器有多种,且其内部的实现方式多种多样,长久以来没有统一的标准,但大部分都是通过开启一个服务在后台,接着通知栏上会有一个常驻的Notification来方便用户的控制。
    反编译大多数音乐APP,你会发现它们都有注册耳机插拔的广播,还有就是你可以通过控制耳机按键来控制音乐播放,而耳机按键事件是可以模拟的,这就为控制第三方音乐播放器提供可能。
    接着就是关于接收音乐信息的问题,这里指的是接收专辑、歌手专辑封面等等,前面说了,通知栏会有常驻的Notification来显示当前一些歌曲的信息,那如何获取呢,一种方式是通过反射,但是普遍性比较差。
    在Android API 19中,谷歌为我们提供了RemoteController,现在这个API已经被MediaSession代替,然而网上对MediaSession的资料几乎为零,所以本篇文章只讲讲RemoteController的使用,如果有关于MediaSession的资料demo或者有关对第三方音乐播放器控制好的方法,欢迎私信留言,本篇文章有欠妥的地方,欢迎指出,笔者加以改正,共同学习。
    一些储备知识:
    1、NotificationListenerService
    相信做过和Notification有关的同学对这个东西多少都有些了解,这是谷歌官方提供的用于监听和处理消息通知的API。使用方式也很简单,继承它,重写其中的几个方法就好,系统会在后台开启一个服务专门用于监听系统消息,当然这需要手动去开启权限。
    2、按键事件:
    关于按键事件来控制Media,看下面两个方法即可

    public boolean sendMusicKeyEvent(int keyCode) {
    
     if (remoteController != null) {
    
     KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    
     boolean down = remoteController.sendMediaKeyEvent(keyEvent);
    
     keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
    
     boolean up = remoteController.sendMediaKeyEvent(keyEvent);
    
     return down && up;
    
     } else {
    
     long eventTime = SystemClock.uptimeMillis();
    
     KeyEvent key = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0);
    
     dispatchMediaKeyToAudioService(key);
    
     dispatchMediaKeyToAudioService(KeyEvent.changeAction(key, KeyEvent.ACTION_UP));
    
     }
    
     return false;
    
     }
    
     private void dispatchMediaKeyToAudioService(KeyEvent event) {
    
     AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
    
     if (audioManager != null) {
    
     try {
    
     audioManager.dispatchMediaKeyEvent(event);
    
     } catch (Exception e) {
    
     e.printStackTrace();
    
     }
    
     }
    
     }
    

    那么接下来我们来说说RemotController来控制及获取第三方音乐信息:

    part1

    首先我们需要继承NotificationListenerService,这里有两个相对比较重要的方法

    @Override
    
     public void onNotificationPosted(StatusBarNotification sbn) {
    
     Log.e(TAG, "onNotificationPosted...");
    
     if (sbn.getPackageName().contains("music"))
    
     {
    
     Log.e(TAG, "音乐软件正在播放...");
    
     Log.e(TAG, sbn.getPackageName());
    
     }
    
     }
    
     @Override
    
     public void onNotificationRemoved(StatusBarNotification sbn) {
    
     Log.e(TAG, "onNotificationRemoved...");
    
     }
    

    这里我们可以通过关键字看到当前正在后台播放的是哪一款播放器。

    part2

    接着让这个继承于NotificationListenerService的服务实现RemoteController.OnClientUpdateListener接口,以下是接口中的方法:

    /** 
    * Interface definition for the callbacks to be invoked whenever media events, metadata * and playback status are available. */
    public interface OnClientUpdateListener { 
    /** 
    * Called whenever all information, previously received through the other 
    * methods of the listener, is no longer valid and is about to be refreshed. 
    * This is typically called whenever a new {@link RemoteControlClient} has been selected 
    * by the system to have its media information published. 
    * @param clearing true if there is no selected RemoteControlClient and no information 
    * is available. 
    */public void onClientChange(boolean clearing); 
    /** 
    * Called whenever the playback state has changed. 
    * It is called when no information is known about the playback progress in the media and 
    * the playback speed.
     * @param state one of the playback states authorized 
    * in {@link RemoteControlClient#setPlaybackState(int)}. 
    */public void onClientPlaybackStateUpdate(int state); 
    /** 
    * Called whenever the playback state has changed, and playback position 
    * and speed are known. 
    * @param state one of the playback states authorized 
    * in {@link RemoteControlClient#setPlaybackState(int)}. 
    * @param stateChangeTimeMs the system time at which the state change was reported, 
    * expressed in ms. Based on {@link android.os.SystemClock#elapsedRealtime()}. 
    * @param currentPosMs a positive value for the current media playback position expressed 
    * in ms, a negative value if the position is temporarily unknown. 
    * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback, 
    * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is 
    * playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}). */
    public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed); /** 
    * Called whenever the transport control flags have changed. 
    * @param transportControlFlags one of the flags authorized 
    * in {@link RemoteControlClient#setTransportControlFlags(int)}. */
    public void onClientTransportControlUpdate(int transportControlFlags); 
    /** 
    * Called whenever new metadata is available. 
    * See the {@link MediaMetadataEditor#putLong(int, long)}, 
    * {@link MediaMetadataEditor#putString(int, String)}, 
    * {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and 
    * {@link MediaMetadataEditor#putObject(int, Object)} methods for the various keys that 
    * can be queried. 
    * @param metadataEditor the container of the new metadata. */
    public void onClientMetadataUpdate(MetadataEditor metadataEditor);};
    

    在最后一个方法中,我们需要的专辑封面、歌手、歌曲名等等资料都能在metadataEditor参数里拿到,这个放在后面说。

    part3

    接下来就是获取合法的RemoteController对象以及其他一些设置,比如设置获取封面时封面的大小等,在onCreate()中执行再合适不过了,这段获取及配置的代码为:

    public void registerRemoteController() {
    
     remoteController = new RemoteController(this, this);
    
     boolean registered;
    
     try {
    
     registered = ((AudioManager) getSystemService(AUDIO_SERVICE))
    
     .registerRemoteController(remoteController);
    
     } catch (NullPointerException e) {
    
     registered = false;
    
     }
    
     if (registered) {
    
     try {
    
     remoteController.setArtworkConfiguration(
    
     100,
    
     100);
    
     remoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK);
    
     } catch (IllegalArgumentException e) {
    
     e.printStackTrace();
    
     }
    
     }
    
     }
    

    还有就是通过回调把一些具体实现放在外部去,前面说到,我们的service是继承自系统的NotificationListenerService ,所以终究来说,它还是一个服务,你可以在正在运行的后台服务中看到,这是作为单独一个服务进行的。
    所以就涉及到了与service通信的问题,我们使用Binder,服务的完整代码如下:

    @TargetApi(Build.VERSION_CODES.KITKAT)
    
    public class RemoteControlService extends NotificationListenerService implements RemoteController.OnClientUpdateListener {
    
     String TAG = "Yankee";
    
     public RemoteController remoteController;
    
     private RemoteController.OnClientUpdateListener mExternalClientUpdateListener;
    
     private IBinder mBinder = new RCBinder();
    
     
    
     @Override
    
     public void onCreate() {
    
     registerRemoteController();
    
     }
    
     
    
     @Override
    
     public IBinder onBind(Intent intent) {
    
     if (intent.getAction().equals("com.yankee.musicview.BIND_RC_CONTROL_SERVICE")) {
    
     return mBinder;
    
     } else {
    
     return super.onBind(intent);
    
     }
    
     }
    
     
    
     @Override
    
     public void onNotificationPosted(StatusBarNotification sbn) {
    
     Log.e(TAG, "onNotificationPosted...");
    
     if (sbn.getPackageName().contains("music"))
    
     {
    
     Log.e(TAG, "音乐软件正在播放...");
    
     Log.e(TAG, sbn.getPackageName());
    
     }
    
     
    
     }
    
     
    
     @Override
    
     public void onNotificationRemoved(StatusBarNotification sbn) {
    
     Log.e(TAG, "onNotificationRemoved...");
    
     }
    
     
    
     public void registerRemoteController() {
    
     remoteController = new RemoteController(this, this);
    
     boolean registered;
    
     try {
    
     registered = ((AudioManager) getSystemService(AUDIO_SERVICE))
    
     .registerRemoteController(remoteController);
    
     } catch (NullPointerException e) {
    
     registered = false;
    
     }
    
     if (registered) {
    
     try {
    
     remoteController.setArtworkConfiguration(
    
     100,
    
     100);
    
     remoteController.setSynchronizationMode(RemoteController.POSITION_SYNCHRONIZATION_CHECK);
    
     } catch (IllegalArgumentException e) {
    
     e.printStackTrace();
    
     }
    
     }
    
     }
    
     
    
     public void setClientUpdateListener(RemoteController.OnClientUpdateListener listener) {
    
     mExternalClientUpdateListener = listener;
    
     }
    
     
    
     @Override
    
     public void onClientChange(boolean clearing) {
    
     if (mExternalClientUpdateListener != null) {
    
     mExternalClientUpdateListener.onClientChange(clearing);
    
     }
    
     }
    
     
    
     @Override
    
     public void onClientPlaybackStateUpdate(int state) {
    
     if (mExternalClientUpdateListener != null) {
    
     mExternalClientUpdateListener.onClientPlaybackStateUpdate(state);
    
     }
    
     }
    
     
    
     @Override
    
     public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
    
     if (mExternalClientUpdateListener != null) {
    
     mExternalClientUpdateListener.onClientPlaybackStateUpdate(state, stateChangeTimeMs, currentPosMs, speed);
    
     }
    
     }
    
     
    
     @Override
    
     public void onClientTransportControlUpdate(int transportControlFlags) {
    
     if (mExternalClientUpdateListener != null) {
    
     mExternalClientUpdateListener.onClientTransportControlUpdate(transportControlFlags);
    
     }
    
     }
    
     
    
     @Override
    
     public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
    
     if (mExternalClientUpdateListener != null) {
    
     mExternalClientUpdateListener.onClientMetadataUpdate(metadataEditor);
    
     }
    
     }
    
     
    
     public boolean sendMusicKeyEvent(int keyCode) {
    
     if (remoteController != null) {
    
     KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    
     boolean down = remoteController.sendMediaKeyEvent(keyEvent);
    
     keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
    
     boolean up = remoteController.sendMediaKeyEvent(keyEvent);
    
     return down && up;
    
     } else {
    
     long eventTime = SystemClock.uptimeMillis();
    
     KeyEvent key = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyCode, 0);
    
     dispatchMediaKeyToAudioService(key);
    
     dispatchMediaKeyToAudioService(KeyEvent.changeAction(key, KeyEvent.ACTION_UP));
    
     }
    
     return false;
    
     }
    
     
    
     private void dispatchMediaKeyToAudioService(KeyEvent event) {
    
     AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
    
     if (audioManager != null) {
    
     try {
    
     audioManager.dispatchMediaKeyEvent(event);
    
     } catch (Exception e) {
    
     e.printStackTrace();
    
     }
    
     }
    
     }
    
     
    
     public class RCBinder extends Binder {
    
     public RemoteControlService getService() {
    
     return RemoteControlService.this;
    
     }
    
     }
    
    }
    
    part4

    那么接下来的操作就是在我们自己的view中进行了,写一个音乐控制的view很简单,并且在onAttachedToWindow()的时候绑定这个服务,接下来附上view的代码;
    MusicView:

    /**
    
     * Created by Yankee on 2016/12/20.
    
     */
    
    @TargetApi(Build.VERSION_CODES.KITKAT)
    
    public class MusicView extends LinearLayout {
    
     private ImageView mCover;
    
     private ImageView mPre;
    
     private ImageView mPause;
    
     private ImageView mNext;
    
     private TextView mTitle;
    
     private TextView mContent;
    
     private Context mContext;
    
     private boolean isPlaying = true;
    
     private RemoteControlService mRCService;
    
     private static final String TAG = "Yankee";
    
     
    
     public MusicView(Context context) {
    
     this(context, null);
    
     }
    
     
    
     public MusicView(Context context, AttributeSet attrs) {
    
     super(context, attrs);
    
     mContext = context;
    
     LayoutInflater.from(context).inflate(R.layout.layout_music_view, this);
    
     initView();
    
     initListener();
    
     }
    
     
    
     private void initView() {
    
     mCover = (ImageView) findViewById(R.id.music_view_cover);
    
     mPre = (ImageView) findViewById(R.id.music_view_previous);
    
     mPause = (ImageView) findViewById(R.id.music_view_pause);
    
     mNext = (ImageView) findViewById(R.id.music_view_next);
    
     mTitle = (TextView) findViewById(R.id.music_view_title);
    
     mContent = (TextView) findViewById(R.id.music_view_content);
    
     }
    
     
    
     private void initListener() {
    
     mPre.setOnClickListener(new OnClickListener() {
    
     @Override
    
     public void onClick(View view) {
    
     mRCService.sendMusicKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
    
     isPlaying = true;
    
     mPause.setImageResource(android.R.drawable.ic_media_pause);
    
     }
    
     });
    
     mPause.setOnClickListener(new OnClickListener() {
    
     @Override
    
     public void onClick(View view) {
    
     mRCService.sendMusicKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
    
     if (isPlaying) {
    
     isPlaying = false;
    
     mPause.setImageResource(android.R.drawable.ic_media_play);
    
     } else {
    
     isPlaying = true;
    
     mPause.setImageResource(android.R.drawable.ic_media_pause);
    
     }
    
     }
    
     });
    
     mNext.setOnClickListener(new OnClickListener() {
    
     @Override
    
     public void onClick(View view) {
    
     mRCService.sendMusicKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
    
     isPlaying = true;
    
     mPause.setImageResource(android.R.drawable.ic_media_pause);
    
     }
    
     });
    
     }
    
     
    
     @Override
    
     protected void onAttachedToWindow() {
    
     super.onAttachedToWindow();
    
     Intent intent = new Intent("com.yankee.musicview.BIND_RC_CONTROL_SERVICE");
    
     intent.setPackage(mContext.getPackageName());
    
     mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    
     }
    
     
    
     @Override
    
     protected void onDetachedFromWindow() {
    
     super.onDetachedFromWindow();
    
     }
    
     
    
     public void setCoverImage(Bitmap bitmap) {
    
     
    
     mCover.setImageBitmap(bitmap);
    
     
    
     }
    
     
    
     public void setTitleString(String title) {
    
     mTitle.setText(title);
    
     }
    
     
    
     public void setContentString(String content) {
    
     mContent.setText(content);
    
     }
    
     
    
     private ServiceConnection mConnection = new ServiceConnection() {
    
     @Override
    
     public void onServiceConnected(ComponentName className, IBinder service) {
    
     RemoteControlService.RCBinder binder = (RemoteControlService.RCBinder) service;
    
     mRCService = binder.getService();
    
     mRCService.setClientUpdateListener(mExternalClientUpdateListener);
    
     }
    
     
    
     @Override
    
     public void onServiceDisconnected(ComponentName name) {
    
     }
    
     };
    
     RemoteController.OnClientUpdateListener mExternalClientUpdateListener = new RemoteController.OnClientUpdateListener() {
    
     @Override
    
     public void onClientChange(boolean clearing) {
    
     Log.e(TAG, "onClientChange()...");
    
     }
    
     
    
     @Override
    
     public void onClientPlaybackStateUpdate(int state) {
    
     Log.e(TAG, "onClientPlaybackStateUpdate()...");
    
     }
    
     
    
     @Override
    
     public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
    
     Log.e(TAG, "onClientPlaybackStateUpdate()...");
    
     }
    
     
    
     @Override
    
     public void onClientTransportControlUpdate(int transportControlFlags) {
    
     Log.e(TAG, "onClientTransportControlUpdate()...");
    
     }
    
     
    
     @Override
    
     public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
    
     String artist = metadataEditor.
    
     getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "null");
    
     String album = metadataEditor.
    
     getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, "null");
    
     String title = metadataEditor.
    
     getString(MediaMetadataRetriever.METADATA_KEY_TITLE, "null");
    
     Long duration = metadataEditor.
    
     getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, -1);
    
     Bitmap defaultCover = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_menu_compass);
    
     Bitmap bitmap = metadataEditor.
    
     getBitmap(RemoteController.MetadataEditor.BITMAP_KEY_ARTWORK, defaultCover);
    
     setCoverImage(bitmap);
    
     setContentString(artist);
    
     setTitleString(title);
    
     Log.e(TAG, "artist:" + artist
    
     + "album:" + album
    
     + "title:" + title
    
     + "duration:" + duration);
    
     }
    
     };
    
    }
    

    布局文件的代码我就不粘贴了

    一些注意事项

    1、不要忘了在配置文件里加上服务的代码,且这个服务需要加权限:

    <service
    
     android:name=".RemoteControlService"
    
     android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
    
     >
    
     <intent-filter>
    
     <action android:name="com.yankee.musicview.BIND_RC_CONTROL_SERVICE" />
    
     <action android:name="android.service.notification.NotificationListenerService" />
    
     </intent-filter>
    
     </service>
    

    2、首先要到安全里开启消息通知权限,首先要到安全里开启消息通知权限,首先要到安全里开启消息通知权限,重要的事情说三遍,否则死活都不会启用的,而且会报:
    java.lang.SecurityException: Missing permission to control media.

    3、代码中有三处用到
    RemoteControlService的name属性,三处务必统一,且小写字母部分务必和包名相同(笔者也不懂为何)

    相关文章

      网友评论

        本文标题:那些不太热门的Android知识——RemoteControll

        本文链接:https://www.haomeiwen.com/subject/etexvttx.html