桌面widget详解(四)——桌面音乐播放器(实战)

桌面widget详解(四)——桌面音乐播放器(实战),第1张

概述先看看本篇的最终效果:一、Service控制播放部分(MusicManageService.java)首先由于我们要与按钮相交互,所以在Service中的交互一般是通过BroadcastReceiver来实现的,所以在MusicManageService的OnCreate函数中(Service起来的时候调用OnCreate)应该包括下面几个步骤:注册Receiver,初始化...

先看看本篇的最终效果:

 

一、Service控制播放部分(MusicManageService.java)

 

首先由于我们要与按钮相交互,所以在Service中的交互一般是通过broadcastReceiver来实现的,所以在MusicManageService的OnCreate函数中(Service起来的时候调用OnCreate)应该包括下面几个步骤:注册Receiver,初始化歌曲播放列表,开始播放默认歌曲;


所以首先是注册Receiver:

IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(ACTION);registerReceiver(receiver,intentFilter);

对应的broadcastReceiver receiver主要是根据接收来的消息来上一首,下一首,暂停、播放歌曲:

public static String ACTION = "to_service";public static String KEY_USR_ACTION = "key_usr_action";public static final int ACTION_PRE = 0,ACTION_PLAY_PAUSE = 1,ACTION_NEXT = 2;private boolean mPlayState = false; private broadcastReceiver receiver = new broadcastReceiver() {	@OverrIDe	public voID onReceive(Context context,Intent intent) {		String action  = intent.getAction();		if (ACTION.equals(action)) {			int Widget_action = intent.getIntExtra(KEY_USR_ACTION,-1);						switch (Widget_action) {			case ACTION_PRE:				playPrev(context);				Log.d("harvic","action_prev");				break;			case ACTION_PLAY_PAUSE:				if (mPlayState) {					pause(context);					Log.d("harvic","action_pause");				}else{					play(context);					Log.d("harvic","action_play");				}					break;			case ACTION_NEXT:				playNext(context);				Log.d("harvic","action_next");				break;			default:				break;			}		}	}};

然后是初始化播放列表:

private int[] mArrayList = new int[9];
private voID initList() {	mArrayList[0] = R.raw.dui_ni_ai_bu_wan;	mArrayList[1] = R.raw.fei_yu;	mArrayList[2] = R.raw.gu_xiang_de_yun;	mArrayList[3] = R.raw.hen_ai_hen_ai_ni;	mArrayList[4] = R.raw.new_day;	mArrayList[5] = R.raw.shi_jian_li_de_hua;	mArrayList[6] = R.raw.ye_gui_ren;	mArrayList[7] = R.raw.yesterday_once_more;	mArrayList[8] = R.raw.zai_lu_shang;}

最后在Service起来时就应该让它播放歌曲:

private voID mediaPlayerStart(){	mPlayer = new MediaPlayer();	mPlayer = MediaPlayer.create(getApplicationContext(),mArrayList[mIndex]);	mPlayer.start();	mPlayState = true;}

上面就基本上就是MusicManageService的骨架了,其它就是上一首,下一首,播放、暂停,这些难度都不大,就不细讲了,完整的MusicManageService.java代码如下:

public class MusicManageService extends Service { 	private MediaPlayer mPlayer;	private int mIndex = 4;// 从中间开始放	private int[] mArrayList = new int[9];	public static String ACTION = "to_service";	public static String KEY_USR_ACTION = "key_usr_action";	public static final int ACTION_PRE = 0,ACTION_NEXT = 2;	private boolean mPlayState = false; 	private broadcastReceiver receiver = new broadcastReceiver() {		@OverrIDe		public voID onReceive(Context context,Intent intent) {			String action  = intent.getAction();			if (ACTION.equals(action)) {				int Widget_action = intent.getIntExtra(KEY_USR_ACTION,-1);								switch (Widget_action) {				case ACTION_PRE:					playPrev(context);					Log.d("harvic","action_prev");					break;				case ACTION_PLAY_PAUSE:					if (mPlayState) {						pause(context);						Log.d("harvic","action_pause");					}else{						play(context);						Log.d("harvic","action_play");					}						break;				case ACTION_NEXT:					playNext(context);					Log.d("harvic","action_next");					break;				default:					break;				}			}		}	}; 	@OverrIDe	public IBinder onBind(Intent intent) {		// Todo auto-generated method stub		return null;	} 	@OverrIDe	public voID onCreate() {		// Todo auto-generated method stub		super.onCreate(); 		IntentFilter intentFilter = new IntentFilter();		intentFilter.addAction(ACTION);		registerReceiver(receiver,intentFilter); 		initList(); 		mediaPlayerStart();			}	private voID mediaPlayerStart(){		mPlayer = new MediaPlayer();		mPlayer = MediaPlayer.create(getApplicationContext(),mArrayList[mIndex]);		mPlayer.start();		mPlayState = true;	} 	private voID initList() {		mArrayList[0] = R.raw.dui_ni_ai_bu_wan;		mArrayList[1] = R.raw.fei_yu;		mArrayList[2] = R.raw.gu_xiang_de_yun;		mArrayList[3] = R.raw.hen_ai_hen_ai_ni;		mArrayList[4] = R.raw.new_day;		mArrayList[5] = R.raw.shi_jian_li_de_hua;		mArrayList[6] = R.raw.ye_gui_ren;		mArrayList[7] = R.raw.yesterday_once_more;		mArrayList[8] = R.raw.zai_lu_shang;	} 	@OverrIDe	public int onStartCommand(Intent intent,int flags,int startID) { 		return super.onStartCommand(intent,flags,startID);	} 	@OverrIDe	public voID onDestroy() { 		super.onDestroy();		mPlayer.stop();	} 	/**	 * 播放下一首	 * 	 * @param context	 */	public voID playNext(Context context) {		if (++mIndex > 8) {			mIndex = 0;		}		mPlayState = true;		playSong(context,mArrayList[mIndex]);	} 	/**	 * 播放上一首	 * 	 * @param context	 */	public voID playPrev(Context context) {		if (--mIndex < 0) {			mIndex = 8;		}		mPlayState = true;		playSong(context,mArrayList[mIndex]);	} 	/*	 * 继续播放	 */	public voID play(Context context) {		mPlayState = true;		mPlayer.start();	} 	/**	 * 暂停播放	 * 	 * @param context	 */	public voID pause(Context context) {		mPlayState = false;		mPlayer.pause();			} 	/**	 * 播放指定的歌曲	 * 	 * @param context	 * @param resID	 */	private voID playSong(Context context,int resID) {		AssetfileDescriptor afd = context.getResources().openRawResourceFd(				mArrayList[mIndex]);		try {			mPlayer.reset();			mPlayer.setDataSource(afd.getfileDescriptor(),afd.getStartOffset(),afd.getDeclaredLength());			mPlayer.prepare();			mPlayer.start();			afd.close();		} catch (Exception e) {			Log.e("harvic","Unable to play audio queue do to exception: "+ e.getMessage(),e);		} 	}}
二、Widget发送广播部分(Exampleappwidgetprovider.java)

首先,在用户添加Widget时,会调用OnUpdate()函数,所在我们在OnUpdate()中要实现绑定RemoteVIEw和更新Widget的 *** 作。

private voID pushUpdate(Context context,AppWidgetManager appWidgetManager) {		RemoteVIEws remoteVIEw = new RemoteVIEws(context.getPackagename(),R.layout.example_appWidget);	//将按钮与点击事件绑定	remoteVIEw.setonClickPendingIntent(R.ID.play_pause,getPendingIntent(context,R.ID.play_pause));	remoteVIEw.setonClickPendingIntent(R.ID.prev_song,R.ID.prev_song));	remoteVIEw.setonClickPendingIntent(R.ID.next_song,R.ID.next_song)); 	// 相当于获得所有本程序创建的appWidget	Componentname componentname = new Componentname(context,Exampleappwidgetprovider.class);	appWidgetManager.updateAppWidget(componentname,remoteVIEw);} @OverrIDepublic voID onUpdate(Context context,AppWidgetManager appWidgetManager,int[] appWidgetIDs) {		pushUpdate(context,appWidgetManager);}

首先是新建RemoteVIEw,并将它与那三个按钮相绑定,其中的GetPendingIntent的实现与上一篇一样,也是把按钮ID传进去,通过Uri来传送,这里为了接收到以后方便识别是按钮点击传过去的消息,我们随便加一个category字段,所以在接收方只需要通过intent.hascategory(Intent.category_ALTERNATIVE)来判断是不是我们这里传过去的Intent即可,这里又比上篇高级了一点点有没有,哈哈。

private PendingIntent getPendingIntent(Context context,int buttonID) {	Intent intent = new Intent();	intent.setClass(context,Exampleappwidgetprovider.class);	intent.addcategory(Intent.category_ALTERNATIVE);	intent.setData(Uri.parse("harvic:" + buttonID));	PendingIntent pi = PendingIntent.getbroadcast(context,intent,0);	return pi;}

然后是接收部分,在接收时,首先根据当前用户点击的哪个按钮,然后给MusicManageService发送不同的广播,让MusicManageService做出不同的响应,接收代码如下 :

public voID onReceive(Context context,Intent intent) {	String action = intent.getAction();	Log.d("harvic","action:"+action);		if (intent.hascategory(Intent.category_ALTERNATIVE)) {		Uri data = intent.getData();        int buttonID = Integer.parseInt(data.getSchemeSpecificPart());        switch (buttonID) {        case R.ID.play_pause:        	pushAction(context,MusicManageService.ACTION_PLAY_PAUSE);        	if(mStop){        		Intent startIntent = new Intent(context,MusicManageService.class);				context.startService(startIntent);        		mStop = false;        	}        	break;        case R.ID.prev_song:        	pushAction(context,MusicManageService.ACTION_PRE);        	break;        case R.ID.next_song:        	pushAction(context,MusicManageService.ACTION_NEXT);        	break;        } 	}	super.onReceive(context,intent);}

在这里首先根据是不是包含intent.hascategory(Intent.category_ALTERNATIVE)这个category来判断是不是点击按钮发出来的消息,然后提取出按钮ID,最后根据不同的按钮ID发送出不同的消息:

private voID pushAction(Context context,int ACTION) {    Intent actionIntent = new Intent(MusicManageService.ACTION);    actionIntent.putExtra(MusicManageService.KEY_USR_ACTION,ACTION);    context.sendbroadcast(actionIntent);}

这里发送消息与MusicManageService的消息接收方式是统一的,发送和接收都是通过Action来匹配,携带的值是当前的Action,下面就是MusicManageService接收时的部分代码,我再摘一遍,方便大家理解:

private broadcastReceiver receiver = new broadcastReceiver() {	@OverrIDe	public voID onReceive(Context context,-1);						switch (Widget_action) {			case ACTION_PRE:				break;			case ACTION_PLAY_PAUSE:				break;			case ACTION_NEXT:				break;			}		}	}};

所以完整的Exampleappwidgetprovider代码是这样的:

public class Exampleappwidgetprovider extends appwidgetprovider { 	private Exampleappwidgetprovider mProvIDer = null;	private boolean mStop = true;		private PendingIntent getPendingIntent(Context context,int buttonID) {		Intent intent = new Intent();		intent.setClass(context,Exampleappwidgetprovider.class);		intent.addcategory(Intent.category_ALTERNATIVE);		intent.setData(Uri.parse("harvic:" + buttonID));		PendingIntent pi = PendingIntent.getbroadcast(context,0);		return pi;	}		// 更新所有的 Widget	private voID pushUpdate(Context context,AppWidgetManager appWidgetManager) {				RemoteVIEws remoteVIEw = new RemoteVIEws(context.getPackagename(),R.layout.example_appWidget);		//将按钮与点击事件绑定		remoteVIEw.setonClickPendingIntent(R.ID.play_pause,R.ID.play_pause));		remoteVIEw.setonClickPendingIntent(R.ID.prev_song,R.ID.prev_song));		remoteVIEw.setonClickPendingIntent(R.ID.next_song,R.ID.next_song)); 		// 相当于获得所有本程序创建的appWidget		Componentname componentname = new Componentname(context,Exampleappwidgetprovider.class);		appWidgetManager.updateAppWidget(componentname,remoteVIEw);	} 	@OverrIDe	public voID onUpdate(Context context,int[] appWidgetIDs) {				pushUpdate(context,appWidgetManager);	}		// 接收广播的回调函数	@OverrIDe	public voID onReceive(Context context,Intent intent) {		String action = intent.getAction();		Log.d("harvic","action:"+action);				if (intent.hascategory(Intent.category_ALTERNATIVE)) {			Uri data = intent.getData();	        int buttonID = Integer.parseInt(data.getSchemeSpecificPart());	        switch (buttonID) {	        case R.ID.play_pause:	        	pushAction(context,MusicManageService.ACTION_PLAY_PAUSE);	        	if(mStop){	        		Intent startIntent = new Intent(context,MusicManageService.class);					context.startService(startIntent);	        		mStop = false;	        	}	        	break;	        case R.ID.prev_song:	        	pushAction(context,MusicManageService.ACTION_PRE);	        	break;	        case R.ID.next_song:	        	pushAction(context,MusicManageService.ACTION_NEXT);	        	break;	        }			}		super.onReceive(context,intent);	}		private voID pushAction(Context context,int ACTION) {        Intent actionIntent = new Intent(MusicManageService.ACTION);        actionIntent.putExtra(MusicManageService.KEY_USR_ACTION,ACTION);        context.sendbroadcast(actionIntent);    }}

到这里,效果是这样的:(在模拟器上点击看起来没有任何反应,其实已经在播放歌曲了,上一首,下一首,播放、暂停功能都是可用的)

三、Service反向通知Widget更新当前状态

上面我们已经实现了Widget按钮向Service发广播来播放歌曲的播放、暂停,上一首,下一首,但是我们的Widget状态确没有改变,这节我们就需要根据当前歌曲的状态来更新Widget控件的状态。

1、发送当前状态广播

首先,我们要在MusicManageService中根据当前的播放状态往Exampleappwidgetprovider发送广播,广播的目的主要是改变当前播放按钮的状态(播放、暂停)还有更新TextVIEw,让它显示当前播放歌曲的ID值。

所以我们在发送广播时,需要定义Intent的Action,和存放播放状态、歌曲ID的putExtra(key,value)中的key值:

public static String MAIN_UPDATE_UI = "main_activity_update_ui";  //Actionpublic static String KEY_MAIN_ACTIVITY_UI_BTN = "main_activity_ui_btn_key"; //putExtra中传送当前播放状态的keypublic static String KEY_MAIN_ACTIVITY_UI_TEXT = "main_activity_ui_text_key"; //putextra中传送TextVIEw的keypublic static final int  VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2;//当前歌曲的播放状态

发送时:

private voID poststate(Context context,int state,int songID) {	Intent actionIntent = new Intent(Exampleappwidgetprovider.MAIN_UPDATE_UI);	actionIntent.putExtra(Exampleappwidgetprovider.KEY_MAIN_ACTIVITY_UI_BTN,state);	actionIntent.putExtra(Exampleappwidgetprovider.KEY_MAIN_ACTIVITY_UI_TEXT,songID);	context.sendbroadcast(actionIntent);}

其中:

 

state就是上面VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2其中一个状态;

songID:表示当前播放歌曲的ID值

所以在播放歌曲状态和歌曲ID值改变时,就应该发送广播:

首先,在Service起来时,我们开始播放歌曲:
 

private voID mediaPlayerStart(){	mPlayer = new MediaPlayer();	mPlayer = MediaPlayer.create(getApplicationContext(),mArrayList[mIndex]);	mPlayer.start();	mPlayState = true;	poststate(getApplicationContext(),Exampleappwidgetprovider.VAL_UPDATE_UI_PLAY,mIndex);}

同样,在上一首,下一首,播放,暂停时都要发送广播:

 

播放时:(改变了播放状态)

public voID play(Context context) {	……	poststate(context,mIndex);}

暂停时:(改变了播放状态)

public voID pause(Context context) {	……			poststate(context,Exampleappwidgetprovider.VAL_UPDATE_UI_PAUSE,mIndex);}

上一首:(改变了歌曲ID)

public voID playPrev(Context context) {	……	poststate(context,mIndex);}

下一首:(改变了歌曲ID)

public voID playNext(Context context) {	……	poststate(context,mIndex);}

OK啦,到这里发送就结束了,下面就是接收了。

2、接收广播

 

首先在接收广播之前要注册,Exampleappwidgetprovider以前说过是直接派生自 broadcastReceiver的,所有我们只能采用静态注册的方式:注意的action要与发送的一致,即:main_activity_update_ui

        <receiver androID:name=".Exampleappwidgetprovider" >            <intent-filter>				<action androID:name="androID.appWidget.action.APPWidget_UPDATE" />				<action androID:name="main_activity_update_ui" />			</intent-filter>			<Meta-data androID:name="androID.appWidget.provIDer"				androID:resource="@xml/example_appWidget_info" />        </receiver>

在上面,我们在Exampleappwidgetprovider中更新RemoteVIEw的pushUpdate() 的代码是这样的:

private voID pushUpdate(Context context,remoteVIEw);}

在这里,我们只是绑定了三个按钮控件,但并没有更新当前播放按钮状态和TextVIEw上的字体,所以我们对它加以更改,在绑定按钮以后,根据当前接收到的状态,更新RemoteVIEw

private voID pushUpdate(Context context,String songname,Boolean play_pause) {		RemoteVIEws remoteVIEw = new RemoteVIEws(context.getPackagename(),R.ID.next_song));		//设置内容	if (!songname.equals("")) {		remoteVIEw.setTextVIEwText(R.ID.song_name,songname);	}	//设定按钮图片	if (play_pause) {		remoteVIEw.setimageVIEwResource(R.ID.play_pause,R.drawable.car_musiccard_pause);	}else {		remoteVIEw.setimageVIEwResource(R.ID.play_pause,R.drawable.car_musiccard_play);	}	// 相当于获得所有本程序创建的appWidget	Componentname componentname = new Componentname(context,remoteVIEw);}

其中songname存储接收过来的歌曲ID值,play_pause表示当前歌曲的播放状态,根据当前的播放状态加载不同的播放状态图片;

 

在理解了上面的更新RemoteVIEw的部分以后,下面看看接收广播的代码:

public voID onReceive(Context context,"action:"+action);		if (intent.hascategory(Intent.category_ALTERNATIVE)) {		…… //接收到的按钮点击广播的处理部分	}else if (MAIN_UPDATE_UI.equals(action)){			int play_pause =  intent.getIntExtra(KEY_MAIN_ACTIVITY_UI_BTN,-1);			int songID = intent.getIntExtra(KEY_MAIN_ACTIVITY_UI_TEXT,-1);			switch (play_pause) {			case VAL_UPDATE_UI_PLAY:				pushUpdate(context,AppWidgetManager.getInstance(context),"current sond ID:"+songID,true);				break;			case VAL_UPDATE_UI_PAUSE:				pushUpdate(context,false);				break;			default:				break;			}					} 	super.onReceive(context,intent);}

首先,我们通过获取action值来判断当前是不是MusicManageService传过来的消息,然后得到传过来当前的播放状态和歌曲ID值,然后利用pushUpdate更新RemoteVIEw;

四、附:相关问题

下面记录一下,我在实际开发中遇到的问题,分享给大家

1、有关RemoteVIEws实例复用(绝对要每次新建RemoteVIEw实例)

在实际项目中,大家可能会想到复用remoteVIEw,即如果已经创建了就不再重新加载layout,而是重新绑定控件及数据


 !!!!!!!千万不要这样!!!!!!!一定要每次都要新建remoteVIEw!!!!!这是血和泪的教训!!!!!


 因为:如果你在绑定数据时涉及图片等大数据,remoteVIEw不会每次清理,所以如果每次都使用同一个remoteVIEw进行传输会因为溢出而绐终无响应!!!! 你看着每次动作都通过updateAppWidget传送出去了,但界面死活就是没响应;而且重装应用程序也不会有任何反应,只能重启手机才会重新有反应,也是醉了。
 主要原因在于:Binder data size limit is 512k,由于传输到appWidget进程中的Binder最大数据量是512K,并且RemoteVIEw也不会每次清理, 所以如果每次都使用同一个RemoteVIEw进行传输会因为溢出而报错.所以必须每次重新建一个RemoteVIEw来传输!!!!!!

2、 *** 作RemoteVIEw中控件的方法

在RemoteVIEw中的 *** 作控件的方法非常有限,但我们的需求确是非常多样的,所以怎样才能像 *** 作平常控件一样多样性的 *** 作RemoteVIEw呢,

举例:
如果我们需要把Widget中的一个vIEw临时隐藏,我们可以这样调用:remotevIEws.setInt(textvIEwID,"setVisibility",VIEW.INVISIBLE);
 

OK啦,终于写完了,有点要累尿了,这部分涉及到的代码量太大,我上面讲的也不太详细,大家匹配代码再仔细琢磨琢磨一下吧。

参考文章:

1、@L_301_0@  (初步入门级,写的很好)

2、《Android桌面组件AppWidget讲解》

3、《app widget 进入主客户端代码》  (讲述了,点击桌面Widget如何进入主app的代码)

4、《AppWidget基础小结》

5、《Android 桌面组件【app widget】 进阶项目--心情记录器》

6、《Android Widget开发的相关技术点记录》 (其中有:存储WidgetID,以防app崩溃后,无法更新Widget的问题)

7、《android widget开发点滴》

8、《Android Appwidget 之按钮事件》

9、《android 转载 widget点击事件》

10、《 Android基础之AppWidgetProvider》

11、《android之widget详解

 

 

 

 

 

 

 

 

 

 

 

 

 

 

总结

以上是内存溢出为你收集整理的桌面widget详解(四)——桌面音乐播放器(实战)全部内容,希望文章能够帮你解决桌面widget详解(四)——桌面音乐播放器(实战)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1089430.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存