我用的开发工具是AndroidStudio,我的手机是Android7.1.2,我的另一个测试手机是Android8.0. 整个项目完整代码放在文章末尾。
本项目已同步上传到我的GitHub:https://github.com/2604150210/JalMusic
进入公司的第一个任务就是写一个音乐播放器(给定时间是两个星期)
对我来说还挺难的?毕竟之前还从没写过这么复杂的APP呢,不过只有挑战自己不会的东西才有意义嘛?才能得到进步。
在第二周的时候,我突然明白了为啥来公司前两星期就让我做一个播放器了,因为一个音乐播放器就要用到好多知识,Android的四大组件全都用到了,还学习了怎样实现进程间通信和异步更新UI,以及Widget的使用。
我录制了三个视频,可是CSDN里面居然不支持上传视频,唉唉唉???我决定把演示视频和代码放到压缩包里一起放在文末链接中。
Activity是对用户可见的UI界面,用户与应用程序都是通过Activity来进行交互的。Activity的生命周期如下:
我的这个音乐播放器项目中只用到了两个Activity,因为我的APP比较简单,只有两个页面,一个是音乐列表MainActivity,一个是详情页DetailActivity。其中onCreate()方法是Activity的入口方法,每一个Activity的实例创建时都会调用onCreate()方法,在这个方法中我们可以做一些初始化操作,如申请权限,初始化UI等。
我在MainActivity初始化的过程中,还不争气地入了坑,由于我刚开始是用我的手机测试的,我的手机是不需要动态获取权限的,直接在manifest中写的,但是在Android8.0的手机上运行却会闪退,是因为Android8.0需要动态获取权限,而我在MainActivity中调用了MusicList类中的静态方法getMusicData(context),这个方法是用来读取手机里面的mp3文件的,但是我刚开始没有动态申请权限,所以在8.0的手机上就闪退了。
Activity的UI初始化和申请权限是两个异步的事件,如果你的UI必须依赖于成功申请权限的话,建议你把UI初始化语句写在成功申请权限后的回调函数onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
中。 如我的UI显示依赖获取READ_EXTERNAL_STORAGE
权限,要不然就无法读取到手机的mp3文件,主页面就是空白了。
我的初始化UI的代码都写在了initView()中了,initView()在申请权限成功时得到调用。
由于我的MainActivity刚打开的时候,如果没有打开的歌曲则底部不显示东西,如果有已暂停或正在播放的歌曲,则在底部显示当前打开的歌曲名,所以我的MainActivity刚开始的时候如果点ListView其中的某一个Item进入到DetailActivity中后,当用户从DetailActivity返回出来时,MainActivity应该在底部显示刚才点击的歌曲,而歌曲列表还要显示当前正在播放的歌曲,所以ListView的布局就应该改变了。我在此处的处理是重新加载UI布局,也就是在onResume()中重新调用initView(),之所以在onResume()中调用initView是和Activity的生命周期有关的,由于退回到MainActivity中的时候,这个页面重新被激活,应该是从onStop()->onRestart()->onStart()->onPause(),而不经过onCreate()了,所以写在onCreate中是不可能会重新初始化UI的,而写在onPause()中的话,每次进入音乐列表都会先初始化UI,通过判断当前有无打开的歌曲来决定调用哪一套Item布局。
package com.jal.www.jalmusic;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ListView listView;
private LinearLayout cur_music;
private TextView tv_main_title;
private ArrayList<Music> listMusic;
private String TAG = "MainActivityLog";
private MyReceiver myReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myReceiver = new MyReceiver(new Handler());
IntentFilter itFilter = new IntentFilter();
itFilter.addAction(MusicService.MAIN_UPDATE_UI);
registerReceiver(myReceiver, itFilter);
requestPermission();
}
private void initView() {
listView = this.findViewById(R.id.listView1);
listMusic = MusicList.getMusicData(getApplicationContext());
Log.i(TAG, "listMusic.size()=="+listMusic.size());
MusicAdapter adapter = new MusicAdapter(this, listMusic);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("listMusic",listMusic);
bundle.putInt("position", position);
Intent intent = new Intent();
intent.putExtras(bundle);
intent.setClass(MainActivity.this, DetailsActivity.class);
startActivity(intent);
}
});
cur_music = findViewById(R.id.cur_music);
tv_main_title = findViewById(R.id.tv_main_title);
if(MusicService.mlastPlayer != null){
tv_main_title.setText(listMusic.get(MusicService.mPosition).getName());
}
}
private class MyReceiver extends BroadcastReceiver {
private final Handler handler;
// Handler used to execute code on the UI thread
public MyReceiver(Handler handler) {
this.handler = handler;
}
@Override
public void onReceive(final Context context, final Intent intent) {
// Post the UI updating code to our Handler
handler.post(new Runnable() {
@Override
public void run() {
initView();
}
});
}
}
@Override
protected void onResume() {
initView();
if (MusicService.mlastPlayer != null){
cur_music.setVisibility(View.VISIBLE);
tv_main_title = findViewById(R.id.tv_main_title);
tv_main_title.setText(listMusic.get(MusicService.mPosition).getName());
cur_music.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle bundle = new Bundle();
int position = MusicService.mPosition;
bundle.putInt("position", position);
Intent intent = new Intent();
intent.putExtras(bundle);
intent.setClass(MainActivity.this, DetailsActivity.class);
startActivity(intent);
}
});
}else{
cur_music.setVisibility(View.GONE);
}
super.onResume();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0){
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
if (grantResult == PackageManager.PERMISSION_DENIED){
String s = permissions[i];
Toast.makeText(this,s+"权限被拒绝了",Toast.LENGTH_SHORT).show();
}else{
initView();
}
}
}
break;
default:
break;
}
}
private void requestPermission(){
List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if (ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()){
ActivityCompat.requestPermissions(this,permissionList.toArray(new String[permissionList.size()]),1);
}else {
initView();
}
}
}
package com.jal.www.jalmusic;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import java.util.ArrayList;
public class DetailsActivity extends AppCompatActivity implements View.OnClickListener {
private MyConnection conn;
private String TAG = "DetailsActivity";
private Button btn_pre;
private Button btn_play;
private Button btn_next;
private ImageView btn_return;
private SeekBar seekBar;
private MusicButton imageView;
private TextView tv_title,tv_cur_time,tv_total_time;
private MusicService.MyBinder musicControl;
private static final int UPDATE_UI = 0;
private ArrayList<Music> listMusic;
MyReceiver myReceiver;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UI:
updateUI();
break;
}
}
};
public DetailsActivity() {
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
listMusic = MusicList.getMusicData(this);
Intent intent = new Intent(this, MusicService.class);
Bundle bundle = getIntent().getExtras();
intent.putExtras(bundle);
conn = new MyConnection();
startService(intent);
bindService(intent, conn, BIND_AUTO_CREATE);
myReceiver = new MyReceiver(new Handler());
IntentFilter itFilter = new IntentFilter();
itFilter.addAction(MusicService.MAIN_UPDATE_UI);
getApplicationContext().registerReceiver(myReceiver, itFilter);
bindViews();
//Mixed mode binding service
}
public class MyReceiver extends BroadcastReceiver {
private final Handler handler;
public MyReceiver(Handler handler) {
this.handler = handler;
}
@Override
public void onReceive(final Context context, final Intent intent) {
// Post the UI updating code to our Handler
handler.post(new Runnable() {
@Override
public void run() {
int play_pause = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_BTN, -1);
int songid = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_TEXT, -1);
tv_title.setText(listMusic.get(songid).getName());
switch (play_pause) {
case MusicService.VAL_UPDATE_UI_PLAY:
btn_play.setText(R.string.pause);
imageView.play();
break;
case MusicService.VAL_UPDATE_UI_PAUSE:
btn_play.setText(R.string.play);
imageView.pause();
break;
default:
break;
}
}
});
}
}
private void bindViews() {
btn_pre = findViewById(R.id.btn_pre);
btn_play = findViewById(R.id.btn_play);
btn_next = findViewById(R.id.btn_next);
btn_return = findViewById(R.id.btn_return);
seekBar = findViewById(R.id.sb);
tv_title = findViewById(R.id.tv_title);
tv_cur_time =findViewById(R.id.tv_cur_time);
tv_total_time = findViewById(R.id.tv_total_time);
imageView = findViewById(R.id.imageview);
btn_pre.setOnClickListener(this);
btn_play.setOnClickListener(this);
btn_next.setOnClickListener(this);
imageView.setOnClickListener(this);
btn_return.setOnClickListener(this);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//Progress bar change
if (fromUser) {
musicControl.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//Start touching the progress bar
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//Stop touching the progress bar
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_play:
case R.id.imageview:
play(v);
break;
case R.id.btn_next:
next(v);
break;
case R.id.btn_pre:
pre(v);
break;
case R.id.btn_return:
finish();
break;
}
}
private class MyConnection implements ServiceConnection {
//This method will be entered after the service is started.
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "::MyConnection::onServiceConnected");
//Get MyBinder in service
musicControl = (MusicService.MyBinder) service;
//Update button text
updatePlayText();
updateUI();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "::MyConnection::onServiceDisconnected");
}
}
@Override
protected void onResume() {
super.onResume();
//Start the update UI bar after entering the interface
if (musicControl != null) {
handler.sendEmptyMessage(UPDATE_UI);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//Unbind from the service after exiting
unbindService(conn);
getApplicationContext().unregisterReceiver(myReceiver);
}
@Override
protected void onStop() {
super.onStop();
//Stop the progress of the update progress bar
handler.removeCallbacksAndMessages(null);
}
//Update progress bar
private void updateProgress() {
int currenPostion = musicControl.getCurrenPostion();
seekBar.setProgress(currenPostion);
}
//Update button text
public void updatePlayText() {
if(MusicService.mlastPlayer!=null &&MusicService.mlastPlayer.isPlaying()){
imageView.play();
btn_play.setText(R.string.pause);
}else{
imageView.pause();
btn_play.setText(R.string.play);
}
}
public void play(View view) {
Intent intent = new Intent(MusicService.ACTION);
Bundle bundle = new Bundle();
bundle.putInt(MusicService.KEY_USR_ACTION,MusicService.ACTION_PLAY_PAUSE);
intent.putExtras(bundle);
sendBroadcast(intent);
updatePlayText();
}
public void next(View view) {
Intent intent = new Intent(MusicService.ACTION);
Bundle bundle = new Bundle();
bundle.putInt(MusicService.KEY_USR_ACTION,MusicService.ACTION_NEXT);
intent.putExtras(bundle);
sendBroadcast(intent);
updatePlayText();
}
public void pre(View view) {
Intent intent = new Intent(MusicService.ACTION);
Bundle bundle = new Bundle();
bundle.putInt(MusicService.KEY_USR_ACTION,MusicService.ACTION_PRE);
intent.putExtras(bundle);
sendBroadcast(intent);
updatePlayText();
}
public void updateUI(){
//Set the maximum value of the progress bar
int cur_time = musicControl.getCurrenPostion(), total_time = musicControl.getDuration();
seekBar.setMax(total_time);
//Set the progress of the progress bar
seekBar.setProgress(cur_time);
String str = musicControl.getName();
tv_title.setText(str);
tv_cur_time.setText(timeToString(cur_time));
tv_total_time.setText(timeToString(total_time));
updateProgress();
//Update the UI bar every 500 milliseconds using Handler
handler.sendEmptyMessageDelayed(UPDATE_UI, 500);
}
private String timeToString(int time) {
time /= 1000;
return String.format("%02d:%02d",time/60,time%60);
}
}
Service是用来处理一些后台程序的,对用户不可见,当用户关闭了APP页面后,有些还需要继续执行的任务不希望被关闭就可以交给Service来处理。我的项目中的MusicService就是继承了Service类,Service有两种使用方式,
我是用启动方式StartService()和绑定方式BindService()在进入DetailActivity时候就启动MusicService,由于是先用了StartService()来启动了Service,然后再用BindService()绑定Service,所以当DetailActivity生命周期结束时,这个Service并不会结束,而是一直是启动着的。此外,我还在MusicService刚刚启动的时候就注册了一个广播,为的是让它来接收到在其他页面点击了上一曲、下一曲、暂停、播放等按钮时,来做相应的处理,因此,其他页面中点击上一曲、下一曲、暂停、播放按钮时都需要像MusicService发送广播通知,让它来更新音乐。
package com.jal.www.jalmusic;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
import android.widget.Toast;
import java.io.IOException;
import java.util.ArrayList;
public class MusicService extends Service {
static MediaPlayer mlastPlayer;
static int mPosition;
private int position;
private String path = "";
private String TAG = "MusicServiceLog";
private MediaPlayer player;
private Music music;
private ArrayList<Music>listMusic;
private Context context;
private RemoteViews remoteView;
private Notification notification;
private String notificationChannelID = "1";
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;
public static String MAIN_UPDATE_UI = "main_activity_update_ui"; //Action
public static String KEY_MAIN_ACTIVITY_UI_BTN = "main_activity_ui_btn_key";
public static String KEY_MAIN_ACTIVITY_UI_TEXT = "main_activity_ui_text_key";
public static final int VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2;
private int notifyId = 1;
@Override
public IBinder onBind(Intent intent) {
//When onCreate() is executed, onBind() will be executed to return the method of operating the music.
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
listMusic = MusicList.getMusicData(context);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION);
registerReceiver(receiver, intentFilter);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
initNotificationBar();
Bundle bundle = intent.getExtras();
position = bundle.getInt("position");
if (mlastPlayer == null || mPosition != position){
prepare();
}else{
player = mlastPlayer;
}
return super.onStartCommand(intent, flags, startId);
}
private void postState(Context context, int state,int songid) {
Intent actionIntent = new Intent(MusicService.MAIN_UPDATE_UI);
actionIntent.putExtra(MusicService.KEY_MAIN_ACTIVITY_UI_BTN,state);
actionIntent.putExtra(MusicService.KEY_MAIN_ACTIVITY_UI_TEXT, songid);
updateNotification();
context.sendBroadcast(actionIntent);
}
private void initNotificationBar(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
CharSequence name = "notification channel";
String description = "notification description";
int importance = NotificationManager.IMPORTANCE_MIN;
NotificationChannel mChannel = new NotificationChannel(notificationChannelID, name, importance);
mChannel.setDescription(description);
mChannel.setLightColor(Color.RED);
mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
mNotificationManager.createNotificationChannel(mChannel);
}
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, notificationChannelID);
Intent intent = new Intent(this, MainActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("position", mPosition);
intent.putExtras(bundle);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, intent, 0);
remoteView = new RemoteViews(getPackageName(),R.layout.notification);
String title = listMusic.get(MusicService.mPosition).getName();
Log.i(TAG, "updateNotification title = " + title);
remoteView.setTextViewText(R.id.notification_title, title);
remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(this, R.id.play_pause));
remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(this, R.id.prev_song));
remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(this, R.id.next_song));
if (MusicService.mlastPlayer != null && MusicService.mlastPlayer.isPlaying()) {
String s = getResources().getString(R.string.pause);
remoteView.setTextViewText(R.id.play_pause, s);
}else {
String s = getResources().getString(R.string.play);
remoteView.setTextViewText(R.id.play_pause, s);
}
mBuilder.setContentIntent(pendingIntent)
.setContent(remoteView)
.setWhen(System.currentTimeMillis())
.setOngoing(false)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setDefaults(Notification.DEFAULT_LIGHTS)
.setSmallIcon(R.mipmap.zjalmusic)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.jalmusic));
notification = mBuilder.build();
notification.flags = Notification.FLAG_ONGOING_EVENT;
NotificationManager manager = (NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
manager.notify(notifyId,notification);
updateNotification();
}
private void updateNotification() {
String title = listMusic.get(MusicService.mPosition).getName();
Log.i(TAG, "updateNotification title = " + title);
remoteView.setTextViewText(R.id.notification_title, title);
if (MusicService.mlastPlayer != null && MusicService.mlastPlayer.isPlaying()) {
String s = getResources().getString(R.string.pause);
remoteView.setTextViewText(R.id.play_pause, s);
}else {
String s = getResources().getString(R.string.play);
remoteView.setTextViewText(R.id.play_pause, s);
}
notification.contentView = remoteView;
NotificationManager manager = (NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
manager.notify(notifyId,notification);
}
private PendingIntent getPendingIntent(Context context, int buttonId) {
Intent intent = new Intent();
intent.setClass(context, JalMusicWidget.class);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
intent.setData(Uri.parse(""+buttonId));
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return pi;
}
void prepare(){
music = listMusic.get(position);
path = music.getUrl();
Log.i(TAG,"path:"+path);
player = new MediaPlayer();//This is only done once, used to prepare the player.
if (mlastPlayer !=null){
mlastPlayer.stop();
mlastPlayer.release();
}
mlastPlayer = player;
mPosition = position;
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
Log.i(TAG,path);
player.setDataSource(path); //Prepare resources
player.prepare();
player.start();
Log.i(TAG, "Ready to play music");
} catch (IOException e) {
Log.i(TAG,"ERROR");
e.printStackTrace();
}
postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
position +=1;
position = (position + listMusic.size())%listMusic.size();
music = listMusic.get(position);
Toast.makeText(context, "自动为您切换下一首:"+music.getName(), Toast.LENGTH_SHORT).show();
prepare();
}
});
}
//This method contains operations on music
public class MyBinder extends Binder {
public boolean isPlaying(){
return player.isPlaying();
}
public void play() {
if (player.isPlaying()) {
player.pause();
Log.i(TAG, "Play stop");
} else {
player.start();
Log.i(TAG, "Play start");
}
}
//Play the next music
public void next(int type){
mPosition +=type;
mPosition = (mPosition + listMusic.size())%listMusic.size();
music = listMusic.get(mPosition);
prepare();
}
//Returns the length of the music in milliseconds
public int getDuration(){
return player.getDuration();
}
//Return the name of the music
public String getName(){
return music.getName();
}
//Returns the current progress of the music in milliseconds
public int getCurrenPostion(){
return player.getCurrentPosition();
}
//Set the progress of music playback in milliseconds
public void seekTo(int mesc){
player.seekTo(mesc);
}
}
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:
next(-1);
Log.d(TAG,"action_prev");
break;
case ACTION_PLAY_PAUSE:
play();
break;
case ACTION_NEXT:
next(1);
Log.d(TAG,"action_next");
break;
default:
break;
}
}
}
};
public void play() {
if (player.isPlaying()) {
player.pause();
postState(getApplicationContext(), VAL_UPDATE_UI_PAUSE,position);
Log.i(TAG, "Play stop");
} else {
player.start();
postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
Log.i(TAG, "Play start");
}
}
//Play the next music
public void next(int type){
position +=type;
position = (position + listMusic.size())%listMusic.size();
music = listMusic.get(position);
prepare();
}
}
我还在MusicService中启动了一个通知栏,之所以要将通知栏放在MusicService中是因为通知栏本身就是为了让用户在退出APP的时候,还能看到后台歌曲的播放情况和便捷切换歌曲的。
我在此处又因为Android版本掉坑里了?????就是在new一个通知栏的时候,8.0以上的版本需要将Notification放到channel中,为的是让同一APP的通知都放在一起不对用户造成打扰。刚开始我用我的手机测试的时候,就是直接学习书本上的例子,顺利地开启了通知栏,但在8.0的手机上报错了,报错说找不到Channel,然后我上网查阅了之后才了解到了一些8.0以上的新特性,也相应的找到了修正,如上代码所示。
广播,继承了BroadcastReceiver的类必须要实现onReceiver方法,在这个方法中可以针对接收到的广播消息来做相应的处理。我的项目中很多地方都用到了广播,在用户点击下一曲、上一曲、播放、暂停的时候,都会发送广播,而广播的接受者是MusicService和DetailActivity以及Widget,前者更新音乐,后者更新UI显示。
Widget是桌面小挂件,是伴随着APP一起的一个小页面,想要创建一个Widget很简单,只需要继承AppWidgetProvider就可以,而AppWidgetProvider本身是一个广播,接收着Widget被创建的通知,也可以接收其他通知,如我的项目中歌曲的切换,然后由onReceiver方法来根据不同的通知,调用相应的方法。
package com.jal.www.jalmusic;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;
import java.util.ArrayList;
/**
* Implementation of App Widget functionality.
*/
public class JalMusicWidget extends AppWidgetProvider {
private boolean mStop = true;
private String TAG = "JalMusicWidgetLog";
private ArrayList<Music> listMusic;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
if (listMusic == null)
listMusic = MusicList.getMusicData(context);
pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(MusicService.mPosition).getName(),true);
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
if (listMusic == null)
listMusic = MusicList.getMusicData(context);
pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(MusicService.mPosition).getName(),true);
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
private void pushAction(Context context, int ACTION) {
Intent actionIntent = new Intent(MusicService.ACTION);
actionIntent.putExtra(MusicService.KEY_USR_ACTION, ACTION);
context.sendBroadcast(actionIntent);
}
public void onReceive(Context context, Intent intent) {
if (listMusic == null)
listMusic = MusicList.getMusicData(context);
String action = intent.getAction();
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,MusicService.ACTION_PLAY_PAUSE);
if(MusicService.mlastPlayer == null){
Intent startIntent = new Intent(context,MusicService.class);
Bundle bundle1 = new Bundle();
bundle1.putInt("position",0);
startIntent.putExtras(bundle1);
context.startService(startIntent);
}
break;
case R.id.prev_song:
pushAction(context, MusicService.ACTION_PRE);
break;
case R.id.next_song:
pushAction(context, MusicService.ACTION_NEXT);
break;
}
}else if (MusicService.MAIN_UPDATE_UI.equals(action)){
int play_pause = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_BTN, -1);
int songid = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_TEXT, -1);
switch (play_pause) {
case MusicService.VAL_UPDATE_UI_PLAY:
pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(songid).getName(),true);
break;
case MusicService.VAL_UPDATE_UI_PAUSE:
pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(songid).getName(),false);
break;
default:
break;
}
}
super.onReceive(context, intent);
}
private void pushUpdate(Context context,AppWidgetManager appWidgetManager,String songName,Boolean play_pause) {
RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.jal_music_widget);
remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(context, R.id.play_pause));
remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(context, R.id.prev_song));
remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(context, R.id.next_song));
//设置内容
if (!songName.equals("")) {
remoteView.setTextViewText(R.id.widget_title, songName);
}
//设定按钮图片
if (play_pause) {
String s = context.getResources().getString(R.string.pause);
remoteView.setTextViewText(R.id.play_pause, s);
// remoteView.setImageViewResource(R.id.play_pause, R.drawable.car_musiccard_pause);
}else {
String s = context.getResources().getString(R.string.play);
remoteView.setTextViewText(R.id.play_pause, s);
// remoteView.setImageViewResource(R.id.play_pause, R.drawable.car_musiccard_play);
}
ComponentName componentName = new ComponentName(context,JalMusicWidget.class);
appWidgetManager.updateAppWidget(componentName, remoteView);
}
private PendingIntent getPendingIntent(Context context, int buttonId) {
Intent intent = new Intent();
intent.setClass(context, JalMusicWidget.class);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
intent.setData(Uri.parse(""+buttonId));
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
return pi;
}
}
我的Widget和Notification中自定义的layout都是通过RemoteView来绑定更新布局的,因为Widget和Notification和其他Activity不是在工作在同一个进程中,Widget和Notification是工作在System进程的,不在主进程中我们就无法用findViewByID来绑定控件。但可以通过进程间通信来实现控件的绑定和UI的更新。RemoteView就是一种方便的进程通信,但他支持的布局和控件都有限。绑定布局和控件以及为按钮添加点击事件使用如下:
Android为了线程安全,不允许我们在UI线程外操作UI;很多时候我们做界面刷新都需要通过Handler来通知UI组件更新。
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理。
在我的项目中,我的进度条更新就是通过Hander来实现的,代码在DetailActivity中。
package com.jal.www.jalmusic;
import android.os.Parcel;
import android.os.Parcelable;
//The Music class implements the Parcelable interface and can be serialized.
public class Music implements Parcelable {
private String title;
private String singer;
private String album;
private String url;
private long size;
private long time;
private String name;
//Non-parametric construction
public Music(){};
// Deserialization
protected Music(Parcel in) {
title = in.readString();
singer = in.readString();
album = in.readString();
url = in.readString();
size = in.readLong();
time = in.readLong();
name = in.readString();
}
// Serialization
public static final Creator<Music> CREATOR = new Creator<Music>() {
@Override
public Music createFromParcel(Parcel in) {
return new Music(in);
}
@Override
public Music[] newArray(int size) {
return new Music[size];
}
};
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getSinger()
{
return singer;
}
public void setSinger(String singer)
{
this.singer = singer;
}
public String getAlbum()
{
return album;
}
public void setAlbum(String album)
{
this.album = album;
}
public String getUrl()
{
return url;
}
public void setUrl(String url)
{
this.url = url;
}
public long getSize()
{
return size;
}
public void setSize(long size)
{
this.size =size;
}
public long getTime()
{
return time;
}
public void setTime(long time)
{
this.time = time;
}
@Override
public String toString() {
return "Music{" +
"title='" + title + '\'' +
", singer='" + singer + '\'' +
", album='" + album + '\'' +
", url='" + url + '\'' +
", size=" + size +
", time=" + time +
", name='" + name + '\'' +
'}';
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Music music = (Music) o;
return size == music.size &&
time == music.time &&
title .equals( music.title)&&
album.equals(music.album)&&
url.equals(music.url)&&
name.equals(music.name);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(title);
dest.writeString(singer);
dest.writeString(album);
dest.writeString(url);
dest.writeLong(size);
dest.writeLong(time);
dest.writeString(name);
}
}
package com.jal.www.jalmusic;
import java.util.List;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MusicAdapter extends BaseAdapter {
private static final String TAG = "MusicAdapterLog";
private List<Music> listMusic;
private Context context;
public MusicAdapter(Context context, List<Music> listMusic) {
this.context = context;
this.listMusic = listMusic;
}
public void setListItem(List<Music> listMusic) {
this.listMusic = listMusic;
}
@Override
public int getCount() {
return listMusic.size();
}
@Override
public Object getItem(int arg0) {
return listMusic.get(arg0);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(
R.layout.music_item, null);
// }
if (MusicService.mlastPlayer != null && MusicService.mPosition == position){
// if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(
R.layout.music_item2, null);
// }
}
Music m = listMusic.get(position);
TextView textMusicName = (TextView) convertView
.findViewById(R.id.music_item_name);
textMusicName.setText(m.getName());
TextView textMusicSinger = (TextView) convertView
.findViewById(R.id.music_item_singer);
textMusicSinger.setText(m.getSinger());
TextView textMusicTime = (TextView) convertView
.findViewById(R.id.music_item_time);
textMusicTime.setText(toTime((int) m.getTime()));
if (MusicService.mlastPlayer != null && MusicService.mPosition == position){
TextView music_isPlay = convertView.findViewById(R.id.music_isPlay);
if (music_isPlay != null){
music_isPlay.setText(MusicService.mlastPlayer.isPlaying()?R.string.play:R.string.pause);
}
}
return convertView;
}
/**
* Time format conversion
*
* @param time
* @return
*/
public String toTime(int time) {
time /= 1000;
int minute = time / 60;
int hour = minute / 60;
int second = time % 60;
minute %= 60;
return String.format("%02d:%02d", minute, second);
}
}
package com.jal.www.jalmusic;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
public class MusicButton extends AppCompatImageView {
private ObjectAnimator objectAnimator;
public static final int STATE_PLAYING =1;
public static final int STATE_PAUSE =2;
public static final int STATE_STOP =3;
public static int state;
public MusicButton(Context context) {
super(context);
init();
}
public MusicButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MusicButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
state = STATE_STOP;
//Add a rotation animation, the rotation center defaults to the midpoint of the control
objectAnimator = ObjectAnimator.ofFloat(this, "rotation", 0f, 360f);
objectAnimator.setDuration(3000);//Set animation time
objectAnimator.setInterpolator(new LinearInterpolator());//Animation time linear gradient
objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
objectAnimator.setRepeatMode(ObjectAnimator.RESTART);
}
public void playMusic(){
if(state == STATE_STOP){
objectAnimator.start();
state = STATE_PLAYING;
}else if(state == STATE_PAUSE){
objectAnimator.resume();
state = STATE_PLAYING;
}else if(state == STATE_PLAYING){
objectAnimator.pause();
state = STATE_PAUSE;
}
}
public void play(){
if(objectAnimator.isPaused()){
System.out.println("isPaused");
objectAnimator.resume();
}else {
System.out.println("else");
objectAnimator.start();
}
}
public void pause(){
objectAnimator.pause();
}
public void stopMusic(){
objectAnimator.end();
state = STATE_STOP;
}
}
package com.jal.www.jalmusic;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;
import java.util.ArrayList;
public class MusicList {
public static ArrayList<Music> getMusicData(Context context) {
ArrayList<Music> musicList = new ArrayList<Music>();
ContentResolver cr = context.getContentResolver();
if (cr != null) {
// Get all the music
Cursor cursor = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
if (null == cursor) {
return null;
}
if (cursor.moveToFirst()) {
do {
Music m = new Music();
String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
String singer = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
if ("" .equals(singer)) {
singer = "未知艺术家";
}
String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
long size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
String url = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
String sbr = name.substring(name.length() - 3, name.length());
// Log.e("--------------", sbr);
if (sbr.equals("mp3")) {
m.setTitle(title);
m.setSinger(singer);
m.setAlbum(album);
m.setSize(size);
m.setTime(time);
m.setUrl(url);
m.setName(name);
musicList.add(m);
}
} while (cursor.moveToNext());
}
}
return musicList;
}
}
https://download.csdn.net/download/jal517486222/11086201
我觉得我的项目还有很多需要改进的地方,我会继续努力的,✧(≖ ◡ ≖✿)嘿嘿