✏️
Android应用开发
  • 简介
  • 目录
  • 第一章 熟悉Android Studio开发环境
    • 第一个项目——Hello World!
    • 第二个项目——计数器
    • 第三个项目——新闻阅读器
  • 第二章 活动、组件及布局
    • 第四个项目——消息发送
    • 第五个项目——计数器2
    • 第六个项目——登陆界面
    • 第七个项目——新闻列表
  • 第三章 数据持久化
    • 第八个项目——登陆界面2
    • 第九个项目——新闻列表2
  • 第四章 内容提供器与数据共享
    • 第十个项目——音乐播放器1
  • 第五章 多媒体与服务
    • 第十一个项目——音乐播放器2
    • 第十二个项目——音乐播放器3
  • 第六章 绑定服务与自定义广播
    • 第十三个项目——音乐播放器4
  • 第七章 网络与多线程
    • 第十四个项目——新闻列表3
  • 附录 扩展项目
    • 第十五个项目——使用ViewHolder提升ListView显示性能
    • 第十六个项目——使用SwipeRefreshLayout实现下拉刷新
    • 第十七个项目——使用接口回调实现删除ListView列表中的Item数据
    • 第十八个项目——使用RecyclerView显示列表数据
    • 第十九个项目——使用Fragment组织UI页面
    • 第二十个项目——使用CursorAdapter进行数据绑定及渲染
    • 第二十一个项目——使用Loader实现异步数据加载操作
    • 第二十二个项目——使用TimerTask及Handler实现秒表计时器
    • 第二十三个项目——使用AsyncTask进行网络异步请求
Powered by GitBook
On this page
  • 实验目的
  • 实验要求
  • 实验内容
  • 步骤一,新建MusicService服务类
  • 步骤二,重载MusicService的onCreate()、onDestroy()方法
  • 步骤三,重载MusicService的onStartCommand方法
  • 步骤四,编译项目运行App
  • 步骤五,使用Foreground Service形式运行MusicService
  • 实验小结

Was this helpful?

  1. 第五章 多媒体与服务

第十二个项目——音乐播放器3

Previous第十一个项目——音乐播放器2Next第六章 绑定服务与自定义广播

Last updated 4 years ago

Was this helpful?

实验目的

  • 了解Service的基本使用方法;

  • 了解Notification发送通知的方法;

实验要求

  • 使用Service实现音乐的后台播放;

  • 使用Notification与Foreground Service实现音乐的后台播放;

实验内容

在第十一个Android项目的基础上,自定义名为MusicService的服务类,将MediaPlayer进行封装,在该服务类中实现当GGMusic应用被切换到后台运行或MainActivity活动返回后可继续进行音乐播放,项目最终运行效果如所示。

图1. 音乐播放器3运行效果

步骤一,新建MusicService服务类

通过New Android Component对话框新建MusicService后,Android Studio会在GGMusic的AndroidManifest.xml配置清单文件中注册MusicService组件,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.glriverside.xgqin.ggmusic">

    <uses-permission 
        android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application
        ...>
        <activity android:name=".MainActivity">
            ...
        </activity>
        <service android:name=".MusicService"
            android:enabled="true"
            android:exported="false">
        </service>
    </application>

</manifest>

步骤二,重载MusicService的onCreate()、onDestroy()方法

打开MusicService.java文件,默认会包含三个重载的方法:

  • onCreate(),服务被创建时执行的方法;

  • onDestroy(),服务结束时执行的方法;

  • onBind(),服务被绑定时执行的方法,在本项目中暂不对其进行讨论;

在本项目中,使用MusicService进行音乐播放,需要在MusicService服务中使用MediaPlayer对多媒体音乐文件进行操作。 对于MediaPlayer对象的管理,可在onCreate()、onDestroy()方法中实现其对象初始化及对象释放操作。 因此,在MusicService服务类中,首先定义名为mMediaPlayer的私有成员变量,在onCreate()方法中进行对象初始化操作,而在onDestroy()方法中则进行对象释放操作。

public class MusicService extends Service {

    ...
    MediaPlayer mMediaPlayer;

    public MusicService() {
    }

    @Override
    public void onDestroy() {
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;
        super.onDestroy();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mMediaPlayer = new MediaPlayer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

步骤三,重载MusicService的onStartCommand方法

服务可以在其他组件中进行调用,通常以startService()方法的形式进行实现。该方法接收一个Intent类型的参数,包含所需启动服务的信息及传递给服务的数据。而服务中的onStartCommand()方法则是用于响应startService()方法的回调方法,该方法包含的参数为:

  • Intent intent,由startService()方法传递过来的Intent对象,可以使用它获取其他组件传递过来的数据;

  • int flags,与特定服务请求相关的额外数据,其值是0或是STARTFLAG REDELIVERY或STAR_FLAG_RETRY的组合;

  • int startId,唯一的整型id,表示特定的一次startService()请求,可以在stopSelf(int)中使用,指定停止特定的一次服务;

在本项目中,通过修改ListView.OnItemClickListener接口中onItemClick()的回调方法,调用startService()启动MusicService活动,并传递所需播放的多媒体音乐文件路径,供MusicService类中的MediaPlayer对象播放。

1. 修改ListView.OnItemClickListener接口

在该接口的onItemClick()方法中,不再直接操作MainActivity活动中的MediaPlayer对象播放音乐,而是通过构造Intent对象,启动MusicService活动的方式支持后台播放音乐,代码如下所示。

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener, ... {

     public static final String DATA_URI = 
             "com.glriverside.xgqin.ggmusic.DATA_URI"; 
    ...
    private ListView.OnItemClickListener itemClickListener =
            new ListView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
                View view, int i, long l) {
            Cursor cursor = mCursorAdapter.getCursor();
            if (cursor != null && cursor.moveToPosition(i)) {
                ...
                Uri dataUri = Uri.parse(data);

                 Intent serviceIntent = new Intent(MainActivity.this, 
                          MusicService.class); 
                 serviceIntent.putExtra(MainActivity.DATA_URI, data); 
                 startService(serviceIntent); 
                ...
            }
        }
    };
    ...
}

2. 重载MusicService服务类的onStartCommand方法

通过startService()方法中的Intent对象已经包含了需要播放的多媒体音乐文件的路径,因此可以在onStartCommand()方法中取出该值并构造对应的Uri对象,而播放音乐的代码则直接从ListView.OnItemClickListener的onItemClick()直接剪切过来,代码如下所示。

public class MusicService extends Service {
    ....

    @Override
    public int onStartCommand(Intent intent,
                            int flags, int startId) {
        String data = intent.getStringExtra(
                            MainActivity.DATA_URI);
        Uri dataUri = Uri.parse(data);

        if (mMediaPlayer != null) {
            try {
                mMediaPlayer.reset();
                mMediaPlayer.setDataSource(
                        getApplicationContext(), 
                        dataUri);
                mMediaPlayer.prepare();
                mMediaPlayer.start();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        return super.onStartCommand(intent, flags, startId);
    }
}

步骤四,编译项目运行App

完成上述步骤后,可以编译GGMusic项目并运行App,点击某一首音乐并通过返回按钮退出GGMusic应用或者点击home按键退出到主屏幕,此时音乐应可在后台播放。

但在音乐未播放完时会出现中断播放的情况,这是由于MusicService服务虽然在后台运行,但仍然处在App的主线程中,而此时App处于后台运行,仍会被Android系统杀掉。解决此问题的办法有:

  • 在MusicService中启动子线程,在子线程中实现音乐播放操作;

  • 使用Foreground Service的形式运行MusicService,确保服务可长期运行;

步骤五,使用Foreground Service形式运行MusicService

Foreground Service也称为前台服务,与普通服务不同的是,前台服务对App使用者来说是可感知的。前台服务通常与Notification结合使用,当启动一个前台服务时,需要给用户发送一个Notification,用户通过该Notification可对服务进行控制。

对于调用前台服务的组件而言,只要使用startForegroundService()方法即可启动前台服务,但服务在响应该方法的onStartCommand()方法中需要自行创建一个Notification对象,并调用startForeground()方法显式的作为前台服务运行。

1. 修改ListView.OnItemClickListener接口的onItemClick()回调函数

将onItemClick()回调函数中的startService()方法,替换为startForegroundService()方法。由于MusicService要以前台服务形式运行,我们还需要通过Intent对象传递当前播放的歌曲名、歌手名等信息给MusicService,用于在Notification中显示当前播放的歌曲信息,具体代码如下所示。

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener, ... {

     public static final String DATA_URI = 
             "com.glriverside.xgqin.ggmusic.DATA_URI"; 
     public static final String TITLE = 
             "com.glriverside.xgqin.ggmusic.TITLE"; 
     public static final String ARTIST = 
             "com.glriverside.xgqin.ggmusic.ARTIST"; 

    ...
    private ListView.OnItemClickListener itemClickListener =
            new ListView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
                View view, int i, long l) {
            Cursor cursor = mCursorAdapter.getCursor();
            if (cursor != null && cursor.moveToPosition(i)) {
                ...
                Uri dataUri = Uri.parse(data);

                 Intent serviceIntent = new Intent(MainActivity.this, 
                          MusicService.class); 
                 serviceIntent.putExtra(MainActivity.DATA_URI, data); 
                 serviceIntent.putExtra(MainActivity.TITLE, title); 
                 serviceIntent.putExtra(MainActivity.ARTIST, artist); 
                 startService(serviceIntent); 
                ...
            }
        }
    };
    ...
}

2. 修改MusicService的onStartCommand()方法

在onStartCommand()方法中,调用startForeground()方法将当前服务以前台服务形式运行,该方法需要两个参数:

  • int id,表示Notification的id,通常需要保存该id以便后续通过该id更新Notification;

  • Notification notification,Notification对象,表示通知实体;

因此在调用startForeground()方法前,需要构造Notification对象,可通过NotificationCompat.Builder静态类进行构造Notification对象。而在Android Oreo版本以上,创建Notification对象时需指定其所属的Notification Channel,因此还需要首先构造Notification Channel。

A 通过NotificationManager构造Notification Channel。首先通过Build.VERSION.SDK_INT可以判断当前系统的API版本,如果其大于Build.VERSION_CODES.O则表明需要构造Notification Channel。

B 通过NotificationCompat.Builder构造Notification对象。NotificationCompat.Builder提供了构造一个通知所需的所有方法,常用的方法包括:

  • setContentTitle(),设置Notification的标题,在此我们将当前播放的歌曲名作为标题;

  • setContentText(),设置Notification的正文文本,在此我们将当前播放的歌手名作为正文文本;

  • setSmallIcon(),设置Notification在系统状态栏上显示的图标;

  • setContentIntent(),设置用户点击通知时的Pending Intent(延迟意图);

其中setContentIntent()为通知设置延迟意图,当用户点击该通知时,通常希望能查看通知详情。在本项目中由于只有一个Activity,因此我们将该延迟意图设为打开MainActivity活动即可。延迟意图在Intent的基础上进行了封装,通过PendingIntent.geActivity()方法构造一个启动活动的延迟意图,具体代码如下所示。

C 通过startForeground()方法将当前的Service与Notification绑定,并以前台服务形式运行。

D 重新编译GGMusic项目并运行其App,点击ListView中某一首歌曲,你将会看到在系统状态栏中出现一个通知,该通知中显示了当前播放的音乐信息,点击该通知可以跳转至MainActivity活动页面。

public class MusicService extends Service {

    private static final int ONGOING_NOTIFICATION_ID = 1001;
    private static final String CHANNEL_ID = "Music channel";
    NotificationManager mNotificationManager;
    ...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        ...

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mNotificationManager = 
                (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Music Channel", NotificationManager.IMPORTANCE_HIGH);

            if (mNotificationManager != null) {
                mNotificationManager.createNotificationChannel(channel);
            }
        }

        Intent notificationIntent = 
                new Intent(getApplicationContext(),
                       MainActivity.class);
        PendingIntent pendingIntent = 
                PendingIntent.getActivity(
                        getApplicationContext(),
                        0, notificationIntent, 0);

        NotificationCompat.Builder builder =
                new NotificationCompat.Builder(
                    getApplicationContext(),
                    CHANNEL_ID);

        Notification notification = builder
                .setContentTitle(title)
                .setContentText(artist)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentIntent(pendingIntent).build();

        startForeground(ONGOING_NOTIFICATION_ID, notification);

        return super.onStartCommand(intent, flags, startId);
    }
}

实验小结

通过本次实验,你应该掌握了如下知识内容:

  • 掌握如何使用Service运行后台任务;

  • 掌握如何在其他组件中启动Service;

  • 掌握使用Foreground Service运行前台服务;

打开GGMusic项目,在Project窗口中右击app文件夹,选择【New】->【Service】->【Service】菜单,在弹出的New Android Component对话框中输入MusicService作为类名,将取消勾选exported复选框,选中enabled复选框,点击【Finish】完成MusicService服务类的创建工作,具体如所示。

图2. New Android Component新建MusicService
图2. New Android Component新建MusicService
图1. 音乐播放器3运行效果