zt-深入理解abstract class和interface

本文探讨了Java中抽象类(abstract class)与接口(interface)的区别,包括语法定义、编程实践及设计理念层面的不同。通过具体示例展示了如何正确选择使用抽象类或接口,以更好地表达设计意图。
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

理解抽象类

 

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP( Open-Closed Principle),抽象类是其中的关键所在。

 




回页首


从语法定义层面看abstract class和interface

 

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

使用abstract class的方式定义Demo抽象类的方式如下:

abstract class Demo {
	abstract void method1();
	abstract void method2();
	…
}

 

使用interface的方式定义Demo抽象类的方式如下:

interface Demo {
	void method1();
	void method2();
	…
}

 

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

对于abstract class和interface在语法定义层面更多的细节问题,不是本文的重点,不再赘述,读者可以参阅参考文献〔1〕获得更多的相关内容。

 




回页首


从编程层面看abstract class和interface

 

从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。

 




回页首


从设计理念层面看abstract class和interface

 

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用abstract class方式定义Door:

	abstract class Door {
		abstract void open();
		abstract void close();
}

 

使用interface方式定义Door:

interface Door {
		void open();
	void close();
}

 

其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。

解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

	abstract class Door {
		abstract void open();
		abstract void close();
		abstract void alarm();
}

 

或者

interface Door {
		void open();
	void close();
	void alarm();
}

 

那么具有报警功能的AlarmDoor的定义方式如下:

	class AlarmDoor extends Door {
		void open() { … }
    		void close() { … }
		void alarm() { … }
}

 

或者

	class AlarmDoor implements Door {
	void open() { … }
    		void close() { … }
		void alarm() { … }
}

 

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:

	abstract class Door {
		abstract void open();
		abstract void close();
	}
interface Alarm {
	void alarm();
}
class AlarmDoor extends Door implements Alarm {
	void open() { … }
	void close() { … }
   	void alarm() { … }
}

 

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

 




回页首


结论

 

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。


参考资料

[1] Thinking in Java, Bruce Eckel

[2] Design Patterns Explained: A New Perspective on Object-Oriented Design, Alan Shalloway and James R. Trott

[3] Effective C++: 50 Specific Ways to Improve Your Programs and Design, Scott Meyers

package com.weishitechsub.quanminchangKmianfei.fragment.home; import android.Manifest; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.CircleCrop; import com.hfd.common.base.BaseFragment; import com.hfd.common.net.HttpBuiler; import com.hfd.common.net.JsonGenericsSerializator; import com.hfd.common.net.Url; import com.hfd.common.util.DensityUtil; import com.hfd.common.util.ToastUtil; import com.hw.lrcviewlib.LrcRow; import com.hw.lrcviewlib.LrcView; import com.weishitechsub.quanminchangKmianfei.bean.MusicBean; import com.weishitechsub.quanminchangKmianfei.dialog.PermissionDialog; import com.weishitechsub.quanminchangKmianfei.utils.MusicCache; import com.weishitechsub.quanminchangKmianfei.utils.MusicPlayerManager; import com.weishitechsub.quanminchangKmianfei.utils.PermissionUtils; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import pub.devrel.easypermissions.EasyPermissions; import com.hfd.common.net.GenericsCallback; import com.weishitechsub.quanminchangKmianfei.R; import com.weishitechsub.quanminchangKmianfei.utils.OnMultiClickListener; import okhttp3.Call; /** * 高端语音导航首页 */ public class HomeFragment extends BaseFragment implements EasyPermissions.PermissionCallbacks{ ImageView iv_song,iv_xh,iv_sys,iv_bf,iv_xys,iv_xz; TextView tv_start_time,tv_end_time,tv_no_lyric,tv_title,tv_singer; LrcView lyricView; SeekBar seekbar; private List<MusicBean.DataBean> restList = new ArrayList<>(); private List<MusicBean.DataBean> musicList; // 存储获取到的歌曲列表 private ObjectAnimator rotationAnimator; private boolean isRotating = false; // 记录当前是否正在旋转 private boolean shouldPauseWhenBackground = true; // 默认返回后台时暂停 private static final String[] PERMS_CAMERA = new String[] {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}; // 需要请求的权限数组 private static final int RC_CAMERA_PERM = 123; // 请求码,唯一标识 private static final int REQUEST_CODE_WRITE_SETTINGS = 101; private AlertDialog progressDialog; private Handler handler; private Runnable updateProgressRunnable; @Override protected int setLayout() { return R.layout.fragment_home; } @Override protected void initView() { iv_song = fvbi(R.id.iv_song); lyricView = fvbi(R.id.lyricView); tv_start_time = fvbi(R.id.tv_start_time); seekbar = fvbi(R.id.seekbar); tv_end_time = fvbi(R.id.tv_end_time); iv_xh = fvbi(R.id.iv_xh); iv_sys = fvbi(R.id.iv_sys); iv_bf = fvbi(R.id.iv_bf); iv_xys = fvbi(R.id.iv_xys); iv_xz = fvbi(R.id.iv_xz); tv_no_lyric = fvbi(R.id.tv_no_lyric); tv_title = fvbi(R.id.tv_title); tv_singer = fvbi(R.id.tv_singer); // 设置最大值为100 seekbar.setMax(100); initLyricStyle(); // 初始化旋转动画 initRotationAnimation(); } @Override protected void initClick() { Log.d("HomeFragment", "initClick called at: " + System.currentTimeMillis()); iv_xh.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { toggleFavorite(); } }); iv_xz.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { // 设置:弹权限时不暂停音乐 shouldPauseWhenBackground = false; getSavePermissions(); } }); //下一首 iv_xys.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { playNext(); } }); //上一首 iv_sys.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { playPrevious(); } }); iv_bf.setOnClickListener(v -> togglePlayPause()); // 播放/暂停 seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { int duration = MusicPlayerManager.getInstance().getDuration(); int newPosition = (int) ((progress / 100.0) * duration); MusicPlayerManager.getInstance().seekTo(newPosition); } } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) {} }); } @Override protected void initData() { musicAppInfo(); } //收藏 private void toggleFavorite() { int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition(); if (pos == -1 || musicList == null || pos >= musicList.size()) { ToastUtil.showShortToast("当前无播放歌曲"); return; } MusicBean.DataBean song = musicList.get(pos); boolean isNowCollected = !song.isCollected(); // 更新歌曲状态 song.setCollected(isNowCollected); Context context = getContext(); if (context == null) return; if (isNowCollected) { MusicCache.addFavorite(context, song); ToastUtil.showShortToast("已收藏:" + song.getTitle()); } else { MusicCache.removeFavorite(context, song); ToastUtil.showShortToast("已取消收藏:" + song.getTitle()); } // 更新 UI 图标 updateFavoriteIcon(isNowCollected); } private void updateFavoriteIcon(boolean isCollected) { iv_xh.setImageResource(isCollected ? R.mipmap.img_home_like_u : R.mipmap.img_xh); } private void musicAppInfo() { HashMap<String, String> map = new HashMap<>(); showDialog(); HttpBuiler.getInfo(Url.music, map).build().execute( new GenericsCallback<MusicBean>(new JsonGenericsSerializator()) { @Override public void onError(Call call, Exception e, int id) { dissmiss(); ToastUtil.showShortToast(e.getMessage()); } @Override public void onResponse(MusicBean response, int id) { dissmiss(); if ("0".equals(response.getCode()) && response.getData() != null && !response.getData().isEmpty()) { restList = response.getData(); // 获取第三条之后的数据(即从索引2开始到末尾) musicList = new ArrayList<>(restList.subList(3, restList.size())); MusicCache.setMainMusicList(musicList); // 缓存主列表 startPlaybackIfNeeded(); } else { ToastUtil.showShortToast("暂无歌曲数据"); } } }); } /** * 设置歌词样式 */ private void initLyricStyle(){ // 应用样式 lyricView.getLrcSetting() .setTimeTextSize(40) .setSelectLineColor(Color.parseColor("#FFFFFFFF")) .setSelectLineTextSize(25) .setNormalRowColor(Color.parseColor("#919191")) .setHeightRowColor(Color.parseColor("#FFFFFFFF")) .setNormalRowTextSize(DensityUtil.sp2px(getContext(), 17)) .setHeightLightRowTextSize(DensityUtil.sp2px(getContext(), 17)) .setTrySelectRowTextSize(DensityUtil.sp2px(getContext(), 17)) .setTimeTextColor(Color.parseColor("#FFFFFFFF")) .setTrySelectRowColor(Color.parseColor("#FFFFFFFF")); lyricView.commitLrcSettings(); } /** * 旋转动画 */ private void initRotationAnimation() { // 创建围绕 Z 轴旋转的动画 rotationAnimator = ObjectAnimator.ofFloat(iv_song, "rotation", 0f, 360f); rotationAnimator.setDuration(10000); // 一圈10秒,可根据需要调整速度 rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE); // 无限循环 rotationAnimator.setInterpolator(new LinearInterpolator()); // 匀速转动 } /** * 如果还没有播放,则自动播放第一首 */ private void startPlaybackIfNeeded() { if (musicList == null || musicList.isEmpty()) return; // 默认从第0首开始播放(如果还没播放过) if (MusicPlayerManager.getInstance().getCurrentPlayingPosition() == -1) { playMusicByPosition(0); } else { updateUIWithCurrentSong(); // 否则刷新当前 UI } setupUpdateProgressWithHandler(); // 开始定时更新进度 } /** * 播放指定位置的歌曲 */ private void playMusicByPosition(int position) { if (musicList == null || position < 0 || position >= musicList.size()) { return; // 越界防护 } MusicBean.DataBean song = musicList.get(position); String url = song.getMusic(); // 假设 getMusic() 返回音频 URL MusicPlayerManager.getInstance().play(url, position); } /** * 上一首 */ private void playPrevious() { if (musicList == null || musicList.isEmpty()) return; int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition(); Log.d("HomeFragment", "Current pos before prev: " + pos); int prevPos = (pos - 1 + musicList.size()) % musicList.size(); // 循环上一首 playMusicByPosition(prevPos); } /** * 下一首 */ private void playNext() { if (musicList == null || musicList.isEmpty()) return; int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition(); Log.d("HomeFragment", "Current pos before next: " + pos); int nextPos = (pos + 1) % musicList.size(); // 循环下一首 playMusicByPosition(nextPos); } /** * 播放/暂停切换 */ private void togglePlayPause() { if (MusicPlayerManager.getInstance().isPlaying()) { MusicPlayerManager.getInstance().pause(); } else { MusicPlayerManager.getInstance().resume(); } } /** * 更新 UI 显示当前歌曲信息(包括歌词、标题等) */ @SuppressLint("StaticFieldLeak") private void updateUIWithCurrentSong() { MusicBean.DataBean currentSong = MusicPlayerManager.getInstance().getCurrentPlayingSong(); if (currentSong == null) return; // 更新收藏图标 boolean isCollected = MusicCache.isFavorite(currentSong); currentSong.setCollected(isCollected); // 同步状态 updateFavoriteIcon(isCollected); String lrcText = currentSong.getLrc(); tv_title.setText(currentSong.getTitle()); tv_singer.setText(currentSong.getSinger()); // 使用 Glide 加载封面 Glide.with(getContext()) .load(currentSong.getCover()) .transform(new CircleCrop()) .into(iv_song); new AsyncTask<String, Void, List<LrcRow>>() { @Override protected List<LrcRow> doInBackground(String... params) { return new LrcDataBuilder().builtFromText(params[0]); } @Override protected void onPostExecute(List<LrcRow> rows) { if (rows != null && !rows.isEmpty()) { lyricView.setLrcData(rows); tv_no_lyric.setVisibility(View.GONE); } else { lyricView.setLrcData(null); tv_no_lyric.setVisibility(View.VISIBLE); } } }.execute(lrcText); // 更新时间(模拟或通过 MediaPlayer 获取) int duration = MusicPlayerManager.getInstance().getDuration(); tv_end_time.setText(formatTime(duration)); } /** * 定时更新播放进度条歌词 */ private void setupUpdateProgressWithHandler() { if (handler == null) { handler = new Handler(Looper.getMainLooper()); } // 定义循环任务 updateProgressRunnable = new Runnable() { @Override public void run() { int currentPosition = MusicPlayerManager.getInstance().getCurrentPosition(); int duration = MusicPlayerManager.getInstance().getDuration(); if (duration > 0) { // 更新进度条 int progress = (int) (((float) currentPosition / duration) * 100); seekbar.setProgress(progress); // 更新时间文本 tv_start_time.setText(formatTime(currentPosition)); //通知 LrcView 当前播放时间(毫秒) lyricView.seekLrcToTime(currentPosition); } // 自动重复执行(每隔 500ms) handler.postDelayed(this, 500); } }; // 开始执行 handler.post(updateProgressRunnable); } /** * 格式化毫秒时间为 mm:ss */ private String formatTime(int timeMs) { int totalSeconds = timeMs / 1000; int minutes = totalSeconds / 60; int seconds = totalSeconds % 60; return String.format("%02d:%02d", minutes, seconds); } /** * 绑定播放器状态监听器 */ @Override public void onResume() { super.onResume(); MusicPlayerManager.getInstance().setOnPlaybackStateChangedListener(playbackListener); if (musicList != null) { // 更新当前歌曲信息播放按钮图标 updateUIWithCurrentSong(); updatePlayButtonState(MusicPlayerManager.getInstance().isPlaying()); boolean isPlaying = MusicPlayerManager.getInstance().isPlaying(); updatePlayButtonState(isPlaying); // 如果正在播放,则恢复旋转 if (isPlaying) { startRotation(); } else { pauseRotation(); } // 恢复进度条更新 setupUpdateProgressWithHandler(); } } @Override public void onPause() { super.onPause(); // 明确判断当前是否正在播放,如果是,则暂停 // 仅当允许暂停时才真正 pause if (shouldPauseWhenBackground && MusicPlayerManager.getInstance().isPlaying()) { MusicPlayerManager.getInstance().pause(); Log.d("HomeFragment", "Fragment 不可见,已暂停音乐"); } pauseRotation(); // 离开界面时停止旋转 // 停止 UI 更新任务 if (handler != null && updateProgressRunnable != null) { handler.removeCallbacks(updateProgressRunnable); } // 移除播放状态监听(避免内存泄漏) MusicPlayerManager.getInstance().setOnPlaybackStateChangedListener(null); } @Override public void onStart() { super.onStart(); // 恢复进度条更新 setupUpdateProgressWithHandler(); boolean isPlaying = MusicPlayerManager.getInstance().isPlaying(); updatePlayButtonState(isPlaying); if (isPlaying) { startRotation(); } else { pauseRotation(); } // 重新绑定监听器 MusicPlayerManager.getInstance().setOnPlaybackStateChangedListener(playbackListener); } @Override public void onDestroy() { super.onDestroy(); MusicPlayerManager.getInstance().release(); // 防止内存泄漏 if (rotationAnimator != null) { rotationAnimator.cancel(); rotationAnimator = null; } if (handler != null) { handler.removeCallbacksAndMessages(null); // 清空所有消息 handler = null; } } // 播放状态监听器 private final MusicPlayerManager.OnPlaybackStateChangedListener playbackListener = new MusicPlayerManager.OnPlaybackStateChangedListener() { private static final int MAX_CONSECUTIVE_ERRORS = 3; @Override public void onPlaying(int position) { updatePlayButtonState(true); updateUIWithCurrentSong(); } @Override public void onPaused() { updatePlayButtonState(false); } @Override public void onCompletion() { playNext(); // 正常播放完成才切下一首 } @Override public void onError(String errorMsg) { // consecutiveErrorCount++; // // 显示一次错误提示即可 // if (consecutiveErrorCount == 1) { // ToastUtil.showShortToast("播放失败,跳过该歌曲"); // } // // // 防止无限循环:最多连续失败3次就停止 // if (consecutiveErrorCount >= MAX_CONSECUTIVE_ERRORS) { // ToastUtil.showShortToast("连续播放失败,已暂停"); // MusicPlayerManager.getInstance().pause(); // return; // } // 尝试播放下一首 playNext(); } }; /** * 更新播放按钮图标(播放/暂停) */ private void updatePlayButtonState(boolean isPlaying) { if (isPlaying) { iv_bf.setImageResource(R.mipmap.img_gc_zt); // 暂停图标 startRotation(); // 开始旋转 } else { iv_bf.setImageResource(R.mipmap.img_bf); // 播放图标 pauseRotation(); // 暂停旋转 } } /** * 开始旋转封面图 */ private void startRotation() { if (isRotating || rotationAnimator == null) return; // 计算从哪里开始(支持恢复) float currentRotation = iv_song.getRotation(); rotationAnimator.setFloatValues(currentRotation, currentRotation + 360f); rotationAnimator.start(); isRotating = true; } /** * 暂停旋转封面图 */ private void pauseRotation() { if (!isRotating || rotationAnimator == null) return; rotationAnimator.cancel(); // cancel 会保留当前角度 isRotating = false; } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } @Override public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) { if(requestCode == RC_CAMERA_PERM){ saveCurrentSong(); } // 恢复正常行为:离开界面时暂停 shouldPauseWhenBackground = true; } @Override public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) { // 即使拒绝,也恢复标志 shouldPauseWhenBackground = true; } // 内部歌词解析器 public static class LrcDataBuilder { // 加上 static public List<LrcRow> builtFromText(String lrcText) { List<LrcRow> rows = new ArrayList<>(); if (lrcText == null || lrcText.trim().isEmpty()) { return rows; } String[] lines = lrcText.split("\\r?\\n"); for (String line : lines) { line = line.trim(); if (line.isEmpty()) continue; List<LrcRow> parsed = parseLrcLine(line); if (!parsed.isEmpty()) { rows.addAll(parsed); } } Collections.sort(rows); return rows; } private List<LrcRow> parseLrcLine(String line) { List<LrcRow> result = new ArrayList<>(); if (line.isEmpty()) return result; // 支持多种换行符预处理(避免后续污染) line = line.replaceAll("[\\r\\n\\u2028\\u2029]+", " "); Pattern pattern = Pattern.compile("\\[(\\d{1,2}):(\\d{2})(?:\\.(\\d{2,3}))?\\]"); Matcher matcher = pattern.matcher(line); while (matcher.find()) { int min = Integer.parseInt(matcher.group(1)); int sec = Integer.parseInt(matcher.group(2)); int milli = 0; if (matcher.group(3) != null) { milli = Integer.parseInt(matcher.group(3)); if (matcher.group(3).length() == 2) milli *= 10; // 补齐毫秒 } long timeMillis = (min * 60L + sec) * 1000 + milli; int textStart = matcher.end(); int textEnd = line.length(); // 查找下一个时间标签作为结束点 Matcher nextMatcher = pattern.matcher(line); if (nextMatcher.find(matcher.start() + 1)) { textEnd = nextMatcher.start(); } String rawText = line.substring(textStart, textEnd).trim(); // 再次防御性清理各种空白与特殊字符 String text = rawText .replaceAll("[\\p{Cc}\\p{Cf}&&[^\\t]]", " ") // 移除控制字符 .replace((char) 0xA0, ' ') // 不间断空格 .replaceAll("\\s+", " ") // 多空格合并 .trim(); if (!text.isEmpty()) { result.add(new LrcRow(text, matcher.group(0), timeMillis)); } } return result; } } private void getSavePermissions() { // 检查并请求权限 if (!EasyPermissions.hasPermissions(getContext(), PERMS_CAMERA)) { new PermissionDialog(getActivity(),"权限说明:由于下载歌曲,需要相应的存储权限。拒绝将无法使用此功能").show(); EasyPermissions.requestPermissions(getActivity(), "需要相应的存储权限", RC_CAMERA_PERM, PERMS_CAMERA); } else { saveCurrentSong(); // 改为保存当前歌曲 } } private void saveCurrentSong() { int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition(); if (pos == -1) { ToastUtil.showShortToast("没有正在播放的歌曲"); return; } MusicBean.DataBean song = musicList.get(pos); if (song == null || song.getMusic() == null) { ToastUtil.showShortToast("歌曲信息不完整"); return; } showProgressDialog(getActivity()); new SaveTask(song).execute(); // 传入整个 song 对象更安全 } private void showProgressDialog(Context context) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage("正在保存..."); builder.setCancelable(false); progressDialog = builder.create(); progressDialog.show(); } private class SaveTask extends AsyncTask<Void, Void, Boolean> { private final MusicBean.DataBean song; private String targetPath; private Exception exception; SaveTask(MusicBean.DataBean song) { this.song = song; } @Override protected Boolean doInBackground(Void... voids) { try { String urlStr = song.getMusic(); String fileName = song.getTitle(); if (fileName == null) fileName = "music_" + System.currentTimeMillis(); File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); if (!dir.exists()) dir.mkdirs(); targetPath = new File(dir, fileName + ".mp3").getAbsolutePath(); InputStream inputStream = new java.net.URL(urlStr).openStream(); FileOutputStream outStream = new FileOutputStream(targetPath); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outStream.write(buffer, 0, bytesRead); } outStream.flush(); outStream.close(); inputStream.close(); // 通知媒体库刷新 Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); scanIntent.setData(Uri.fromFile(new File(targetPath))); getActivity().sendBroadcast(scanIntent); return true; } catch (Exception e) { e.printStackTrace(); exception = e; return false; } } @Override protected void onPostExecute(Boolean success) { if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); } if (success) { Toast.makeText(getActivity(), "音乐已保存至: " + targetPath, Toast.LENGTH_LONG).show(); } else { Toast.makeText(getActivity(), "保存失败: " + (exception != null ? exception.getMessage() : "未知错误"), Toast.LENGTH_SHORT).show(); } } } }package com.weishitechsub.quanminchangKmianfei.utils; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; import com.weishitechsub.quanminchangKmianfei.bean.MusicBean; import java.util.List; public class MusicPlayerManager { private boolean isPreparing = false; // 是否正在准备 private boolean isPrepared = false; // 是否已准备好 private static volatile MusicPlayerManager instance; // 双检锁 private MediaPlayer mediaPlayer; private OnPlaybackStateChangedListener listener; private int currentPlayingPosition = -1; // 当前主列表播放位置 private String currentPlayingUrl = null; private long currentRequestId = -1; // 请求ID,用于防止旧请求覆盖新状态 // 私有构造 private MusicPlayerManager() { // MediaPlayer 初始化延迟到首次使用 } // 获取单例 public static MusicPlayerManager getInstance() { if (instance == null) { synchronized (MusicPlayerManager.class) { if (instance == null) { instance = new MusicPlayerManager(); } } } return instance; } // 播放状态监听器 public interface OnPlaybackStateChangedListener { void onPlaying(int position); void onPaused(); void onCompletion(); void onError(String errorMsg); } public void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) { this.listener = listener; } /** * 播放主列表中的歌曲 */ public void play(@NonNull String musicUrl, int position) { Log.d("MusicPlayer", "pos: " + position); // ====== 取消之前的请求 ====== final long requestId = ++currentRequestId; // 更新状态(立即更新,供 UI 使用) this.currentPlayingPosition = position; this.currentPlayingUrl = musicUrl; // 停止并重置播放器 if (mediaPlayer != null) { try { if (mediaPlayer.isPlaying()) { mediaPlayer.stop(); } mediaPlayer.reset(); isPrepared = false; isPreparing = false; } catch (Exception e) { Log.e("MusicPlayer", "reset failed", e); release(); } } // 创建新的 MediaPlayer if (mediaPlayer == null) { mediaPlayer = new MediaPlayer(); setupListeners(); } // 准备播放(带 requestId 防止幽灵回调) prepareAndPlay(musicUrl, () -> { // 只有最新请求才通知播放开始 if (requestId == currentRequestId) { if (listener != null) { listener.onPlaying(position); } } else { Log.d("MusicPlayer", "Ignored outdated onPlaying callback for pos=" + position); } }); } /** * 内部方法:准备并播放音乐 */ private void prepareAndPlay(String musicUrl, Runnable onPreparedAction) { final long requestId = currentRequestId; // 捕获当前请求ID try { mediaPlayer.reset(); // 设置音频属性 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build()); } else { mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); } mediaPlayer.setDataSource(musicUrl); mediaPlayer.setOnPreparedListener(mp -> { // 只有最新请求才真正 start 回调 if (requestId == currentRequestId) { mp.start(); onPreparedAction.run(); } else { Log.d("MusicPlayer", "prepareAsync completed but request is outdated, ignored."); } }); mediaPlayer.prepareAsync(); } catch (Exception e) { Log.e("MusicPlayer", "prepareAsync error", e); if (listener != null && requestId == currentRequestId) { listener.onError("无法准备: " + e.getMessage()); } } } /** * 设置 MediaPlayer 回调监听 */ private void setupListeners() { mediaPlayer.setOnCompletionListener(mp -> { if (listener != null) { listener.onCompletion(); } }); mediaPlayer.setOnErrorListener((mp, what, extra) -> { if (listener != null) { listener.onError("播放错误: code=" + what + ", extra=" + extra); } return true; // 消费错误,不进入默认行为 }); } /** * 暂停播放 */ public void pause() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); if (listener != null) { listener.onPaused(); } } } /** * 继续播放 */ public void resume() { if (mediaPlayer != null && !mediaPlayer.isPlaying() && currentPlayingUrl != null) { mediaPlayer.start(); if (listener != null) { listener.onPlaying(currentPlayingPosition); } } } /** * 是否正在播放 */ public boolean isPlaying() { return mediaPlayer != null && mediaPlayer.isPlaying(); } /** * 获取当前播放的位置(主列表索引) */ public int getCurrentPlayingPosition() { return currentPlayingPosition; } /** * 获取当前播放的音乐 URL */ public String getCurrentPlayingMusicUrl() { return currentPlayingUrl; } /** * 获取当前播放的歌曲信息(从主列表中查找) */ public MusicBean.DataBean getCurrentPlayingSong() { List<MusicBean.DataBean> mainList = MusicCache.getMainMusicList(); if (mainList != null && currentPlayingPosition >= 0 && currentPlayingPosition < mainList.size()) { MusicBean.DataBean song = mainList.get(currentPlayingPosition); if (song != null && song.getMusic().equals(currentPlayingUrl)) { return song; } } // 回退:根据 URL 查找 if (mainList != null && currentPlayingUrl != null) { for (int i = 0; i < mainList.size(); i++) { MusicBean.DataBean song = mainList.get(i); if (song != null && song.getMusic().equals(currentPlayingUrl)) { currentPlayingPosition = i; // 自动修正索引 return song; } } } return null; } /** * 获取当前播放列表(即主列表) */ public List<MusicBean.DataBean> getCurrentPlayingList() { return MusicCache.getMainMusicList(); } /** * 跳转到指定时间 */ public void seekTo(int milliseconds) { if (mediaPlayer != null && mediaPlayer.isPlaying()) { try { mediaPlayer.seekTo(milliseconds); } catch (IllegalStateException e) { // 当前未初始化或未准备好 Log.e("MusicPlayer", "seekTo failed: not prepared", e); } } } /** * 获取当前播放进度(毫秒) */ public int getCurrentPosition() { if (mediaPlayer != null) { try { return mediaPlayer.getCurrentPosition(); } catch (IllegalStateException e) { return 0; } } return 0; } /** * 获取音频总时长(毫秒) */ public int getDuration() { if (mediaPlayer != null) { try { return mediaPlayer.getDuration(); } catch (IllegalStateException e) { return 0; } } return 0; } /** * 释放 MediaPlayer 资源 */ public void release() { if (mediaPlayer != null) { try { if (mediaPlayer.isPlaying()) { mediaPlayer.stop(); } } catch (Exception e) { // 忽略异常 } finally { mediaPlayer.release(); mediaPlayer = null; } } currentPlayingPosition = -1; currentPlayingUrl = null; } } package com.weishitechsub.quanminchangKmianfei.utils; import android.view.View; public abstract class OnMultiClickListener implements View.OnClickListener{ // 两次点击按钮之间的点击间隔不能少于1000毫秒 private static final long MIN_CLICK_DELAY_TIME = 1000; private static long lastClickTime; public abstract void onMultiClick(View v); @Override public void onClick(View v) { long curClickTime = System.currentTimeMillis(); if((curClickTime - lastClickTime) >= MIN_CLICK_DELAY_TIME) { // 超过点击间隔后再将lastClickTime重置为当前点击时间 lastClickTime = curClickTime; onMultiClick(v); } } }还是点击一次025-12-10 18:46:07.820 16467-16467 HomeFragment com...itechsub.quanminchangKmianfei D Current pos before next: 2 2025-12-10 18:46:16.805 16467-16467 HomeFragment com...itechsub.quanminchangKmianfei D Current pos before next: 3 2025-12-10 18:46:16.892 16467-16467 HomeFragment com...itechsub.quanminchangKmianfei D Current pos before next: 4,pos2,3,4瞬间打印
12-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值