Android MediaPlayer 本地音乐播放器

运行截图

项目请在真机(自己的手机)上测试运行,因为我不喜欢用虚拟机。
为了不浪费您的时间,先看一下运行的效果图,
一进去先进行音乐扫描,然后列表展示出来,点击即可播放。

演示视频地址

源码地址

GitHub 项目地址
这个给不想浪费时间往下看的朋友,只因你的时间很宝贵。

前言

至于为什么写一个这样的Demo呢,因为有很多人学习Android就是对于手机应用感兴趣,而网络上的很多源码,一些开源项目的代码小白看不懂,小白能看懂的,有些博主又要用积分下载,痛定思痛,索性自己写一个,当然在写的过程中查阅了网络的资料,也加入了自己的想法,希望能帮到对手机音乐播放器这方面有想法的朋友,好了,话不多说,进入正题.:

代码解释

项目配置

1.权限配置:
打开AndroidManifest.xml

<!--文件读写权限  Android6.0 以后需要动态获取  10.0之后对文件的处理更复杂了--><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2.依赖引入:
先打开工程的build.gradle,
加入如下图中的代码

     maven { url "https://jitpack.io" }mavenCentral()

然后打开项目的build.gradle,在dependencies闭包中加入以下代码依赖:

 //butterknife  绑定视图依赖BindView,告别findById,不过你还得安装一个butterknife插件才行implementation 'com.jakewharton:butterknife:10.1.0'annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'//Google Material控件,以及迁移到AndroidX下一些控件的依赖implementation 'com.google.android.material:material:1.0.0'implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'implementation 'androidx.annotation:annotation:1.1.0'implementation 'androidx.legacy:legacy-support-v4:1.0.0'//RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'//权限请求框架implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'implementation "io.reactivex.rxjava2:rxjava:2.0.0"//状态栏implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3'

在android闭包中加入

compileOptions {sourceCompatibility = 1.8targetCompatibility = 1.8}

指定你的JDK版本,我的项目中用的是AndroidX,如果你没有用过的,建议你先去了解一下:
好了,下面看布局文件,这次是做的一个完成的项目,所以新建了一个工程,里面有一些样式和图片、图标,工程目录如下图,这里就不在做解释了:

布局文件只有两个,activity_main.xml和item_music_rv_list.xml,activity_main这个是项目创建的时候赠送的,item_music_rv_list是为了显示歌曲信息而创建的

activity_main.xml代码:

<?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"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@mipmap/img_main_bg_1"android:fitsSystemWindows="true"android:orientation="vertical"tools:context=".MainActivity"><!--Toolbar--><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="?attr/colorPrimary"app:contentInsetLeft="@dimen/activity_horizontal_margin"app:popupTheme="@style/AppTheme.PopupOverlay"><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="聆听音乐"android:textColor="@color/common_black"android:textSize="18sp" /><TextViewandroid:visibility="gone"android:id="@+id/tv_clear_list"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right"android:foreground="@drawable/bg_white"android:layout_marginRight="@dimen/dp_16"android:padding="@dimen/dp_15"android:text="清空"android:textColor="@color/common_black" /></androidx.appcompat.widget.Toolbar><!--扫描歌曲的布局--><LinearLayoutandroid:id="@+id/scan_lay"android:background="@color/white"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/img_scan_logo" /><Buttonandroid:id="@+id/btn_scan"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/dp_20"android:background="@drawable/selector_scan_btn"android:text="开始扫描"android:textColor="#FFF"android:textSize="22sp" /></LinearLayout><!--扫描之后,歌曲的展示和播放布局--><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><!--播放歌曲时,显示你播放的是哪一首歌,跑马灯效果--><LinearLayoutandroid:visibility="gone"android:id="@+id/play_state_lay"android:layout_marginTop="@dimen/dp_1"android:gravity="center"android:background="@color/half_transparent"android:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/play_state_img"android:background="@mipmap/list_pause_state"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:gravity="center"android:text="歌曲信息"android:ellipsize="marquee"android:focusableInTouchMode="true"android:focusable="true"android:marqueeRepeatLimit="marquee_forever"android:singleLine="true"android:textColor="@color/white"android:id="@+id/tv_play_song_info"android:orientation="vertical"android:layout_width="@dimen/dp_150"android:layout_height="wrap_content"/></LinearLayout><!--歌曲的展示列表,都什么年代了,你还在用ListView吗?--><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_music"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/><!--底部歌曲播放控制布局--><LinearLayoutandroid:layout_marginTop="@dimen/dp_4"android:orientation="vertical"android:background="@color/half_transparent"android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:gravity="center_vertical"android:layout_alignParentBottom="true"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><!--播放时间--><TextViewandroid:id="@+id/tv_play_time"android:text="00:00"android:layout_marginLeft="@dimen/dp_4"android:textSize="@dimen/sp_14"android:textColor="@color/white"android:layout_width="wrap_content"android:layout_height="wrap_content"/><!--歌曲播放进度条 ,样式是自定义,因为原生的太丑了--><SeekBarandroid:layout_marginLeft="@dimen/dp_10"android:layout_marginRight="@dimen/dp_10"android:id="@+id/time_seekBar"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:max="100"android:maxHeight="2dp"android:minHeight="2dp"android:progress="0"android:progressDrawable="@drawable/seekbar_style"android:thumb="@drawable/thumb" /><!--总时间--><TextViewandroid:id="@+id/tv_total_time"android:layout_marginRight="@dimen/dp_4"android:text="00:00"android:textSize="@dimen/sp_14"android:textColor="@color/white"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout><LinearLayoutandroid:padding="@dimen/dp_10"android:gravity="center"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><!--上一曲--><ImageViewandroid:id="@+id/btn_previous"android:background="@mipmap/icon_previous"android:layout_width="@dimen/dp_40"android:layout_height="@dimen/dp_40" /><!--播放或暂停--><ImageViewandroid:layout_marginLeft="@dimen/dp_20"android:layout_marginRight="@dimen/dp_20"android:id="@+id/btn_play_or_pause"android:background="@mipmap/icon_pause"android:layout_width="@dimen/dp_44"android:layout_height="@dimen/dp_44" /><!--下一曲--><ImageViewandroid:id="@+id/btn_next"android:background="@mipmap/icon_next"android:layout_width="@dimen/dp_40"android:layout_height="@dimen/dp_40"/></LinearLayout></LinearLayout></LinearLayout></LinearLayout>

item_music_rv_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/item_music"android:background="@color/color_transparent_10"android:foreground="@drawable/bg_white"android:orientation="vertical"android:layout_marginBottom="@dimen/dp_1"android:gravity="center_vertical"><LinearLayoutandroid:gravity="center_vertical"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:gravity="center_vertical"android:padding="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_position"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="2dp"android:text="1"android:textColor="@color/white"android:textSize="16sp" /><LinearLayoutandroid:layout_marginLeft="@dimen/dp_10"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:gravity="center_vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_song_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="歌曲名"android:maxLines="1"android:textColor="@color/white"android:textSize="@dimen/sp_18" /></LinearLayout><LinearLayoutandroid:layout_marginTop="@dimen/dp_4"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_singer"android:layout_width="0dp"android:layout_weight="1"android:text="歌手"android:ellipsize="end"android:maxLines="1"android:layout_height="wrap_content"android:textColor="@color/white"android:textSize="14sp" /><TextViewandroid:id="@+id/tv_duration_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="12dp"android:textColor="@color/white"android:text="时间"android:textSize="14sp" /></LinearLayout></LinearLayout></LinearLayout></LinearLayout>
</LinearLayout>

布局其实没有什么好讲解的,就是显示和隐藏的控制。

样式文件:
rounded_corners.xml 水波纹效果的xml文件

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"><shape android:shape="rectangle"><solid android:color="#18ffc400"/></shape></item><item android:state_focused="true" android:state_enabled="true"><shape android:shape="rectangle"><solid android:color="#0f000000"/></shape></item>
</selector>

在bg_white.xml中调用了rounded_corners.xml

bg_white.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"android:color="#20000000"android:drawable="@drawable/rounded_corners"/>

扫描按钮的背景图
scan_finish_btn_bg_pressed.9

scan_finish_btn_bg_normal.9

selector_scan_btn.xml使用上面两个图片的按钮点击样式

<?xml version="1.0" encoding="utf-8"?>
<selectorxmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true" android:drawable="@drawable/scan_finish_btn_bg_pressed" /><item android:drawable="@drawable/scan_finish_btn_bg_normal" />
</selector>

selector_scan_btn这个样式文件,在activity_main中用到

seekbar_style.xml 进度条样式

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"><item android:id="@android:id/background"><shape><corners android:radius="5dp" /><solid android:color="#B6C4F9" /></shape></item><item android:id="@android:id/secondaryProgress"><clip><shape><corners android:radius="5dp" /><solid android:color="#B6C4F9" /></shape></clip></item><item android:id="@android:id/progress"><clip><shape><corners android:radius="5dp" /><solid android:color="#7D95F3" /></shape></clip></item>
</layer-list>

thumb.xml 进度条滑块样式

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="oval"><solid android:color="#40DEF1" /><sizeandroid:width="10dp"android:height="10dp" /><strokeandroid:width="1dp"android:color="#40DEF1" /></shape>

上面两个关于进度条的样式也在activity_main.xml中用到

然后是values下面的styles.xml文件

<resources><!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/white</item><item name="colorPrimaryDark">@color/white</item><item name="colorAccent">@color/colorAccent</item></style><style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /></resources>

样式和布局讲完了,接下来是工具类:
Constant.java 缓存字符工具类,因为现在只在MainActivity中用到,所以提现不出它的优势,代码如下:

public class Constant {public final static String MUSIC_DATA_FIRST = "musicDataFirst";
}

DateUtil.java 时间转换工具类:


/*** 时间转换工具类*/
public class DateUtil {//获取当前完整的日期和时间public static String getNowDateTime(){SimpleDateFormat sdf = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");return sdf.format(new Date());}//获取当前日期public static String getNowDate(){SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");return sdf.format(new Date());}//获取当前时间public static String getNowTime(){SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");return sdf.format(new Date());}//获取当前时间不包含秒public static String getNowTimeM(){SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");return sdf.format(new Date());}//转换当前时间不包含时public static String parseTime(int oldTime) {SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");// 时间格式String newTime = sdf.format(new Date(oldTime));return newTime;}//获取当前日期(精确到毫秒)public static String getNowTimeDetail(){SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:SSS");return sdf.format(new Date());}//获取当前日期是星期几public static String getWeekOfDate(Date date) {String[] weekDays = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };Calendar cal = Calendar.getInstance();cal.setTime(date);int w = cal.get(Calendar.DAY_OF_WEEK) - 1;if (w < 0)w = 0;return weekDays[w];}//将时间戳转化为对应的时间(10位或者13位都可以)public static String formatTime(long time){String times = null;if(String.valueOf(time).length()>10){// 10位的秒级别的时间戳times = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time * 1000));}else {// 13位的秒级别的时间戳times  = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);}return times;}//将时间字符串转为时间戳字符串public static String getStringTimestamp(String time) {String timestamp = null;try {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Long longTime = sdf.parse(time).getTime()/1000;timestamp = Long.toString(longTime);} catch (ParseException e) {e.printStackTrace();}return timestamp;}//将长整型时间转为为分秒public static String time(long millionSeconds) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");Calendar c = Calendar.getInstance();c.setTimeInMillis(millionSeconds);return simpleDateFormat.format(c.getTime());}//将长度转换为时间public static StringBuilder mFormatBuilder = new StringBuilder();public static Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());public static String stringForTime(int timeMs) {int totalSeconds = timeMs / 1000;int seconds = totalSeconds % 60;int minutes = (totalSeconds / 60) % 60;int hours = totalSeconds / 3600;mFormatBuilder.setLength(0);if (hours > 0) {return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();} else {return mFormatter.format("%02d:%02d", minutes, seconds).toString();}}
}

MusicUtils.java 音乐扫描工具类

public class MusicUtils {/*** 扫描系统里面的音频文件,返回一个list集合*/public static List<Song> getMusicData(Context context) {List<Song> list = new ArrayList<Song>();// 媒体库查询语句(写一个工具类MusicUtils)Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null,null, MediaStore.Audio.AudioColumns.IS_MUSIC);if (cursor != null) {while (cursor.moveToNext()) {Song song = new Song();song.song = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));//歌曲名称song.singer = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));//歌手song.album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));//专辑名song.path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));//歌曲路径song.duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));//歌曲时长song.size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE));//歌曲大小if (song.size > 1000 * 800) {// 注释部分是切割标题,分离出歌曲名和歌手 (本地媒体库读取的歌曲信息不规范)if (song.song.contains("-")) {String[] str = song.song.split("-");song.singer = str[0];song.song = str[1];}list.add(song);}}// 释放资源cursor.close();}return list;}//专辑图片private static String imgUrl(Context context){String album_art= null;String[] mediaColumns1 = new String[] {MediaStore.Audio.Albums.ALBUM_ART, MediaStore.Audio.Albums.ALBUM};Cursor cursor1 = context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, mediaColumns1, null, null,null);if (cursor1 != null) {cursor1.moveToFirst();do {album_art =  cursor1.getString(0);if (album_art != null) {Log.d("ALBUM_ART", album_art);}String album =  cursor1.getString(1);if (album != null) {Log.d("ALBUM_ART", album);}} while (cursor1.moveToNext());cursor1.close();}return album_art;}/*** 定义一个方法用来格式化获取到的时间*/public static String formatTime(int time) {if (time / 1000 % 60 < 10) {return time / 1000 / 60 + ":0" + time / 1000 % 60;} else {return time / 1000 / 60 + ":" + time / 1000 % 60;}}

ObjectUtil.java 对象工具类,用于判空

public final class ObjectUtils {private ObjectUtils() {throw new UnsupportedOperationException("u can't instantiate me...");}/*** Return whether object is empty.** @param obj The object.* @return {@code true}: yes<br>{@code false}: no*/public static boolean isEmpty(final Object obj) {if (obj == null) {return true;}if (obj.getClass().isArray() && Array.getLength(obj) == 0) {return true;}if (obj instanceof CharSequence && obj.toString().length() == 0) {return true;}if (obj instanceof Collection && ((Collection) obj).isEmpty()) {return true;}if (obj instanceof Map && ((Map) obj).isEmpty()) {return true;}if (obj instanceof SimpleArrayMap && ((SimpleArrayMap) obj).isEmpty()) {return true;}if (obj instanceof SparseArray && ((SparseArray) obj).size() == 0) {return true;}if (obj instanceof SparseBooleanArray && ((SparseBooleanArray) obj).size() == 0) {return true;}if (obj instanceof SparseIntArray && ((SparseIntArray) obj).size() == 0) {return true;}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {if (obj instanceof SparseLongArray && ((SparseLongArray) obj).size() == 0) {return true;}}if (obj instanceof LongSparseArray && ((LongSparseArray) obj).size() == 0) {return true;}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {if (obj instanceof android.util.LongSparseArray&& ((android.util.LongSparseArray) obj).size() == 0) {return true;}}return false;}public static boolean isEmpty(final CharSequence obj) {return obj == null || obj.toString().length() == 0;}public static boolean isEmpty(final Collection obj) {return obj == null || obj.isEmpty();}public static boolean isEmpty(final Map obj) {return obj == null || obj.isEmpty();}public static boolean isEmpty(final SimpleArrayMap obj) {return obj == null || obj.isEmpty();}public static boolean isEmpty(final SparseArray obj) {return obj == null || obj.size() == 0;}public static boolean isEmpty(final SparseBooleanArray obj) {return obj == null || obj.size() == 0;}public static boolean isEmpty(final SparseIntArray obj) {return obj == null || obj.size() == 0;}public static boolean isEmpty(final LongSparseArray obj) {return obj == null || obj.size() == 0;}@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)public static boolean isEmpty(final SparseLongArray obj) {return obj == null || obj.size() == 0;}@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)public static boolean isEmpty(final android.util.LongSparseArray obj) {return obj == null || obj.size() == 0;}/*** Return whether object is not empty.** @param obj The object.* @return {@code true}: yes<br>{@code false}: no*/public static boolean isNotEmpty(final Object obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final CharSequence obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final Collection obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final Map obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final SimpleArrayMap obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final SparseArray obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final SparseBooleanArray obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final SparseIntArray obj) {return !isEmpty(obj);}public static boolean isNotEmpty(final LongSparseArray obj) {return !isEmpty(obj);}@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)public static boolean isNotEmpty(final SparseLongArray obj) {return !isEmpty(obj);}@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)public static boolean isNotEmpty(final android.util.LongSparseArray obj) {return !isEmpty(obj);}/*** Return whether object1 is equals to object2.** @param o1 The first object.* @param o2 The second object.* @return {@code true}: yes<br>{@code false}: no*/public static boolean equals(final Object o1, final Object o2) {return o1 == o2 || (o1 != null && o1.equals(o2));}/*** Require the objects are not null.** @param objects The object.* @throws NullPointerException if any object is null in objects*/public static void requireNonNull(final Object... objects) {if (objects == null) throw new NullPointerException();for (Object object : objects) {if (object == null) throw new NullPointerException();}}/*** Return the nonnull object or default object.** @param object        The object.* @param defaultObject The default object to use with the object is null.* @param <T>           The value type.* @return the nonnull object or default object*/public static <T> T getOrDefault(final T object, final T defaultObject) {if (object == null) {return defaultObject;}return object;}/*** Return the hash code of object.** @param o The object.* @return the hash code of object*/public static int hashCode(final Object o) {return o != null ? o.hashCode() : 0;}
}

SPUtils.java 缓存读写工具类,配合Constant使效果更佳

/*** sharepref工具类*/
public class SPUtils {private static final String NAME="config";public static void putBoolean(String key, boolean value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putBoolean(key, value).commit();}public static boolean getBoolean(String key, boolean defValue, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getBoolean(key, defValue);}public static void putString(String key, String value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putString(key, value).commit();}public static String getString(String key, String defValue, Context context) {if(context!=null){SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getString(key, defValue);}return "";}public static void putInt(String key, int value, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putInt(key, value).commit();}public static int getInt(String key, int defValue, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getInt(key, defValue);}public static void remove(String key, Context context) {SharedPreferences sp = context.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().remove(key).commit();}}

StatusBarUtil.java 状态栏工具类:

public class StatusBarUtil {/*** 修改状态栏为全透明** @param activity*/@TargetApi(19)public static void transparencyBar(Activity activity) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {Window window = activity.getWindow();window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);window.setStatusBarColor(Color.TRANSPARENT);} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {Window window = activity.getWindow();window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);}}/*** 修改状态栏颜色,支持4.4以上版本** @param activity* @param colorId*/public static void setStatusBarColor(Activity activity, int colorId) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {Window window = activity.getWindow();
//      window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);window.setStatusBarColor(activity.getResources().getColor(colorId));} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明transparencyBar(activity);SystemBarTintManager tintManager = new SystemBarTintManager(activity);tintManager.setStatusBarTintEnabled(true);tintManager.setStatusBarTintResource(colorId);}}/*** 状态栏亮色模式,设置状态栏黑色文字、图标,* 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android** @param activity* @return 1:MIUUI 2:Flyme 3:android6.0*/public static int StatusBarLightMode(Activity activity) {int result = 0;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {if (MIUISetStatusBarLightMode(activity, true)) {result = 1;} else if (FlymeSetStatusBarLightMode(activity.getWindow(), true)) {result = 2;} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);result = 3;}}return result;}/*** 已知系统类型时,设置状态栏黑色文字、图标。* 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android** @param activity* @param type     1:MIUUI 2:Flyme 3:android6.0*/public static void StatusBarLightMode(Activity activity, int type) {if (type == 1) {MIUISetStatusBarLightMode(activity, true);} else if (type == 2) {FlymeSetStatusBarLightMode(activity.getWindow(), true);} else if (type == 3) {activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);}}/*** 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标*/public static void StatusBarDarkMode(Activity activity, int type) {if (type == 1) {MIUISetStatusBarLightMode(activity, false);} else if (type == 2) {FlymeSetStatusBarLightMode(activity.getWindow(), false);} else if (type == 3) {activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);}}/*** 设置状态栏图标为深色和魅族特定的文字风格* 可以用来判断是否为Flyme用户** @param window 需要设置的窗口* @param dark   是否把状态栏文字及图标颜色设置为深色* @return boolean 成功执行返回true*/public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {boolean result = false;if (window != null) {try {WindowManager.LayoutParams lp = window.getAttributes();Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");darkFlag.setAccessible(true);meizuFlags.setAccessible(true);int bit = darkFlag.getInt(null);int value = meizuFlags.getInt(lp);if (dark) {value |= bit;} else {value &= ~bit;}meizuFlags.setInt(lp, value);window.setAttributes(lp);result = true;} catch (Exception e) {}}return result;}/*** 需要MIUIV6以上** @param activity* @param dark     是否把状态栏文字及图标颜色设置为深色* @return boolean 成功执行返回true*/public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {boolean result = false;Window window = activity.getWindow();if (window != null) {Class clazz = window.getClass();try {int darkModeFlag = 0;Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");darkModeFlag = field.getInt(layoutParams);Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);if (dark) {extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体} else {extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体}result = true;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上if (dark) {activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);} else {activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);}}} catch (Exception e) {}}return result;}}

ToastUtils.java Toast消息类,原来的太麻烦了,

public class ToastUtils {public static void showLongToast(Context context, CharSequence llw) {Toast.makeText(context.getApplicationContext(), llw, Toast.LENGTH_LONG).show();}public static void showShortToast(Context context, CharSequence llw) {Toast.makeText(context.getApplicationContext(), llw, Toast.LENGTH_SHORT).show();}
}

工具类介绍完成,接下来是数据模型:
Song.java 歌曲工具类:

public class Song {/*** 歌手*/public String singer;/*** 歌曲名*/public String song;/*** 专辑名*/public String album;/*** 专辑图片*/public String album_art;/*** 歌曲的地址*/public String path;/*** 歌曲长度*/public int duration;/*** 歌曲的大小*/public long size;public String getSinger() {return singer;}public void setSinger(String singer) {this.singer = singer;}public String getSong() {return song;}public void setSong(String song) {this.song = song;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}public int getDuration() {return duration;}public void setDuration(int duration) {this.duration = duration;}public long getSize() {return size;}public void setSize(long size) {this.size = size;}public String getAlbum() {return album;}public void setAlbum(String album) {this.album = album;}public String getAlbum_art() {return album_art;}public void setAlbum_art(String album_art) {this.album_art = album_art;}
}

MusicListAdapter.java 歌曲列表适配器

public class MusicListAdapter extends BaseQuickAdapter<Song, BaseViewHolder>{public MusicListAdapter(int layoutResId, @Nullable List<Song> data) {super(layoutResId, data);}@Overrideprotected void convert(BaseViewHolder helper, Song item) {//给控件赋值int duration = item.duration;String time = MusicUtils.formatTime(duration);helper.setText(R.id.tv_song_name,item.getSong().trim())//歌曲名称.setText(R.id.tv_singer,item.getSinger()+" - "+item.getAlbum())//歌手 - 专辑.setText(R.id.tv_duration_time,time)//歌曲时间//歌曲序号,因为getAdapterPosition得到的位置是从0开始,故而加1,//是因为位置和1都是整数类型,直接赋值给TextView会报错,故而拼接了"".setText(R.id.tv_position,helper.getAdapterPosition()+1+"");helper.addOnClickListener(R.id.item_music);//给item添加点击事件,点击之后传递数据到播放页面或者在本页面进行音乐播放}
}

然后是主角出场了:
MainActivity.java 主要逻辑代码都在里面:
注释也已经写好了,我就不过多的啰嗦了,代码如下:

package com.llw.music;import android.Manifest;
import android.graphics.drawable.Drawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;import com.chad.library.adapter.base.BaseQuickAdapter;
import com.llw.music.adapter.MusicListAdapter;
import com.llw.music.model.Song;
import com.llw.music.utils.Constant;
import com.llw.music.utils.MusicUtils;
import com.llw.music.utils.ObjectUtils;
import com.llw.music.utils.SPUtils;
import com.llw.music.utils.StatusBarUtil;
import com.llw.music.utils.ToastUtils;
import com.tbruyelle.rxpermissions2.RxPermissions;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;import static com.llw.music.utils.DateUtil.parseTime;public class MainActivity extends AppCompatActivity implements MediaPlayer.OnCompletionListener {@BindView(R.id.rv_music)RecyclerView rvMusic;@BindView(R.id.btn_scan)Button btnScan;@BindView(R.id.scan_lay)LinearLayout scanLay;@BindView(R.id.tv_clear_list)TextView tvClearList;@BindView(R.id.tv_title)TextView tvTitle;@BindView(R.id.toolbar)Toolbar toolbar;@BindView(R.id.tv_play_time)TextView tvPlayTime;@BindView(R.id.time_seekBar)SeekBar timeSeekBar;@BindView(R.id.tv_total_time)TextView tvTotalTime;@BindView(R.id.btn_previous)ImageView btnPrevious;@BindView(R.id.btn_play_or_pause)ImageView btnPlayOrPause;@BindView(R.id.btn_next)ImageView btnNext;@BindView(R.id.tv_play_song_info)TextView tvPlaySongInfo;@BindView(R.id.play_state_img)ImageView playStateImg;@BindView(R.id.play_state_lay)LinearLayout playStateLay;private MusicListAdapter mAdapter;//歌曲适配器private List<Song> mList;//歌曲列表private RxPermissions rxPermissions;//权限请求private MediaPlayer mediaPlayer;//音频播放器private String musicData = null;// 记录当前播放歌曲的位置public int mCurrentPosition;private static final int INTERNAL_TIME = 1000;// 音乐进度间隔时间private Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message message) {// 展示给进度条和当前时间int progress = mediaPlayer.getCurrentPosition();timeSeekBar.setProgress(progress);tvPlayTime.setText(parseTime(progress));// 继续定时发送数据updateProgress();return true;}});@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);StatusBarUtil.StatusBarLightMode(this);rxPermissions = new RxPermissions(this);//使用前先实例化timeSeekBar.setOnSeekBarChangeListener(seekBarChangeListener);//滑动条监听musicData = SPUtils.getString(Constant.MUSIC_DATA_FIRST, "yes", this);if (musicData.equals("no")) {//说明是第一次打开APP,未进行扫描scanLay.setVisibility(View.GONE);initMusic();} else {scanLay.setVisibility(View.VISIBLE);}}private void permissionRequest() {//使用这个框架需要制定JDK版本,建议用1.8rxPermissions.request(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(granted -> {if (granted) {//请求成功之后开始扫描initMusic();} else {//失败时给一个提示ToastUtils.showShortToast(MainActivity.this, "未授权");}});}//获取音乐列表private void initMusic() {mList = new ArrayList<>();//实例化//数据赋值mList = MusicUtils.getMusicData(this);//将扫描到的音乐赋值给音乐列表if (!ObjectUtils.isEmpty(mList) && mList != null) {scanLay.setVisibility(View.GONE);SPUtils.putString(Constant.MUSIC_DATA_FIRST, "no", this);}mAdapter = new MusicListAdapter(R.layout.item_music_rv_list, mList);//指定适配器的布局和数据源//线性布局管理器,可以设置横向还是纵向,RecyclerView默认是纵向的,所以不用处理,如果不需要设置方向,代码还可以更加的精简如下rvMusic.setLayoutManager(new LinearLayoutManager(this));//如果需要设置方向显示,则将下面代码注释去掉即可
//        LinearLayoutManager manager = new LinearLayoutManager(this);
//        manager.setOrientation(RecyclerView.HORIZONTAL);
//        rvMusic.setLayoutManager(manager);//设置适配器rvMusic.setAdapter(mAdapter);//item的点击事件mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {@Overridepublic void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {if (view.getId() == R.id.item_music) {mCurrentPosition = position;changeMusic(mCurrentPosition);
//                    mAdapter.notifyItemRangeChanged(0, mList.size());}}});//设置背景样式initStyle();}private void initStyle() {//toolbar背景变透明toolbar.setBackgroundColor(getResources().getColor(R.color.half_transparent));//文字变白色tvTitle.setTextColor(getResources().getColor(R.color.white));tvClearList.setTextColor(getResources().getColor(R.color.white));StatusBarUtil.transparencyBar(this);}@OnClick({R.id.tv_clear_list, R.id.btn_scan, R.id.btn_previous, R.id.btn_play_or_pause, R.id.btn_next})public void onViewClicked(View view) {switch (view.getId()) {case R.id.tv_clear_list://清空数据mList.clear();mAdapter.notifyDataSetChanged();SPUtils.putString(Constant.MUSIC_DATA_FIRST, "yes", this);scanLay.setVisibility(View.VISIBLE);toolbar.setBackgroundColor(getResources().getColor(R.color.white));StatusBarUtil.StatusBarLightMode(this);tvTitle.setTextColor(getResources().getColor(R.color.black));tvClearList.setTextColor(getResources().getColor(R.color.black));if (mediaPlayer == null) {mediaPlayer = new MediaPlayer();}if (mediaPlayer.isPlaying()) {mediaPlayer.pause();mediaPlayer.reset();}break;case R.id.btn_scan://扫描本地歌曲permissionRequest();break;case R.id.btn_previous://上一曲changeMusic(--mCurrentPosition);//当前歌曲位置减1break;case R.id.btn_play_or_pause://播放或者暂停// 首次点击播放按钮,默认播放第0首,下标从0开始if (mediaPlayer == null) {changeMusic(0);} else {if (mediaPlayer.isPlaying()) {mediaPlayer.pause();btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));playStateImg.setBackground(getResources().getDrawable(R.mipmap.list_play_state));//如果你是用TextView的leftDrawable设置的图片,在代码里面就可以通过下面代码来动态更换
//                        Drawable leftImg = getResources().getDrawable(R.mipmap.list_play_state);
//                        leftImg.setBounds(0, 0, leftImg.getMinimumWidth(), leftImg.getMinimumHeight());
//                        tvPlaySongInfo.setCompoundDrawables(leftImg, null, null, null);} else {mediaPlayer.start();btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_play));playStateImg.setBackground(getResources().getDrawable(R.mipmap.list_pause_state));}}break;case R.id.btn_next://下一曲changeMusic(++mCurrentPosition);//当前歌曲位置加1break;}}//切歌private void changeMusic(int position) {Log.e("MainActivity", "position:" + position);if (position < 0) {mCurrentPosition = position = mList.size() - 1;Log.e("MainActivity", "mList.size:" + mList.size());} else if (position > mList.size() - 1) {mCurrentPosition = position = 0;}Log.e("MainActivity", "position:" + position);if (mediaPlayer == null) {mediaPlayer = new MediaPlayer();}try {// 切歌之前先重置,释放掉之前的资源mediaPlayer.reset();// 设置播放源Log.d("Music", mList.get(position).path);mediaPlayer.setDataSource(mList.get(position).path);tvPlaySongInfo.setText("歌名: " + mList.get(position).song +"  歌手: " + mList.get(position).singer);//            Glide.with(this).load(mList.get(position).album_art).into(songImage);tvPlaySongInfo.setSelected(true);//跑马灯效果playStateLay.setVisibility(View.VISIBLE);// 开始播放前的准备工作,加载多媒体资源,获取相关信息mediaPlayer.prepare();// 开始播放mediaPlayer.start();} catch (IOException e) {e.printStackTrace();}// 切歌时重置进度条并展示歌曲时长timeSeekBar.setProgress(0);timeSeekBar.setMax(mediaPlayer.getDuration());tvTotalTime.setText(parseTime(mediaPlayer.getDuration()));updateProgress();if (mediaPlayer.isPlaying()) {btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_play));playStateImg.setBackground(getResources().getDrawable(R.mipmap.list_pause_state));} else {btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));playStateImg.setBackground(getResources().getDrawable(R.mipmap.list_play_state));}}private void updateProgress() {// 使用Handler每间隔1s发送一次空消息,通知进度条更新Message msg = Message.obtain();// 获取一个现成的消息// 使用MediaPlayer获取当前播放时间除以总时间的进度int progress = mediaPlayer.getCurrentPosition();msg.arg1 = progress;mHandler.sendMessageDelayed(msg, INTERNAL_TIME);}//滑动条监听SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}// 当手停止拖拽进度条时执行该方法// 获取拖拽进度// 将进度对应设置给MediaPlayer@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {int progress = seekBar.getProgress();mediaPlayer.seekTo(progress);}};//播放完成之后自动下一曲@Overridepublic void onCompletion(MediaPlayer mp) {changeMusic(++mCurrentPosition);}
}

GitHub 项目源码
这个地址是为了不浪费你的时间往上滑动。

以上就是所有代码了,如果你写的过程中,某些地方为红色,就是没有文件资源,或者是依赖。有任何问题评论留言,我会在第一时间回复你,感谢你的阅读,希望对你有所帮助,山高水长,后会有期~

Android MediaPlayer 音乐播放器扫描 本地音乐、上一曲、下一曲切歌、播放本地音乐相关推荐

  1. 《新lrc播放器2》-iPhone上可以显示lrc歌词的播放器可以在播放mp3文件时显示lrc文件中的歌词的播放器

    https://apps.apple.com/cn/app/%E6%96%B0lrc%E6%92%AD%E6%94%BE%E5%99%A82/id1535214306 以前,在iPhone上播放lrc ...

  2. Android开发本地及网络Mp3音乐播放器(二十)歌曲下载完成后通知主界面更新本地音乐

    转载请注明出处:http://blog.csdn.net/iwanghang/article/details/51448597 项目源码(打赏5积分请点这边):http://download.csdn ...

  3. android连接蓝牙控制音乐播放器播放暂停/上一曲/下一曲,且断开蓝牙暂停音乐

    客户需求要蓝牙设备可以控制音乐播放器的暂停等操作,当时只做了蓝牙的权限配置,未对这些操作做处理. 1.配置清单文件 <serviceandroid:name=".PlayerServi ...

  4. 一款简单的本地音乐播放器,界面美观、包括主题切换、歌单管理等等

    MeetMusic 项目地址:lijunyandev/MeetMusic  简介:一款简单的本地音乐播放器,界面美观.包括主题切换.歌单管理等等 音乐-播放器-主题切换- App 效果图        ...

  5. 音乐播放器的实现(四)—— 歌曲列表、顺序播放、单曲循环、随机播放、自动下一曲

    音乐播放器的实现(四)-- 歌曲列表.顺序播放.单曲循环.随机播放.自动下一曲 传送门:(完整工程见第五章篇尾) 音乐播放器的实现(一)-- Audio Listener和Audio Source面板 ...

  6. 音乐播放器的实现(三)---进度条控制、播放、暂停、上(下)一曲、播放时间和总时间显示

    音乐播放器的实现(三)---进度条控制.播放.暂停.上(下)一曲.播放时间和总时间显示 传送门:(完整工程见第五章篇尾) 音乐播放器的实现(一)-- Audio Listener和Audio Sour ...

  7. 开博尔android播放器,开博尔q50评测 | 开博尔q50播放器评测_什么值得买

    影音俱佳--开博尔Q50 4K 蓝光播放器 2019-12-06 17:00:59 13点赞 43收藏 14评论 现在随着OPPO的退市,先锋的缺货,高端播放器市场似乎可选择的越来越少,相对于传统4K ...

  8. php 手机端播放器,用JS代码适配电脑端和手机端播放器代码

    用JS代码适配电脑端和手机端播放器代码 蓝叶    网站设计    2016-08-10    10668    9评论 随着html5的崛起,很多网站都采用html5代码来设计,让网站美观大气,但是 ...

  9. 计算机音乐先点什么,我电脑上有几千首歌,想在歌曲前面加上序号,但我忘了怎样使用拖把更? 爱问知识人...

    - 基本的转换.添加.删除.替换.加序等更名操作 - 文件名繁简互换.内码互换 - 利用文本文件更名 - 支持目录.子目录及其文件.快捷方式等更名操作 - 根据音乐文件中的标签进行更名,支持 MP3/ ...

  10. 免费网页直播|点播播放器-自动重连解决网络|操作造成断流的播放问题

    LivePlayer网页直播|点播免费播放器下载: 支持m3u8播放;支持HTTP-FLV播放;支持RTMP播放;支持直播和点播播放;支持播放器快照截图;支持点播多清晰度播放;支持全屏或比例显示;自带 ...

最新文章

  1. MAC OS X的ACL扩展权限设置
  2. jsp springmvc 视图解析器_Springmvc中多视图解析器解析问题
  3. SCI论文写作--IEEE的期刊和杂志区分
  4. 无法使用tomcat6.exe启动服务
  5. spring-framework-5.1.x 源码编译 环境搭建 [ idea:2020.1 ]
  6. 纽交所决定将蛋壳公寓ADS摘牌
  7. 64位Eclipse运行时提示“Failed to load the JNI shared library \Java\jre6\bin\client\jvm.dll”的一个解决方案
  8. spark入门_[大数据之Spark]——快速入门
  9. HTML5网页设计实例:企业网站设计——红色文化传媒网站(20页) HTML+CSS+JavaScript
  10. steamcommunity 302占用端口
  11. 推荐一款在线工具-程序员的工具箱
  12. 攻防世界--logmein
  13. 当前量子计算技术前沿是什么水平?
  14. 一个小时开发的直播推拉流软件来了
  15. frame切换/窗口切换
  16. 计算机文字转表格,Word怎么把文字转换成表格 30秒搞定
  17. python程序基础网课答案_知到Python程序设计基础网课答案
  18. 苹果如何做ASO优化?优化的主要思路有哪些?
  19. 网管服务器维修,网管员管理和维护的超级武器
  20. php的mssql_connect+端口连接不了的问题

热门文章

  1. MySQL备份系列--备份方案总结性梳理
  2. 《Essential Linux Device Drivers》中文版第1章
  3. 十年磨一剑 | 《腾讯大数据构建之道》正式出版
  4. PTA_2021年团体程序设计天梯赛_总决赛_L2-4 哲哲打游戏 (25 分)_搞心态_简单模拟
  5. 第二代战斗机的特点有哪些
  6. 软件测试Bug评测 之Serverity(严重程度)、Priority(优先级)
  7. 在已安装win10环境中利用EasyBCD引导安装Ubuntu16.04-小白补充
  8. 问题记录vips.h:133: undefined reference to `vips_reduce‘ collect2: error: ld returned 1 exit status
  9. scss混合器实现全局更换主题以及相关背景图
  10. bootstrap中,role=form及role作用角色