好久没更新博客了,把之前写的一个小项目发出来吧。这几天也有小学弟经常问这个事情
一、项目整体架构
1、博主参考资料
2、项目下载链接
后续会更新GitHub下载地址
二、技术介绍
1、Activity
摘抄自网络---Activity介绍
可以参考博主的另一边文章-
Activity是对用户可见的UI界面,用户与应用程序都是通过Activity来进行交互的。
Activity的生命周期
我们在书写Activity的时候应该遵守Activity的生命周期,在合理的地方添加合理的代码。
下边是博主的MainActivity的代码,只放了一部分,大家可以去下载代码来仔细查看
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import com.example.applicationmusic.activity.MusicPlayPauseActivity;
import com.example.applicationmusic.model.Music;
import com.example.applicationmusic.service.MusicService;
import com.example.applicationmusic.util.MusicList;
import com.facebook.stetho.Stetho;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
//定义控件
private ListView listView; //定义List 用于存放当前显示信息
private ArrayList<Music> listMusic; //存放音乐数据
private ArrayList<HashMap<String, Object>> listItem; //在数组中存放数据
private SimpleAdapter mSimpleAdapter; //适配器
private int num = 0; //排序方式
private MyReceiver myReceiver; //主要适用于接收广播信息
static {
System.loadLibrary("native-lib");
}
//Activity启动时
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.music_list);
Stetho.initializeWithDefaults(this); //查看数据库
myReceiver = new MyReceiver(new Handler()); //新建一个接收类
//注册广播
IntentFilter itFilter = new IntentFilter();
itFilter.addAction(MusicService.MAIN_UPDATE_UI);
registerReceiver(myReceiver, itFilter);
requestPermission();
findViewById(R.id.artistBtn).setOnClickListener(this);
findViewById(R.id.titleBtn).setOnClickListener(this);
findViewById(R.id.genreBtn).setOnClickListener(this);
}
//onResume 回调时使用
@Override
protected void onResume() {
init();
super.onResume();
}
//初始化函数
public void init(){
//设置当前音乐列表
listView = findViewById(R.id.lv);
listItem = MusicList.getMusicMap(getApplicationContext(),num);
listMusic = MusicList.getMusicList(getApplicationContext(),num);
//new String 数据来源, new int 数据到哪去
mSimpleAdapter = new SimpleAdapter(this, listItem, R.layout.item_music,
new String[]{"textTitle", "textArtist", "textGenre"},
new int[]{R.id.textTitle, R.id.textArtist, R.id.textGenre});
listView.setAdapter(mSimpleAdapter); //为listview添加数据
//列表点击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Bundle bundle = new Bundle();
bundle.putInt("position",position);
bundle.putInt("num",num);
Intent intent = new Intent();
intent.putExtras(bundle);
intent.setClass(MainActivity.this, MusicPlayPauseActivity.class);
startActivity(intent);
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.titleBtn: num = 1;break;
case R.id.artistBtn: num = 2;break;
case R.id.genreBtn:num = 3;break;
}
//执行点击事件之后执行一遍init()方法 目的是为了实现listview的数据初始化
init();
}
//实现一个广播接收器----可以在其中的onReceive方法中去执行各自的方法
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() {
init();
}
});
}
}
//判断当前是否权限
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
init();
}else{
Toast.makeText(this, "拒绝权限,将无法使用程序。", Toast.LENGTH_LONG).show();
finish();
}
break;
default:
}
}
//从数据库中更新数据(插入信息)
public void addSqlLite(){
ArrayList<Music> arrayList = new ArrayList<>();
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music");
final File[] files = file.listFiles();
if (files == null) {
Log.e("error", "空目录");
} else {
for (int i = 0; i < files.length; i++) {
//设置存储音乐到数据库
String[] strings = ParseOne(files[i].getAbsolutePath());
Music music = new Music();
music.setTitle(strings[0]);
music.setArtist(strings[1]);
music.setGenre(strings[2]);
music.setPath(files[i].getAbsolutePath());
arrayList.add(music);
}
Context context = getApplicationContext();
MusicList.addSqlLite(context,arrayList);
}
}
//是否需要权限
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 {
init();
}
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String[] ParseOne(String input_);
@Override
protected void onDestroy() {
getApplicationContext().unregisterReceiver(myReceiver);
super.onDestroy();
}
}
2、Service
Service是用来处理一些后台程序的,对用户不可见,当用户关闭了APP页面后,有些还需要继续执行的任务不希望被关闭就可以交给Service来处理。
Service的生命周期
在使用Service的时候我们要通过startService来启动当前的Service,博主使用了StartService与BindService。这样在播放的Activity结束以后,我们的Service也会一直存在的。同时,Service中结合了广播,这样我们就可以随时接收到广播信息。
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.example.applicationmusic.R;
import com.example.applicationmusic.model.Music;
import com.example.applicationmusic.util.MusicList;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @name ApplicationMusic
* @class com.example.applicationmusic.service
* @description 提供音乐服务(主要包括音乐播放这块)
* @anthor yinchen
* @time 20-8-7 上午9:34
*/
public class MusicService extends Service {
public static MediaPlayer mlastPlayer;
public static int mPosition;
public static int mm;
private String TAG = "ChiHuoZhi-----Service:";
private Music music; //获得当前点击的音乐
private String path; //当前播放的音乐的路径
private MediaPlayer mediaPlayer; //定义mediaPlayer来播放音频
private AudioManager audioManager; //定时音乐管理器 主要是后期来获取焦点用的
private int position; //定义当前所在位置
private List<Music> listMusic; //定义当前音乐列表
private ArrayList<HashMap<String, Object>> listItem; //在数组中存放数据
private SimpleAdapter mSimpleAdapter; //适配器
private RemoteViews remoteView; //主要用来处理视图
private String notificationChannelID = "1";
private Context context;
private int num = 0;
public static int flag = 0;
private AudioManager mAudioManager;
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 String ARTIST = "";
public static final int VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2;
static {
System.loadLibrary("native-lib");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
//Service被创建的时候 --- 主要用来去实例化上述声明的东西
@Override
public void onCreate() {
Log.d(TAG, "onCreate: ");
super.onCreate();
//初始化AudioManager对象
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
remoteView = new RemoteViews(getPackageName(),R.layout.activity_main);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION);
registerReceiver(receiver, intentFilter);
Log.d(TAG, "onCreate: receiver:"+receiver);
flag = 1;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: ");
Bundle bundle = intent.getExtras();
num = bundle.getInt("num");
Log.d(TAG, "onStartCommand: "+mm+"@@@"+num);
position = bundle.getInt("position");
listMusic = MusicList.getMusicList(getApplicationContext(),num);
if (mlastPlayer == null || mPosition != position || mm != num){
init();
}else{
mediaPlayer = mlastPlayer;
}
return super.onStartCommand(intent, flags, startId);
}
//实时更新广播中的信息 相当于修改Service的值
private void postState(Context context, int state,int songid) {
Log.d(TAG, "postState: ");
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);
context.sendBroadcast(actionIntent);
}
public void init(){
Log.d(TAG, "init: ");
music = listMusic.get(position);
path = music.getPath();
Log.d(TAG, "init: path="+path);
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mediaPlayer = new MediaPlayer();
if (mlastPlayer !=null){
mlastPlayer.stop();
mlastPlayer.release();
}
mlastPlayer = mediaPlayer;
mPosition = position;
mm = num;
try {
Log.i(TAG,path);
remoteView.setTextViewText(R.id.artistText,music.getArtist());
mediaPlayer.setDataSource(path);
mediaPlayer.prepare();
mediaPlayer.start();
Log.i(TAG, "Ready to play music");
} catch (IOException e) {
Log.i(TAG,"ERROR");
e.printStackTrace();
}
postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
position +=1;
position = (position + listMusic.size())%listMusic.size();
music = listMusic.get(position);
Toast.makeText(getApplicationContext(), "自动为您切换下一首:"+music.getTitle(), Toast.LENGTH_SHORT).show();
init();
}
});
}
//This method contains operations on music
public class MyBinder extends Binder {
public boolean isPlaying(){
return mediaPlayer.isPlaying();
}
public void play() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
Log.i(TAG, "Play stop");
} else {
mediaPlayer.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);
init();
}
//Returns the length of the music in milliseconds
public int getDuration(){
return mediaPlayer.getDuration();
}
//Return the name of the music
public String getName(){
return music.getTitle();
}
//Return the name of the music
public String getArtist(){
return music.getArtist();
}
//Returns the current progress of the music in milliseconds
public int getCurrenPostion(){
return mediaPlayer.getCurrentPosition();
}
//Set the progress of music playback in milliseconds
public void seekTo(int mesc){
mediaPlayer.seekTo(mesc);
}
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Service->Receiver");
String action = intent.getAction();
if (ACTION.equals(action)) {
int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);
switch (widget_action) {
case ACTION_PRE:
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
next(-1);
Log.d(TAG,"action_prev");
break;
case ACTION_PLAY_PAUSE:
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
play();
break;
case ACTION_NEXT:
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
next(1);
Log.d(TAG,"action_next");
break;
default:
break;
}
}
}
};
/* MediaPlayer的播放方法*/
public void play() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
postState(getApplicationContext(), VAL_UPDATE_UI_PAUSE,position);
Log.i(TAG, "Play stop");
} else {
mediaPlayer.start();
postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
Log.i(TAG, "Play start");
}
}
public void pause(){
mediaPlayer.pause();
postState(getApplicationContext(), VAL_UPDATE_UI_PAUSE,position);
Log.i(TAG, "Play stop");
}
public void next(int type){
position +=type;
position = (position + listMusic.size())%listMusic.size();
music = listMusic.get(position);
init();
}
//从数据库中更新数据(插入信息)
public void addSqlLite(){
Log.d(TAG, "addSqlLite: ");
ArrayList<Music> arrayList = new ArrayList<>();
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music");
final File[] files = file.listFiles();
if (files == null) {
Log.e("error", "空目录");
} else {
for (int i = 0; i < files.length; i++) {
//设置存储音乐到数据库
String[] strings = ParseTwo(files[i].getAbsolutePath());
Music music = new Music();
music.setTitle(strings[0]);
music.setArtist(strings[1]);
music.setGenre(strings[2]);
music.setPath(files[i].getAbsolutePath());
arrayList.add(music);
}
context = getApplicationContext();
MusicList.addSqlLite(context,arrayList);
}
}
/**
* 焦点变化监听器
*/
private AudioManager.OnAudioFocusChangeListener mAudioFocusChange = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "onAudioFocusChange: "+focusChange);
switch (focusChange){
case AudioManager.AUDIOFOCUS_LOSS:
//长时间丢失焦点
Log.d(TAG, "AUDIOFOCUS_LOSS");
pause();
//释放焦点
mAudioManager.abandonAudioFocus(mAudioFocusChange);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
//短暂性丢失焦点
pause();
Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT");
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
//短暂性丢失焦点并作降音处理
Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
break;
case AudioManager.AUDIOFOCUS_GAIN:
//重新获得焦点
Log.d(TAG, "AUDIOFOCUS_GAIN");
play();
break;
}
}
};
//注册广播时间与注销广播 放到一个方法里
private void bord(){
if(flag == 0){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION);
registerReceiver(receiver, intentFilter);
flag = 1;
}
}
public native String[] ParseTwo(String input);
@Override
public void onDestroy() {
if(flag == 1){
if(receiver != null) getApplicationContext().unregisterReceiver(receiver);
}
super.onDestroy();
}
}
3、BroadcastReceiver
概念:Android四大组件之一,没有可视化界面,用于不同组件和多线程之间的通信。
在代码中,Activity与Service中都用到了广播的机制。这种机制的好处是:我们能够拿到广播中的信息,这样为我们的实时更新提供了很大的便捷。具体代码实现已在代码中书写,这里只单独拿出来一部分来展示。
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Service->Receiver");
String action = intent.getAction();
if (ACTION.equals(action)) {
int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);
switch (widget_action) {
case ACTION_PRE:
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
next(-1);
Log.d(TAG,"action_prev");
break;
case ACTION_PLAY_PAUSE:
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
play();
break;
case ACTION_NEXT:
mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
next(1);
Log.d(TAG,"action_next");
break;
default:
break;
}
}
}
};
三、项目总结
- 在项目中还用到了很多的一些类,在这里就不列举出来了,大家可以参考我的代码,代码地址也给出了。
- 如果大家有什么不懂得,留言即可,看到就会回复哦!
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。
本文链接:https://www.blog.ycisch.com/archives/617.html