ophone平台提供了完整的多媒体解决方案。为开发者提供了统一的,简单易用的开发接口。本文首先介绍了ophone平台的多媒体框架,然后详细介绍了 在ophone平台上开发音乐播放程序所需的基本知识。通过一步一步构建一个简单的音乐播放器示例程序,来帮助读者了解具体的开发过程。该示例涵盖了application,activity,service,intent,broadcast receiver等基本概念,使读者对ophone程序的开发有一个全面的了解,进一步巩固和熟悉这些基本概念。最后介绍了如何利用mat工具分析ophone
程序。
本文适合ophone平台开发的初学者阅读。(作者:cmri 孟钊)
ophone平台的多媒体架构
在开始构建我们的示例程序前,先让我们大概了解一下ophone平台的多媒体框架。
图 一
图一是ophone平台的整体框架结构,从图上我们可以看出ophone平台大致可以分成以下几个层次:
最上层是application层。它包含了主屏,电话,浏览器,地址本等核心的应用程序。我们将开发的音乐播放器也属于这一层。
第二层是application framework层。这一层为开发者提供了完整的编程接口。多媒体部分提供了mediaplayer, mediarecorder等接口。同时mediaprovider,mediascanner等系统服务也对媒体文件的管理提供了支持。本文将重点介绍 它们的使用。
第三层是library层, 它由一系列的c/c++库组成,这些库的能力通过jni封装成java接口,由application framework层提供给开发者。多媒体系统库opencore,它是ophone多媒体的核心,来源于packetvideo。它非常复杂,提供了完 整的多媒体解决方案。
最底层为linux kernel和驱动,负责与硬件的数据交互等。
图二说明了在ophone平台中播放音乐文件时的调用关系。
对于应用程序开发者来说,需要重点学习和关注的是如何使用appliation framework层提供给开发者的接口。
音乐媒体信息的管理
在开始构架程序之前,我们需要准备一下必须的基本知识。首先来了解一下在ophone平台中应该如何获取音乐文件的信息以及如何管理这些信息。
ophone系统提供了mediascanner,mediaprovider,mediastore等接口,并且提供了一套数据库表格,通过content provider的方式提供给用户。当手机开机或者有sd卡插拔等事件发生时,系统将会自动扫描sd卡和手机内存上的媒体文件,如audio,video,图片等,将相应的信息放到定义好的数据库表格中。在这个程序中,我们不需要关心如何去扫描手机中的文件,只要了解如何查询和使用 这些信息就可以了。
mediastore中定义了一系列的数据表格,通过contentresolver提供的查询接口,我们可以得到各种需要的信息。下面我们重点介绍如何管理sd卡上的音乐文件信息。
先来了解一下contentresolver的查询接口:
view plaincopy to clipboardprint?
cursor query(uri uri, string[] projection, string selection, string[] selectionargs, string sortorder);
[java]
view plaincopyprint?
uri:指明要查询的数据库名称加上表的名称,从mediastore中我们可以找到相应信息的参数,具体请参考开发文档。
projection: 指定查询数据库表中的哪几列,返回的游标中将包括相应的信息。null则返回所有信息。
selection: 指定查询条件
selectionargs:参数selection里有 ?这个符号是,这里可以以实际值代替这个问号。如果selection这个没有?的话,那么这个string数组可以为null。
sortorder:指定查询结果的排列顺序
查询所有歌曲:
cursor cursor = query(mediastore.audio.media.external_content_uri,
null,
null,
null, mediastore.audio.media.default_sort_order);
null, null, mediastore.audio.media.default_sort_order);
该命令将返回所有在外部存储卡上的音乐文件的信息,其中常用的信息如下:
mediastore.audio.media._id:歌曲id
int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.media._id));
mediastore.audio.media.title:歌曲的名称
string tilte = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.title));
mediastore.audio.media.album :歌曲的专辑名
string album = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.album));
mediastore.audio.media.artist:歌曲的歌手名
string artist = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.artist));
mediastore.audio.media.data:歌曲文件的路径
string url = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.data));
mediastore.audio.media.duration:歌曲的总播放时长
int duration = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.media.duration));
mediastore.audio.media.size: 歌曲文件的大小
int size = cursor.getlong(cursor.getcolumnindexorthrow(mediastore.audio.media.size));
查询歌手信息:
cursor cursor = query(mediastore.audio.artists.external_content_uri,
null, null,
mediastore.audio.artists.default_sort_order);
该命令将返回所有在外部存储卡上的歌手信息,其中常用的信息如下:
mediastore.audio.artists._id:歌手id
int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.artists._id));
mediastore.audio.artists.artist :歌手姓名
string name = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.artists.artist));
mediastore.audio.artists.number_of_albums: 共有多少该歌手的专辑
int numofalbum = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.artists.number_of_albums));
mediastore.audio.artists.number_of_tracks: 共有多少该歌手的歌曲
int numofsong = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.artists.number_of_tracks));
查询专辑信息:
cursor cursor = query(mediastore.audio.albums.external_content_uri,
null, null,null,
mediastore.audio.albums.default_sort_order);
该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:
mediastore.audio.albums._id :专辑id
int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.albums._id));
mediastore.audio.albums.album:专辑名称
string name = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.albums.album));
mediastore.audio.albums.number_of_songs:共用多少歌曲属于该专辑
int numofsong = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.albums.number_of_songs));
查询播放列表
cursor cursor = query(mediastore.audio.playlists.external_content_uri,
mediastore.audio.playlists.date_added +
" asc");
mediastore.audio.playlists.date_added + " asc");
该命令将返回所有在外部存储卡上的专辑信息,其中常用的信息如下:
mediastore.audio.playlists._id :播放列表id
int id = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.playlists._id));
mediastore.audio.playlists.name:播放列表名称
string name = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.playlists.name));
mediastore.audio.playlists.date_added :添加时间
long dateadded = cursor.getlong(cursor.getcolumnindexorthrow(mediastore.audio.playlists.date_added));
mediastore.audio.playlists.date_modified :修改时间
long datemodified = cursor.getlong(cursor.getcolumnindexorthrow(mediastore.audio.playlists.date_modified));
通过组合这些查询结果,指定查询条件,用户可以很方便的查询指定的媒体信息,比如:查询属于指定歌手(歌手id 为 aid)的歌曲:
query(mediastore.audio.media.external_content_uri,
mediastore.audio.media.artist_id +
"=" + aid, null,
mediastore.audio.media.title);
查询属于指定专辑(专辑id 为 aid)的歌曲:
return query(mediastore.audio.media.external_content_uri,
mediastore.audio.media.album_id +
以上我们重点介绍了音乐媒体信息的查询方法,对于媒体信息的增删改等操作主要集中在对播放列表的管理上,也是通过content resolver的insert,update,delete等接口来实现的。只要搞清楚了各个参数的含义,相应uri以及各个字段的义,很容易实现。由 于篇幅原因,我们不再详细介绍,有兴趣的朋友可以查看ophone开发文档。
音乐播放
音乐文件的播放功能是由mediaplayer类实现的,mediaplayer提供了常用的接口,比如播放,暂停,停止,快速定位等。
播放音乐文件的基本调用流程:
生成mediaplayer实例。
设置播放源(文件)
准备播放
开始播放
mediaplayer mp = new mediaplayer();
mp.setdatasource(file_to_play);
mp.prepare();
mp.start();
以上代码即可以完成最简单的音乐播放功能。
除了mediaplayer类,我们还需要注意几个播放器件listener的使用,它们提供了播放器的更多的状态信息。
1.mediaplayer.onbufferingupdatelistener
当播放网络上的媒体文件或者流媒体时 mediaplayer.onbufferingupdatelistener 的onbufferingupdate(mediaplayer mp, int percent)接口函数会被回调,通知当前的缓冲进度信息。
通过setonbufferingupdatelistener(mediaplayer.onbufferingupdatelistener listener) 函数来注册该listener
2.mediaplayer.oncompletionlistener
当前歌曲播放结束后,mediaplayer.oncompletionlistener的 oncompletion(mediaplayer mp) 接口会被回调,通知歌曲结束事件。
通过setoncompletionlistener(mediaplayer.oncompletionlistener listener) 函数来注册该监听器
3.mediaplayer.onerrorlistener
当由于某种原因,mediaplayer进入错误状态时,mediaplayer.onbufferingupdatelistener的onerror(mediaplayer mp, int what, int extra)接口会被回调,通知错误信息。此时mediaplayer 应该调用reset()函数,将mediaplayer重新置于idle状态。如果发生无法回复的错误,需要重新获取mediaplayer的实例。
4.mediaplayer.onpreparedlistener
当播放网络媒体文件或流媒体时,播放器的准备时间较长,播放器准备完毕可以开始播放时,mediaplayer.onpreparedlistener的onprepared(mediaplayer mp)接口会被回调,通知该信息。
当播放器需要支持播放流媒体或者网络媒体文件时,建议使用prepareasync()接口调用来准备播放器,同时通过mediaplayer.onpreparedlistener来监听prepared信息。这样可以避免因为网络等因素造成的mediaplayer准 备时间过长进而导致程序长时间无响应。
构建音乐播放器程序
在学习了媒体信息管理和媒体播放的基本内容后,我们现在可以开始动手构建我们的简单播放器示例程序了。
一.创建工程
在eclipse开发环境中创建一个新的android project.
file > new > android project.
设置工程名为musicplayerdemo, 设置packages名为 com.ophone
二.指定程序的application,添加musicplayerdemoapp
添加musicplayerdemoapp类,它继承自 android.app.application。
application类用来存储程序的状态,它存在于整个程序的生命周期之中。
修改androidmanifest.xml如下,指定musicplayerdemoapp为示例程序的application.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ophone"
android:versioncode="1"
android:versionname="1.0">
<application android:name="musicplayerdemoapp"
android:icon="@drawable/icon"
android:label="@string/app_name">
</application>
</manifest>
我们需要注意application的两个函数: oncreate() 和 onterminate(). 当程序开始运行时,oncreate()函数会首先被调用,此时没有任何其他的对象在运行,在这里我们可以进行一些初始化的工作。当程序结束时, onterminate()函数会被调用,程序进程将会退出,我们可以在此做一些最终的清理工作。需要注意的是,当因为系统资源紧张等问题,程序被系统kill的时候,onterminate()不会被调用到,程序将直接退出。
稍后我们再来修改musicplayerdemoapp,先往下继续。
三.管理音乐信息的类musicdbcontroller
为了使接口整洁,便于管理和使用,我们将在第三章介绍的 查询管理音乐信息的方法统一封装在musicdbcontroller类中。
public static musicdbcontroller getinstance(musicplayerdemoapp app) {
if(sinstance == null) {
sinstance = new musicdbcontroller(app);
}
return sinstance;
}
private musicdbcontroller(musicplayerdemoapp app) {
mapp = app;
}
private cursor query(uri _uri, string[] prjs, string selections,
string[] selectargs, string order) {
contentresolver resolver = mapp.getcontentresolver();
if (resolver ==
null) {
return null;
return resolver.query(_uri, prjs, selections, selectargs,
order);
musicdbcontroller采用单例模式,使程序中只有唯一的实例。我们传入musicplayerdemoapp 作为context生成content resolver,用来查询媒体库。
现在,我们修改musicplayerdemoapp,添加一个musicdbcontroller的成员,并在oncreate()中初始化它。
private musicdbcontroller mdbcontorller =
null;
public void oncreate() {
// todo auto-generated method stub
super.oncreate();
// init musicdbcontroller
mdbcontorller = musicdbcontroller.getinstance(this);
并且提供一个获取musicdbcontroller的接口:
public musicdbcontroller getmusicdbcontroller(){
return mdbcontorller;
这样程序中的任何activity和serivce都可以通过getapplicatio()函数得到musicplayerdemoapp, 再通过getmusicdbcontroller()接口获取musicdbcontroller,进而获取所需要的媒体信息。
四.展示媒体库-musiclistactivity 和 musiclistadapter。
首先添加musiclistadapter,它继承自simplecursoradapter。通过重载bindview()函数, 把媒体库信息绑定到指定的listview上。
我们使用android.r.layout.cmcc_list_5作为listview的layout,它的布局定义如下:
android.r.layout.cmcc_list_5:
android.r.id.listicon1 图片
android.r.id.text1 左上文字
android.r.id.text2 左下文字
android.r.id.text3 右下文字
public void bindview(view view, context context, cursor cursor) {
super.bindview(view, context, cursor);
textview titleview = (textview) view.findviewbyid(android.r.id.text1);
textview artistview = (textview) view.findviewbyid(android.r.id.text2);
textview durationview = (textview) view.findviewbyid(android.r.id.text3);
imageview imageview = (imageview) view.findviewbyid(android.r.id.listicon1);
// set icon
imageview.setimageresource(r.drawable.cmcc_list_music);
// set track name
titleview.settext(cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.title)));
// set artist name
artistview.settext(cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.artist)));
// set duration
int duration = cursor.getint(cursor.getcolumnindexorthrow(mediastore.audio.media.duration));
durationview.settext(maketimestring(duration));
注意,上面这段代码中的android.r.id.text1,android.r.id.text2,android.r.id.text3 和 android.r.id.listicon1是在我们传入中的listview(android.r.layout.cmcc_list_5)的layout中定义的。如果你使用了自己定义的layout,请把它们替换成你自己定义的widget id。
现在可以来添加我们的第一个activity -musiclistactivity,它以list的形式展示了所有歌曲。musiclistactivity继承自listactivity。
在oncreate()中获取musicdbcontroller的实例,为获取歌曲信息做准备。
private musicdbcontroller mdbcontroller =
null;
/** called when the activity is first created. */
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.main);
mdbcontroller = ((musicplayerdemoapp)getapplication()).getmusicdbcontroller();
通过musiclistadapter,我们将从musicdbcontroller中拿到的媒体库信息,绑定到listview,我们在onresume()完成这个工作。
protected void onresume() {
super.onresume();
mcursor = mdbcontroller.getallsongs();
musiclistadapter adapter = new musiclistadapter(this, android.r.layout.cmcc_list_5, mcursor,
new string[]{}, new
int[]{});
setlistadapter(adapter);
}
将musiclistactivity添加到androidmanifest.xml中
<activity android:name=".musiclistactivity">
<intent-filter>
<action android:name="android.intent.action.main" />
<category android:name="android.intent.category.launcher" />
</intent-filter>
</activity>
现在运行一下我们的程序,它已经可以展现给你媒体库的音乐列表了。
同样的,仿照上面的过程,我们还可以添加展示专辑列表,艺术家列表等等activity,我们就不再一一介绍了。
五.后台播放-使用service
现在我们需要考虑如何来播放这些媒体库中的文件了。我们希望当用户退出这个程序界面后,我们的程序仍然能够继续播放歌曲,比如用户在读邮件时,可以听听音 乐。为了达到后台播放的效果,需要使用service。当程序的所有activity都退出后,service仍然可以在后台运行。在这个示例中我们使用local service,它与应用程序运行在同一个进程中。(我们甚至可以不使用bind service就直接获得它的句柄,调用它所提供的函数。)
首先,创建一个musicplaybackservice类,它继承自android.app.service,重载onbind方法,返回自 定义的localbinder,通过localbinder的getservice()方法就可以获得musicplaybackservice的句柄 了。
private final ibinder mbinder =
new localbinder();
public class localbinder
extends binder {
public musicplaybackservice getservice() {
return musicplaybackservice.this;
public ibinder onbind(intent intent) {
return mbinder;
我们继续完成musicplaybackservice的基本构架,添加一个mediaplayer成员,并在oncreate()函数中对其进行初始化,它将负责音乐播放的主要功能。
private mediaplayer mmediaplayer =
mmediaplayer = new mediaplayer();
构架完成musicplaybackservice的基本架构后,我们要定义一些常用的控制接口了,其他模块通过这些接口,可以控制音乐的播放,暂停,停止等功能。
public void setdatasource(string path) {
try {
mmediaplayer.reset();
mmediaplayer.setdatasource(path);
mmediaplayer.prepare();
} catch (ioexception e) {
return;
} catch (illegalargumentexception e) {
public void start() {
mmediaplayer.start();
public void stop() {
mmediaplayer.stop();
public void pause() {
mmediaplayer.pause();
public boolean isplaying() {
return mmediaplayer.isplaying();
public int getduration() {
return mmediaplayer.getduration();
public int getposition() {
return mmediaplayer.getcurrentposition();
public long seek(long whereto) {
mmediaplayer.seekto((int) whereto);
return whereto;
最后,修改androidmanifest.xml,添加musicplaybackservice的定义。
<service android:name=".musicplaybackservice" android:exported="true" >
<action android:name="com.ophone.musicplaybackservice" />
</intent-filter>
</service>
六.开始播放歌曲
musicplaybackservice准备就绪,我们可以利用它来播放歌曲了。修改musiclistactivity,在 oncreate() 中通过startservice()函数启动musicplaybackservice,并通过bindservice()函数与之绑定。当绑定完成 时,serviceconnection的 onserviceconnected()接口将被调用。
private musicplaybackservice mplaybackservice =
private serviceconnection mplaybackconnection =
new serviceconnection() {
public void onserviceconnected(componentname classname, ibinder service) {
mplaybackservice = ((musicplaybackservice.localbinder)service).getservice();
public void onservicedisconnected(componentname classname) {
mplaybackservice = null;
};
public void oncreate(bundle savedinstancestate) {
setcontentview(r.layout.list_layout);
// bind playback service
startservice(new intent(this,musicplaybackservice.class));
bindservice(new intent(this,musicplaybackservice.class), mplaybackconnection, context.bind_auto_create);
为musiclistactivity添加点击事件处理,当用户点击一个音乐item时,会开始自动播放该歌曲,当用户点击一个item时,onlistitemclick()函数会被调用。
protected void onlistitemclick(listview l, view v,
int position, long id) {
super.onlistitemclick(l, v, position, id);
if (mcursor ==
null ||mcursor.getcount() == 0) {
mcursor.movetoposition(position);
string url = mcursor
.getstring(mcursor
.getcolumnindexorthrow(mediastore.audio.media.data));
mplaybackservice.setdatasource(url);
mplaybackservice.start();
现在赶紧运行一下程序吧,看看是不是已经可以播放音乐了呢。
七. 播放控制-使用intent和broadcast receiver
目前我们只能播放音乐,还无法控制音乐的播放,暂停,停止,等等,让我们进一步来完善这个播放程序,给它添加两个控制按钮。
修改musiclistactivity的layout文件list_layout.xml如下:
<linearlayout
android:id="@+id/widget1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
>
<relativelayout android:id="@+id/control_panel"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<textview android:id="@+id/show_text"
android:textsize="20sp"
android:text="@string/click_to_play"/>
<button android:id="@+id/play_pause_btn"
android:layout_width="100px"
android:layout_height="wrap_content"
android:layout_alignparentleft="true"
android:visibility="invisible"
android:text="@string/play"/>
<button android:id="@+id/stop_btn"
android:layout_alignparentright="true"
android:text="@string/stop"/>
</relativelayout>
<listview android:id="@id/android:list"
android:layout_height="fill_parent"
android:cachecolorhint="#00000000"/>
<textview android:id="@id/android:empty"
android:text="@string/no_music"/>
</linearlayout>
在musiclistactivity中,添加两个按钮点击事件的处理程序,通过button的setonclicklistener()函数,为button添加一个button.onclicklistener,当有点击事件发生时,button.onclicklistener的onclick()接口将被调用。
private textview mtextview =
private button mplaypausebutton =
private button mstopbutton =
在oncreate函数中,增加如下的代码:
mtextview = (textview)findviewbyid(r.id.show_text);
mplaypausebutton = (button) findviewbyid(r.id.play_pause_btn);
mstopbutton = (button) findviewbyid(r.id.stop_btn);
mplaypausebutton.setonclicklistener(new button.onclicklistener() {
public void onclick(view v) {
// perform action on click
if (mplaybackservice !=
null && mplaybackservice.isplaying()) {
mplaybackservice.pause();
mplaypausebutton.settext(r.string.play);
} else
if (mplaybackservice != null){
mplaybackservice.start();
mplaypausebutton.settext(r.string.pause);
}
}
});
mstopbutton.setonclicklistener(new button.onclicklistener() {
public
void onclick(view v) {
null ) {
mtextview.setvisibility(view.visible);
mplaypausebutton.setvisibility(view.invisible);
mstopbutton.setvisibility(view.invisible);
mplaybackservice.stop();
现在运行程序,我们还看不到这两个控制按钮,默认状态下他们是不可见状态。程序刚启动时,默认显示提示信息。当播放器状态发生改变,有歌曲进行播放时,我 们显示控制按钮,隐藏提示信息。我们使用intent和broadcast receiver来实现这个功能。
定义准备完毕和播放完毕的action string
public static
final string player_prepare_end =
"com.ophone.musicplaybackservice.prepared";
public static
final string play_completed = "com.ophone.musicplaybackservice.playcompleted";
播放器状态发生改变的时候,通过intent的形式,将消息广播出去,给mediaplayer添加mediaplayer.onpreparedlistener和mediaplayer.oncompletionlistener,监听准备完毕和播 放结束的消息。
mediaplayer.oncompletionlistener mcompletelistener =
new mediaplayer.oncompletionlistener() {
public void oncompletion(mediaplayer mp) {
broadcastevent(play_completed);
};
mediaplayer.onpreparedlistener mpreparelistener =
new mediaplayer.onpreparedlistener() {
public void onprepared(mediaplayer mp) {
broadcastevent(player_prepare_end);
private void broadcastevent(string what) {
intent i = new intent(what);
sendbroadcast(i);
修改musicplaybackservice,在mediaplayer中注册这个两个listener:
public void oncreate() {
mmediaplayer = new mediaplayer();
mmediaplayer.setonpreparedlistener(mpreparelistener);
mmediaplayer.setoncompletionlistener(mcompletelistener);
在musiclistactivity中,我们定义一个broadcastreceiver来处理这两个消息:
protected broadcastreceiver mplayerevtreceiver =
new broadcastreceiver() {
@override
public void onreceive(context context, intent intent) {
string action = intent.getaction();
if (action.equals(musicplaybackservice.player_prepare_end)) {
// will begin to play
mtextview.setvisibility(view.invisible);
mplaypausebutton.setvisibility(view.visible);
mstopbutton.setvisibility(view.visible);
mplaypausebutton.settext(r.string.pause);
} else if(action.equals(musicplaybackservice.play_completed)) {
mplaypausebutton.settext(r.string.play);
在oncreate()函数中,注册这个broadcastreceiver来监听player_prepare_end 和play_completed 这两个信息 ,在oncreate函数中添加下面的代码:
intentfilter filter = new intentfilter();
filter.addaction(musicplaybackservice.player_prepare_end);
filter.addaction(musicplaybackservice.play_completed);
registerreceiver(mplayerevtreceiver, filter);
ok,现在我们的音乐播放器已经成型了,马上运行一下吧。
给程序加点新功能
下面介绍的功能,在我们的示例代码中并没有实现,如果您感兴趣的话,可以按照下文介绍的大概步骤,添加到程序中,他们其实都很简单。
1.利用alarm service实现简单的闹铃功能。
alarm service是ophone平台提供的一个系统服务。程序可以向alarm service注册一个pendingintent,当到达注册时间的时候,alarm service会发出这个事先注册的intent,程序监听这个intent就可以达到定时的效果。
1)添加一个broadcastreceiver
public class startalarm
extends broadcastreceiver {
// 添加处理程序,启动播放。
在androidmanifest.xml中添加定义:
<receiver android:name=".startalarm" />
2)注册alarm service
intent startintent = new intent(context, startalarm.class);
pendingintent startsender = pendingintent.getbroadcast(
context, 0, startintent,
0);
// schedule the alarm! starttimemillis 是定时时间
alarmmanager am = (alarmmanager)getsystemservice(alarm_service);
am.setrepeating(alarmmanager.rtc_wakeup, starttimemillis,
24 *
60 * 60 *
1000, startsender);
ok,我们就完成了定时注册,当注册时间到达时,即使程序没有运行,也会被唤醒,startalarm的onreceive()函数被调用,开始播放音乐。一个简单的闹钟功能就实现了。感兴趣的朋友可以马上动手试验试验。
2.设置振铃
当我们发现了一首非常好听的歌曲,想把它设置成来电振铃, 如何实现呢?很简单,只需要如下两个步骤。
第一步,更新歌曲在media provider数据库中的信息,
将 mediastore.audio.media.is_ringtone,
mediastore.audio.media.is_alarm,
mediastore.audio.media.is_notification都置成 1。
假设歌曲的id为 songid:
contentresolver resolver = ctx.getcontentresolver();
// set the flag in the database to mark this as a ringtone
uri ringuri = contenturis.withappendedid(mediastore.audio.media.external_content_uri, songid);
try {
contentvalues values = new contentvalues(2);
values.put(mediastore.audio.media.is_ringtone,
"1");
values.put(mediastore.audio.media.is_alarm,
values.put(mediastore.audio.media.is_notification,
"1");
resolver.update(ringuri, values, null,
null);
} catch (unsupportedoperationexception ex) {
第二步,通过android.provider.settings.profile的setringtone接口,设置歌曲为振铃:
settings.profile.setringtone(resolver, ringuri);
现在给自己打个电话试试看,是不是振铃已经起作用了
使用mat分析ophone程序
我们的示例代码已经完成了,大家可以按照上文的步骤自己一步一步来构造自己的音乐播放器,也可以使用附录的源代码包,将工程导入进eclipse直接体验一下。最后和大家分享一下使用mat分析ophone程序的方法。
通常来说我们调试ophone程序有两个最常见的方法,一,利用ophone平台提供的android.util.log通过log信息来分析 错误发生的原因。 二,通过设置断点,一步一步的跟踪程序发现问题。这两个方法非常有效,介绍相关方法的文章也很多,大家google一下就找到了。
还有一类常见的问题就是memory leak。对内存泄漏这类问题,以上两种方法不是很有效,在ddms工具里面,我们也基本上只能查看到heap的使用情况,对分析问题帮助不大。我们可以 利用eclipse mat (memory analyzer tool)工具来分析此类问题。eclipse memory analyzer是一个快速并且功能强大的java heap分析器,能够帮助你查找内存泄漏和减少内存消耗。
如何安装使用mat工具,请到http://www.eclipse.org/mat/学习,我们主要来介绍一下如何在ophone上得到程序运行的heap dump信息。
adb shell 登陆到手机或模拟器
su – 切换到root权限
chmod 777 /data/misc, 使/data/misc目录具有读写权限
通过ps命令,找到要调试的程序的pid
kill -10 pid
在/data/misc 目录下,会生成文件名类似heap-dump-xxxxx-pidxxx.hprof的文件。
通过adb pull 命令将.hprof文件拽到pc端
使用ophone sdk提供的hprof-conv工具将ophone生成的hprof文件转换成mat识别的标准格式。例如:
hprof-conv heap-dump-xxxxx-pidxxx.hprof standard-dump-file.hprof
9. 使用mat工具打开 standard-dump-file.hprof, 你将看到类似下图的分析报告。
分析报告提供了详尽的heap信息,同时还指出了可疑的内存泄漏的对象。
大家可以根据mat提供的详细heap信息,查找漏洞了。
附录:源代码:
uploads/file/musicplayerdemo.zip