Android插件化开发指南——2.15 实现一个音乐播放器APP

1. 前言

最近对Android插件化开发比较感兴趣,也读了部分《Android插件化开发指南》这本书。在该书中1.4部分介绍了这么一句话:

我们曾天真地认为,Android插件化是为了增加新的功能,或者增加一个完整的模块。费了不少时间和精力,等项目实施了插件化后,我们才发现,插件化80%的使用场景,是为了修复线上bug

我现在也粗浅的认为插件化是为了新增新的功能,至于修复线上bug这部分,确实还没有接触到或者说了解。希望后续自己能了解更多。

对于《Android插件化开发指南》这本书,我决定将其消化吸收了再整理博客笔记,因为确实写的比较好,有很多地方值得学习和借鉴。在这篇博客中,就简单的将书中2.15的例子展示出来,看看值得学习的点。

2. 实现一个音乐播放器APP

代码作者包老师也给出了源码地址,分别是:

感兴趣的可以直接看作者提供的源码。

2.1 设计思路一(一个Service和两个BroadcastReceiver)

  • 音乐前台使用一个Activity来展示歌曲信息;
  • 音乐后台播放,一般使用Service来实现;
  • 用户交互至少需要提供开始和暂停按钮,无论点击那个按钮都是通知后台Service播放或者停止音乐;
  • 当前音乐播放完毕,自动切换到下一首的时候,需要通知前台Activity

所以这个案例中至少需要一个Service和两个BroadcastReceiver

首先是在MainActivity中简单定义两个按钮,并为这两个按钮提供发送广播(发送广播到Service)的方法。

btnPlay.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View v) {
    
    
        //send message to receiver in Service
        Intent intent = new Intent("UpdateService");  // 传入Action
        intent.putExtra("command", 1);
        sendBroadcast(intent);
    }
});

btnStop.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View v) {
    
    
        //send message to receiver in Service
        Intent intent = new Intent("UpdateService");  // 传入Action
        intent.putExtra("command", 2);
        sendBroadcast(intent);
    }
});

同时还需要提供一个BroadcastReceiver用来接受从Service发来的播放结束的广播消息。

// 用来接受Service发来的消息
public class Receiver1 extends BroadcastReceiver {
    
    
    @Override
    public void onReceive(Context context, Intent intent) {
    
    
        status = intent.getIntExtra("status", -1);
        int current = intent.getIntExtra("current", -1);
        if (current >= 0) {
    
    
            tvTitle.setText(MyMusics.musics[current].title);
            tvAuthor.setText(MyMusics.musics[current].author);
        }

        switch (status) {
    
    
            case 0x11:
                btnPlay.setImageResource(R.drawable.play);
                break;
            case 0x12:
                btnPlay.setImageResource(R.drawable.pause);
                break;
            case 0x13:
                btnPlay.setImageResource(R.drawable.play);
                break;
            default:
                break;
        }
    }
}

同时还需要在onCreate方法后面完成对上面Receiver1的注册:

receiver1 = new Receiver1();
IntentFilter filter = new IntentFilter();
filter.addAction("UpdateActivity"); // 可以收到action=UpdateActivity
registerReceiver(receiver1, filter);

因为在播放按钮中我们并没有启动后台Service,所以还需要在onCreate的后面完成:

Intent intent = new Intent(this, MyService.class);
startService(intent);

对于启动的MyService服务,播放完毕后需要发送广播到Activity

mPlayer = new MediaPlayer();
mPlayer.setOnCompletionListener(new OnCompletionListener() {
    
    
    @Override
    public void onCompletion(MediaPlayer mp) {
    
    
        current++;
        if (current >= 3) {
    
    
            current = 0;
        }
        prepareAndPlay(MyMusics.musics[current].name);

        //send message to receiver in Activity
        Intent sendIntent = new Intent("UpdateActivity");
        sendIntent.putExtra("status", -1);
        sendIntent.putExtra("current", current);
        sendBroadcast(sendIntent);
    }
});

为了接收从Activity中传入的信号(播放或者暂停),这里定义一个接收器:

public class Receiver2 extends BroadcastReceiver {
    
    
    @Override
    public void onReceive(final Context context, Intent intent) {
    
    
        int command = intent.getIntExtra("command", -1);
        switch (command) {
    
    
            case 1:
                if (status == 0x11) {
    
    
                    prepareAndPlay(MyMusics.musics[current].name);
                    status = 0x12;
                }
                else if (status == 0x12) {
    
    
                    mPlayer.pause();
                    status = 0x13;
                }
                else if (status == 0x13) {
    
    
                    mPlayer.start();
                    status = 0x12;
                }
                break;
            case 2:
                if (status == 0x12 || status == 0x13) {
    
    
                    mPlayer.stop();
                    status = 0x11;
                }
        }

        //send message to receiver in Activity
        Intent sendIntent = new Intent("UpdateActivity");
        sendIntent.putExtra("status", status);
        sendIntent.putExtra("current", current);
        sendBroadcast(sendIntent);
    }
}

让媒体播放器执行相应的方法,并把状态更新,传递到Activity

当然这个Receiver2也需要在这个MyService中注册,为:

receiver2 = new Receiver2();
IntentFilter filter = new IntentFilter();
filter.addAction("UpdateService");
registerReceiver(receiver2, filter);

2.2 设计思路二(一个Service和一个BroadcastReceiver)

上面的逻辑比较清晰,就是A(Activity)B(Service)两个组件直接各自注册一个用来接收对方广播的接收器,然后收到消息后进行对应的逻辑处理。上面的流程可以简化为如下的图:
在这里插入图片描述

其实,很多音乐播放器只有一个Receiver,这是怎么实现的呢?

因为从上图可以看出,MyActivityMyService的代码耦合度比较高,在学习设计模式中我们知道,为了解耦我们通常需要面向接口编程,而不是具体的实现。所以在《Android插件化开发指南》一书中作者使用了接口来进行解耦,从而替代从ActivityService中发送广播的过程。如下图所示:在这里插入图片描述
首先定义好接口:

public interface IServiceInterface {
    
    
    public void play();
    public void stop();
}

Activity中定义一个IServiceInterface的实例:

IServiceInterface myService = null;

ServiceConnection mConnection = new ServiceConnection() {
    
    
    public void onServiceConnected(ComponentName name, IBinder binder) {
    
    
        myService = (IServiceInterface) binder;
        Log.e("MainActivity", "onServiceConnected");
    }

    public void onServiceDisconnected(ComponentName name) {
    
    
        Log.e("MainActivity", "onServiceDisconnected");
    }
};

这里使用onBind方式,因为在ServiceConnection回调中可以得到一个IBinder对象,而这个对象是在ServiceonBind中返回的,所以我们可以返回一个实现了IServiceInterface接口的IBinder对象,那么就可以得到接口的实现。在Activity中对应的将按钮的响应事件定义为:

btnPlay.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View v) {
    
    
        myService.play();
    }
});

btnStop.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View v) {
    
    
        myService.stop();
    }
});

当然在Activity中对应的定义广播接收器不变,一样需要注册这个广播接收器,并在最后启动定义的MyService服务。和前面的保持一致,这里就不再给出。

对于MyService这个类,才是本次的重点,因为在这个类中需要提供在onBind之后的返回对象,而我们需要让这个对象是接口的实例,故而定义如下:

// MyService类的内部类
private class MyBinder extends Binder implements IServiceInterface {
    
    

    @Override
    public void play() {
    
    
        if (status == 0x11) {
    
    
            prepareAndPlay(MyMusics.musics[current].name);
            status = 0x12;
        } else if (status == 0x12) {
    
    
            mPlayer.pause();
            status = 0x13;
        } else if (status == 0x13) {
    
    
            mPlayer.start();
            status = 0x12;
        }

        sendMessageToActivity(status, current);
    }

    @Override
    public void stop() {
    
    
        if (status == 0x12 || status == 0x13) {
    
    
            mPlayer.stop();
            status = 0x11;
        }

        sendMessageToActivity(status, current);
    }
}

其余的和前面的保持一致,也就是在播放完毕后发送广播通知Activity

3.仿一个音乐播放器

从上面的两个案例中,感觉确实案例二更加优美。所以接下来继续尝试在这个案例剖析的基础上进行拓展。决定以酷狗音乐为例,来进行仿写试试。当然,写的过程将持续记录到这篇博客中。

Android启动页白屏/黑屏问题解决
仿酷狗音乐启动页——Activity转场效果


Thanks

  • 《Android插件化开发指南》

Guess you like

Origin blog.csdn.net/qq_26460841/article/details/120992273