看很多网友对music源码比较感兴趣,自己琢磨了一下,正好有时间,写了个小Demo,结合Demo我把自己学到东西跟大家分享下。
首先讲下Demo实现的功能,很简单,就是实现和music同步播放,并把歌名,演唱者,专辑封面显示给用户,之前学到widget,此次就用widget实现吧,正好也是个知识的巩固吧。没有用到activity。
Demo名MyMusic
1、在laytou下创建widget布局文件,mymusic_widget_laytou
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:background="@color/light_blue" >
<ImageView
android:id="@+id/img_album"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="centerCrop" />
<LinearLayout
android:id="@+id/widget"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/album_cover"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="4"
android:gravity="center_horizontal"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_songname"
android:layout_width="fill_parent"
android:layout_height="40dip"
android:layout_above="@+id/tv_artist"
android:gravity="center"
android:text="@string/songname"
android:textColor="@color/white" />
<TextView
android:id="@+id/tv_artist"
android:layout_width="fill_parent"
android:layout_height="40dip"
android:layout_alignParentBottom="true"
android:gravity="center"
android:text="@string/artist"
android:textColor="@color/white" />
</RelativeLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:gravity="center_vertical" >
<ProgressBar
android:id="@+id/probar_song"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="20dip" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_gravity="center"
android:layout_weight="1"
android:orientation="horizontal" >
<ImageView
android:id="@+id/img_rewind"
android:layout_width="40dip"
android:layout_height="fill_parent"
android:clickable="true"
android:focusable="true"
android:src="@drawable/rewind_bg" />
<ImageView
android:id="@+id/img_play"
android:layout_width="40dip"
android:layout_height="fill_parent"
android:layout_marginLeft="40dip"
android:clickable="true"
android:focusable="true"
android:src="@drawable/play_bg" />
<ImageView
android:id="@+id/img_next"
android:layout_width="40dip"
android:layout_height="fill_parent"
android:layout_marginLeft="40dip"
android:clickable="true"
android:focusable="true"
android:src="@drawable/forward_bg" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
2、在res下新建xml文件夹,在该文件夹下新建mymusic_widget.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="160dip"
android:minHeight="80dip"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="0"
android:initialLayout="@layout/mymusic_widget_layout"
android:previewImage="@drawable/mymusicpre"
>
</appwidget-provider>
3、新建java文件,MymusicProvider,该类继承自AppWidgetProvider
package com.example.mymusic;
import java.io.FileDescriptor;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.widget.RemoteViews;
public class MyMusicProvider extends AppWidgetProvider {
public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
public static final String PAUSE_ACTION = "com.android.music.musicservicecommand.pause";
public static final String PREVIOUS_ACTION = "com.android.music.musicservicecommand.previous";
public static final String NEXT_ACTION = "com.android.music.musicservicecommand.next";
public static final String PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
public static final String META_CHANGED = "com.android.music.metachanged";
public static final String MUSIC_SERVICE = "com.example.mymusic.MUSIC_SERVICE";
// public static RemoteViews musicRemoteViews;
// public static Context mContext;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
RemoteViews musicRemoteViews = new RemoteViews(
context.getPackageName(), R.layout.mymusic_widget_layout);
initView(context, musicRemoteViews);
musicRemoteViews.setProgressBar(R.id.probar_song, 100, 0, false);
appWidgetManager.updateAppWidget(appWidgetIds, musicRemoteViews);
Intent musicIntent = new Intent(MUSIC_SERVICE);
context.startService(musicIntent);
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
Log.v("receive", intent.getAction());
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(context);
ComponentName provider = new ComponentName(context,
MyMusicProvider.class);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(provider);
RemoteViews musicRemoteViews = new RemoteViews(
context.getPackageName(), R.layout.mymusic_widget_layout);
String actionString = intent.getAction();
if (actionString.equals(PLAYSTATE_CHANGED)
|| actionString.equals(META_CHANGED)) {
linkButtons(context, musicRemoteViews);
getSongInfo(intent, musicRemoteViews);
changePlayState(context, intent, musicRemoteViews);
musicRemoteViews.setImageViewBitmap(R.id.img_album,
getArtwork(context, intent));
musicRemoteViews.setProgressBar(R.id.probar_song, 100,
MyService.mProgress, false);
}
appWidgetManager.updateAppWidget(appWidgetIds, musicRemoteViews);
}
/**更新进度条,在Service里调用*/
public static void updateProgress(Context context, int progress) {
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(context);
ComponentName provider = new ComponentName(context,
MyMusicProvider.class);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(provider);
RemoteViews musicRemoteViews = new RemoteViews(
context.getPackageName(), R.layout.mymusic_widget_layout);
musicRemoteViews.setProgressBar(R.id.probar_song, 100, progress,
false);
appWidgetManager.updateAppWidget(appWidgetIds, musicRemoteViews);
}
/**获取歌曲名和演唱者*/
private static void getSongInfo(Intent intent, RemoteViews remoteViews) {
String songString = intent.getStringExtra("track");
String artiString = intent.getStringExtra("artist");
remoteViews.setTextViewText(R.id.tv_songname, songString);
remoteViews.setTextViewText(R.id.tv_artist, artiString);
}
/**监听播放/暂停按钮状态切换背景图片*/
private void changePlayState(Context context, Intent intent,
RemoteViews remoteViews) {
boolean isPlaying = intent.getBooleanExtra("playing", false);
Log.e("111", intent.getAction() + " " + isPlaying);
if (isPlaying) {
remoteViews
.setImageViewResource(R.id.img_play, R.drawable.pause_bg);
} else {
remoteViews.setImageViewResource(R.id.img_play, R.drawable.play_bg);
}
}
/**获取专辑封面,没有的话显示默认*/
private Bitmap getArtwork(Context context, Intent intent) {
String str = "content://media/external/audio/media/"
+ intent.getLongExtra("id", -1L) + "/albumart";
Uri uri = Uri.parse(str);
ParcelFileDescriptor pfd = null;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, "r");
} catch (Exception e) {
e.printStackTrace();
}
if (pfd != null) {
Bitmap bm = null;
FileDescriptor fd = pfd.getFileDescriptor();
bm = BitmapFactory.decodeFileDescriptor(fd);
return bm;
}
return null;
}
/**为按钮设置背景图片,并且绑定时间*/
private void linkButtons(Context context, RemoteViews remoteViews) {
Intent intent;
PendingIntent pendingIntent;
remoteViews.setImageViewResource(R.id.img_rewind, R.drawable.rewind_bg);
remoteViews.setImageViewResource(R.id.img_play, R.drawable.play_bg);
remoteViews.setImageViewResource(R.id.img_next, R.drawable.forward_bg);
intent = new Intent("com.android.music.PLAYBACK_VIEWER");
pendingIntent = PendingIntent.getActivity(context, 0,
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
remoteViews.setOnClickPendingIntent(R.id.album_cover, pendingIntent);
intent = new Intent(TOGGLEPAUSE_ACTION);
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.img_play, pendingIntent);
intent = new Intent(NEXT_ACTION);
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.img_next, pendingIntent);
intent = new Intent(PREVIOUS_ACTION);
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.img_rewind, pendingIntent);
}
/**初次运行初始化界面*/
private void initView(Context context, RemoteViews remoteViews) {
Intent intent;
PendingIntent pendingIntent;
intent = new Intent("com.android.music.PLAYBACK_VIEWER");
pendingIntent = PendingIntent.getActivity(context, 0,
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
remoteViews.setOnClickPendingIntent(R.id.album_cover, pendingIntent);
remoteViews.setImageViewResource(R.id.img_rewind,
R.drawable.button_rewind_gray);
remoteViews.setImageViewResource(R.id.img_play,
R.drawable.button_play_gray);
remoteViews.setImageViewResource(R.id.img_next,
R.drawable.button_forward_gray);
}
}
4、自定义一个Service,在Service里面更新播放进度条
package com.example.mymusic;
import com.android.music.IMediaPlaybackService;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
public class MyService extends Service {
private static IMediaPlaybackService mService;
public static long mPosition;
public static long mDuration;
public static int mProgress;
public static Handler mHandler;
public static final int UPDATE_PROGRESS = 1;
public boolean miStart = false;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Intent i = new Intent();
i.setClassName("com.android.music",
"com.android.music.MediaPlaybackService");
ServiceConnection conn = new MediaPlayerServiceConnection();
boolean isBinded = this.bindService(i, conn, 0);
Log.v("isbinded", "isBinded : " + isBinded);
if(!miStart && mHandler!= null){
mHandler.sendEmptyMessage(UPDATE_PROGRESS);
miStart = true;
}
return 1;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
try {
mPosition = mService.position();
mDuration = mService.duration();
mProgress = (int) (100 * mPosition/mDuration);
MyMusicProvider.updateProgress(MyService.this, mProgress);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 1000);
}
};
}
class MediaPlayerServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder boundService) {
Log.i("MediaPlayerServiceConnection",
"Connected! Name: " + name.getClassName());
// This is the important line
mService = IMediaPlaybackService.Stub.asInterface(boundService);
// If all went well, now we can use the interface
}
public void onServiceDisconnected(ComponentName name) {
mService = null;
Log.i("MediaPlayerServiceConnection", "Disconnected!");
}
}
}
5、这里Service用到aidl(android内部进程通信接口的描述语言)系统自带的IMediaPlaybackService.aidl,里面涵盖了很多种方法可以留给用户调用,包括上面的Provider里面,其实都可已用该aidl来获取到专辑,歌曲,作者信息等等,而不用自己另外写功能去实现;本实例中只用到aidl里面的position()和duration方法,分别时当前进度和总进度;
在src下新建包com.android.music,将music源码里的IMediaPlaybackService.aidl拷贝到该包名下,或者自己新建 名字一样,代码如下:
/* //device/samples/SampleCode/src/com/android/samples/app/RemoteServiceInterface.java
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package com.android.music;
import android.graphics.Bitmap;
interface IMediaPlaybackService
{
void openFile(String path);
void open(in long [] list, int position);
int getQueuePosition();
boolean isPlaying();
void stop();
void pause();
void play();
void prev();
void next();
long duration();
long position();
long seek(long pos);
String getTrackName();
String getAlbumName();
long getAlbumId();
String getArtistName();
long getArtistId();
void enqueue(in long [] list, int action);
long [] getQueue();
void moveQueueItem(int from, int to);
void setQueuePosition(int index);
String getPath();
long getAudioId();
void setShuffleMode(int shufflemode);
int getShuffleMode();
int removeTracks(int first, int last);
int removeTrack(long id);
void setRepeatMode(int repeatmode);
int getRepeatMode();
int getMediaMountedCount();
int getAudioSessionId();
}
从代码里不难看出IMediaPlaybackService.aidl相当于一个接口,里面封装好了各种函数可直接调用。
6、对MyMusicProvider当中出现的Broadcast都需要在AndroidManifest.xml当中注册生效:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mymusic"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/music_ico"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<receiver android:name=".MyMusicProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE_VIEW" />
<action android:name="com.android.music.playstatechanged" />
<action android:name="com.android.music.metachanged"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/mymusic_widget" />
</receiver>
<!--
<activity
android:name="com.example.mymusic.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-->
</application>
</manifest>
至此,整个Demo完毕。