类微信界面AS开发(三)——Broadcast实现音乐盒

952 阅读8分钟

一:Broadcast、BroadcastReceiver是什么?

Broadcast(广播机制) 是一种广泛运用的应用程序之间 传输信息 的机制,而 BroadcastReceiver(广播接收器) 则是用于接收来自系统和应用的广播对并对其进行响应的组件,Android中我们要发送的广播内容是一个Intent,这个Intent中可以携带我们要传送的数据。

1. 先让我们来看广播的流程图:

2. 为什么要使用广播实现

现在的很多都是在线音乐盒,在我们点击播放一首歌曲的时候,要从服务器拿数据,然后播放,这其实不止一个流程,可以理解为多线程的,普通的顺序过程实现效果不好。所以我们专门做一个activity来控制音乐的播放,音乐盒界面只实现用户点击动作。

二:修改原有布局文件tab03.xml

这里我们分为三个部分:歌曲封面、歌曲信息、控制界面,全部采用线性布局。

这里有一个小的地方要注意:我们的屏幕大小是一定的,当歌曲名过于长时,我们需要使它先走马灯一样循环出现(水平方向)。

android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"

这两句代码的作用就是设置“跑马灯”模式且一直循环下去。

全部的布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/cover"
        android:layout_width="match_parent"
        android:layout_height="600dp"
        android:layout_weight="1"
        android:src="@drawable/isu" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="20dp"
            android:layout_weight="1"
            android:ellipsize="marquee"
            android:marqueeRepeatLimit="marquee_forever"
            android:text="歌曲名"
            android:gravity="center"
            android:textColor="#000000"
            android:textSize="30dp" />

        <TextView
            android:id="@+id/author"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="艺术家"
            android:textSize="20dp" />
    </LinearLayout>


    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:gravity="center"
        android:layout_marginBottom="10dp">
        <ImageButton
            android:id="@+id/pre"
            android:layout_width="wrap_content"

            android:layout_height="wrap_content"
            android:src="@drawable/pre"
            android:background="#FFFFFF"/>
        <ImageButton
            android:id="@+id/play"
            android:layout_width="wrap_content"

            android:layout_height="wrap_content"
            android:src="@drawable/play"
            android:background="#FFFFFF"/>
        <ImageButton
            android:id="@+id/next"
            android:layout_width="wrap_content"

            android:layout_height="wrap_content"
            android:src="@drawable/next"
            android:background="#FFFFFF"/>
        <!--停止在这不太好看,将其宽度设置为0,先隐藏起来-->
        <ImageButton
            android:id="@+id/stop"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:src="@drawable/stop"
            android:background="#FFFFFF"/>
    </LinearLayout>
</LinearLayout>

效果如下所示:

三:在原有的Fragment上修改代码实现音乐盒界面的点击功能

  1. 首先实现接口OnClickListener,然后生成重写函数onclick生成大致的框架。

  2. 接着,声明刚刚控件,代码如下:

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;
    
    // 获取界面中显示歌曲标题、作者文本框
    TextView title, author;
    // 播放/暂停、停止按钮
    ImageButton play, stop;
    // 上一首、下一首按钮
    ImageButton pre, next;
    // 获取封面
    ImageView cover;
    
    ActivityReceiver activityReceiver;
    public static final String CTL_ACTION =
            "org.zh.action.CTL_ACTION";
    public static final String UPDATE_ACTION =
            "org.zh.action.UPDATE_ACTION";
    
    // 定义音乐的播放状态,0x11代表没有播放;0x12代表正在播放;0x13代表暂停
    int status = 0x11;
    
    String[] titleStrs = new String[]{"I See You.mp3", "官方回答.mp3", "麻醉师.mp3", "奇妙世界.mp3"};
    String[] authorStrs = new String[]{"Missio", "O.WEN", "胡睿", "刘思鉴"};
    Integer[] covers = new Integer[]{R.drawable.isu, R.drawable.answer, R.drawable.docter, R.drawable.word};
    
  3. 声明完控件之后当然是匹配各个控件,并为每个控件添加相应的监听。

    !注意:与Activity不同,在Fragment里,匹配各个控件是在OncreateView中实现,不是OnCreate!!!

    代码如下:

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.tab03, container, false);
        // 监听需要添加在onCreateView()中,而非onCreate()
        // 匹配对应的控件
        play = view.findViewById(R.id.play);
        stop = view.findViewById(R.id.stop);
        title = view.findViewById(R.id.title);
        author = view.findViewById(R.id.author);
        cover = view.findViewById(R.id.cover);
        pre = view.findViewById(R.id.pre);
        next = view.findViewById(R.id.next);
    
        // 添加监听
        play.setOnClickListener(this);
        stop.setOnClickListener(this);
    
        // 上一首,下一首添加监听器
        pre.setOnClickListener(this);
        next.setOnClickListener(this);
    
    
        return view;
    }
    
  4. 现在各控件已经推荐了监听器,下面就是实现之前接口产生的重载函数OnClick(对不同的控件活动做出相应的动作——>发送广播到Service组件)

    代码如下:

     @Override
    public void onClick(View view) {
        // 创建Intent
        Intent intent = new Intent(CTL_ACTION);
        switch (view.getId()) {
            // 按下按钮
            case R.id.play:
                intent.putExtra("control", 1);
                break;
            case R.id.stop:
                intent.putExtra("control", 2);
                break;
            case R.id.pre:
                intent.putExtra("control", 3);
                break;
            case R.id.next:
                intent.putExtra("control", 4);
                break;
        }
        //发送广播,将被Service 组件的BroadcastReceiver 接收到
        getActivity().sendBroadcast(intent);
    }
    
  5. 既然有发送广播,当然也要能接收响应来自service的广播。这里才是在onCreate函数里实现,不要与Activity里的实现搞混了

    接收广播,我们需要一个IntentFilter(意图过滤器),并且指定广播接收器监听的Action。这里我们采用动态注册的方式注册。 关于广播的两种注册后文会讲到的,请耐心阅读。

    代码如下:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    
        activityReceiver = new ActivityReceiver();
    
        // 创建IntentFilter 事件过滤器
        IntentFilter filter = new IntentFilter();
        // 指定BroadcastReceiver 监听的Action
        filter.addAction(UPDATE_ACTION);
        // 注册BroadcastReceiver
        getActivity().getApplicationContext().registerReceiver(activityReceiver, filter);
    
        Intent intent = new Intent(getActivity(), MusicService.class);
        // 启动后台Service
        getActivity().startService(intent);
    }
    
    // 自定义BroadcastReceiver,负责监听从service传回来的广播
    public class ActivityReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            // 获取Intent 的update 消息,update代表播放状态
            // 该方法中的 defaultValue 表示name对应的putExtra中没有传入有效的int类型值就将defaultValue的值作为默认值传入。
            int update = intent.getIntExtra("update", -1);
    
            // 获取Intent 中的current消息,表示当前播放歌曲
            int current = intent.getIntExtra("current", -1);
    
            if (current >= 0) {
                title.setText(titleStrs[current]);
                author.setText(authorStrs[current]);
                cover.setImageResource(covers[current]);
            }
    
            switch (update) {
                case 0x11:
                    play.setImageResource(R.drawable.play);
                    status = 0x11;
                    break;
                // 控制系统进入播放状态
                case 0x12:
                    // 播放状态下设置使用暂停图标
                    play.setImageResource(R.drawable.pause);
                    status = 0x12;
                    break;
                // 控制系统进入暂停状态
                case 0x13:
                    play.setImageResource(R.drawable.play);
                    status = 0x13;
                    break;
            }
        }
    }
    

四:创建assets资源文件夹

与res文件夹一样,assets文件夹同样是资源文件夹。但不同的是assets目录是Android的一种特殊目录,用于放置APP所需的固定文件,且该文件被打包到APK中时,不会被编码到二进制文件。

  1. assets目录不会被映射到R中,因此,资源无法通过R.id方式获取,必须要通过AssetManager进行操作与获取;res/raw目录下的资源会被映射到R中,可以通过getResource()方法获取资源。
  2. 多级目录:assets下可以有多级目录,res/raw下不可以有多级目录。
  3. 编码(都不会被编码):assets目录下资源不会被二进制编码;res/raw应该也不会被编码。 创建方法: main目录上右击: 直接在src/main下新建assets目录:

创建完后,就可以将mp3文件放入了。

五:在AndroidMainfest.xml文件里绑定服务

这里很重要!!一定要绑定服务.MusicService,绑定后,service组件才会起到作用。

代码如下:

 <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MusicService">
        </service>

六:创建MusicService.java文件

之前我们说过,Fragment里只是实现界面的点击动作并发送广播各service,而真正的音乐播放控制是由Service组件实现的。

因此,我们需要新建一个MusicService.java文件,由它来继承Service,实现音乐的播放功能。

代码如下:

public class MusicService extends Service {
    MyReceiver serviceReceiver;
    // AssetManager用于获取assets下的资源
    AssetManager am;
    String[] musics = new String[]{"I See You.mp3", "官方回答.mp3",
            "麻醉师.mp3", "奇妙世界.mp3"};
    MediaPlayer mPlayer;

    // 当前的状态,0x11代表没有播放;0x12代表正在播放;0x13代表暂停
    int status = 0x11;
    // 当前播放音乐
    int current = 0;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // 获取Assets 资源目录
        am = getAssets();

        // 创建BroadcastReceiver
        serviceReceiver = new MyReceiver();
        // 创建IntentFilter
        IntentFilter filter = new IntentFilter();
        filter.addAction(frdFragment.CTL_ACTION);
        registerReceiver(serviceReceiver, filter);

        // 创建MediaPlayer
        mPlayer = new MediaPlayer();
        // 为MediaPlayer 播放完成事件绑定监视器
        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                // 播放完后切换下一首
                current++;
                if (current >= 4)
                    current = 0;
                // 发送广播通知Activity 更改文本框
                Intent sendIntent = new Intent(frdFragment.UPDATE_ACTION);
                sendIntent.putExtra("current", current);
                // 发送广播,将Activity组件中的BroadcastReceiver接收到
                sendBroadcast(sendIntent);
                // 准备并播放音乐
                prepareAndPlay(musics[current]);
            }
        });
    }

    public class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(final Context context, Intent intent) {
            int control = intent.getIntExtra("control", -1);
            switch (control) {
                // 播放或暂停
                case 1:
                    // 原来处于没有播放状态
                    if (status == 0x11) {
                        // 准备并播放音乐
                        prepareAndPlay(musics[current]);
                        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;
                    }
                case 3:
                    current--;
                    if (current < 0)
                        current = 0;
                    prepareAndPlay(musics[current]);
                case 4:
                    current++;
                    if (current > musics.length - 1)
                        current = 0;
                    prepareAndPlay(musics[current]);
            }
            // 广播通知Activity更改图标、文本框
            Intent sendIntent = new Intent(frdFragment.UPDATE_ACTION);
            sendIntent.putExtra("update", status);
            sendIntent.putExtra("current", current);

            // 发送广播、将被Activity组件中的广播接收器接收到
            sendBroadcast(sendIntent);
        }
    }


    private void prepareAndPlay(String music) {
        try {
            // 打开指定音乐文件
            AssetFileDescriptor afd = am.openFd(music);
            mPlayer.reset();
            // 使用MediaPlayer 加载指定的mp3 文件
            mPlayer.setDataSource(afd.getFileDescriptor(),
                    afd.getStartOffset(), afd.getLength());
            // 异步准备音频文件
            mPlayer.prepare();
            // 播放
            //调用mediaPlayer的监听方法,音频准备完毕会响应此方法
            mPlayer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

七:实验结果展示

八:关于广播的两种注册方式的扩展补充

一. 注册广播

在android中,我们如果想接收到广播信息,必须自定义我们的广播接收者。要写一个类来继承BroadcastReceiver,并且重写其onReceive()方法,实现接收到特定广播所要做的事情。

这是一个自定义的广播接收者:

public class MyBroadCastReceiver extends BroadcastReceiver   
{  
   @Override  
   public void onReceive(Context context, Intent intent)   
   {   
       //在这里可以写相应的逻辑来实现一些功能
       //可以从Intent中获取数据、还可以调用BroadcastReceiver的getResultData()获取数据
   }   
} 

我们已经定义好了一个广播接收者。要想使用它接受到广播,就要注册这个广播接收者。

有两种方式注册广播:

(1)代码中动态注册

步骤如下:

  1. 实例化自定义的广播接收者
  2. 实例化意图过滤器,并设置要过滤的广播类型(如,我们接收收到短信系统发出的广播)
  3. 使用Context的registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)方法注册广播 代码:
//new出上边定义好的BroadcastReceiver
MyBroadCastReceiver yBroadCastReceiver = new MyBroadCastReceiver();

//实例化过滤器并设置要过滤的广播  
IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");

//注册广播   
myContext.registerReceiver(smsBroadCastReceiver,intentFilter, 
             "android.permission.RECEIVE_SMS", null); 

(2)在Manifest.xml中静态注册

直接在Manifest.xml文件的application 节点中配置广播接收者。

<receiver android:name=".MyBroadCastReceiver">  
            <!-- android:priority属性是设置此接收者的优先级(从-10001000) -->
            <intent-filter android:priority="20">
            <actionandroid:name="android.provider.Telephony.SMS_RECEIVED"/>  
            </intent-filter>  
</receiver>

还要在同级的位置配置可能使用到的权限。

<uses-permission android:name="android.permission.RECEIVE_SMS">
</uses-permission>

(3)两种注册广播的不同

  1. 第一种不是常驻型广播,也就是说广播跟随程序的生命周期。
  2. 第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

参考博客: Android 两种注册、发送广播的区别

源码地址: gitee.com/White-9/Lik…

ps:博客写的略显粗糙,如有错误,还望指正。(如果解决了你的一些问题,点个赞呗)

这好吗?这很好