0.Android高仿网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
Posted 爱上学习啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0.Android高仿网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM相关的知识,希望对你有一定的参考价值。
效果
0.系列文章目录
因为目录比较多,每次更新这里比较麻烦,所以推荐点击到主页,然后查看android云音乐专栏。
1.项目简介
这是一个使用Java(以后还会推出Kotlin版本)语言,从0开发一个Android平台,接近企业级的项目(我的云音乐),包含了基础内容,高级内容,项目封装,项目重构等知识;主要是使用系统功能,流行的第三方框架,第三方服务,完成接近企业级商业级项目。
2.项目功能点
隐私协议对话框
启动界面和动态处理权限
引导界面和广告
轮播图和侧滑菜单
首页复杂列表和列表排序
音乐播放和音乐列表管理
全局音乐控制条
桌面歌词和自定义样式
全局媒体控制中心
评论和回复评论
评论富文本点击
评论提醒人和话题
朋友圈动态列表和发布
高德地图定位和路径规划
阿里云OSS上传
视频播放和控制
QQ/微信登录和分享
商城/购物车\\微信\\支付宝支付
文本和图片聊天
消息离线推送
自动和手动检查更新
内存泄漏和优化
…
3.开发环境概述
2022年5月开发完成的,所以全部都是最新的,平均每3年会重新制作,现在已经是第三版了。
JDK17
Android 12/13
最低兼容版本:Android 6.0
Android Studio 2021.1
4.编译和运行
用最新AS打开MyCloudMusicAndroidJava目录,然后等待完全编译成功,因为是企业级项目,所以第三方依赖很多,同时代码量也很多,所以必须要确认完全编译成功,才能运行。
5.项目目录结构
├── MyCloudMusicAndroidJava
│ ├── LRecyclerview //第三方Recyclerview框架
│ ├── LetterIndexView //类似微信通讯录字母索引
│ ├── app //云音乐项目
│ ├── build.gradle
│ ├── common.gradle //通用项目配置文件
│ ├── config //配置目录,例如签名
│ ├── glidepalette //Glide画板,用来从网络图片提取颜色
│ ├── gradle
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── keystore.properties
│ ├── local.properties
│ ├── settings.gradle
│ ├── super-j //公用Java语言扩展
│ ├── super-player-tencent //腾讯开源的超级播放器
│ ├── super-speech-baidu //百度语音识别
6.依赖框架
内容太多,只列出部分。
//分页组件版本
//这里可以查看最新版本:https://developer.android.google.cn/jetpack/androidx/releases/paging
def paging_version = "3.1.1"
//添加所有libs目录里面的jar,aar
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
//官方兼容组件,像AppCompatActivity就是该依赖里面的
implementation 'androidx.appcompat:appcompat:1.4.1'
//Material Design组件,像FloatingActionButton就是该依赖里面的
implementation 'com.google.android.material:material:1.4.0'
//官方提供的约束布局,像ConstraintLayout就是该依赖里面的
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
//UI框架,主要是用他的工具类,也可以单独拷贝出来
//https://qmuiteam.com/android/get-started
implementation 'com.qmuiteam:qmui:2.0.1'
//动态处理权限
//https://github.com/permissions-dispatcher/PermissionsDispatcher
implementation "com.github.permissions-dispatcher:permissionsdispatcher:4.8.0"
annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:4.8.0"
//api:依赖会传递到其他应用本模块的项目
implementation project(path: ':super-j')
...
//使用gson解析json
//https://github.com/google/gson
implementation 'com.google.code.gson:gson:2.9.0'
//自动释放RxJava相关资源
//https://github.com/uber/AutoDispose
implementation "com.uber.autodispose2:autodispose-androidx-lifecycle:2.1.1"
//banner轮播图框架
//https://github.com/youth5201314/banner
implementation 'io.github.youth5201314:banner:2.2.2'
//图片加载框架,还引用他目的是,coil有些功能不好实现
//https://github.com/bumptech/glide
implementation 'com.github.bumptech.glide:glide:+'
annotationProcessor 'com.github.bumptech.glide:compiler:+'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
//给控件添加未读消息数红点
//https://github.com/bingoogolapple/BGABadgeView-Android
implementation 'com.github.bingoogolapple.BGABadgeView-Android:api:1.2.0'
annotationProcessor 'com.github.bingoogolapple.BGABadgeView-Android:compiler:1.2.0'
//webview进度条
//https://github.com/youlookwhat/WebProgress
implementation 'com.github.youlookwhat:WebProgress:1.2.0'
//日志框架
//https://github.com/JakeWharton/timber
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation "androidx.media:media:+"
//和Glide配合处理图片
//可以实现很多效果
//模糊;圆角;圆
//我们这里是用它实现模糊效果
//https://github.com/wasabeef/glide-transformations
implementation 'jp.wasabeef:glide-transformations:+'
//圆形图片控件
//https://github.com/hdodenhof/CircleImageView
implementation 'de.hdodenhof:circleimageview:+'
//下载框架
//https://github.com/ixuea/android-downloader
implementation 'com.ixuea:android-downloader:3.0.0'
//阿里云oss
//官方文档:https://help.aliyun.com/document_detail/32043.html
//sdk地址:https://github.com/aliyun/aliyun-oss-android-sdk
implementation 'com.aliyun.dpa:oss-android-sdk:+'
//高德地图,这里引用的是3d
//https://lbs.amap.com/api/android-sdk/guide/create-project/android-studio-create-project#gradle_sdk
implementation 'com.amap.api:3dmap:+'
//定位功能
implementation 'com.amap.api:location:+'
//百度语音相关技术,目前主要用在收货地址编辑界面,语音输入收货地址
//https://ai.baidu.com/ai-doc/SPEECH/Pkgt4wwdx#%E9%9B%86%E6%88%90%E6%8C%87%E5%8D%97
implementation project(path: ':super-speech-baidu')
//TextView显示富文本,目前主要用在商品详情界面,显示富文本商品描述
//https://github.com/wangchenyan/html-text
implementation 'com.github.wangchenyan:html-text:+'
//Hutool是一个小而全的Java工具类库
// 通过静态方法封装,降低相关API的学习成本
// 提高工作效率,使Java拥有函数式语言般的优雅
//https://github.com/looly/hutool
implementation 'cn.hutool:hutool-all:5.7.14'
//支付宝支付
//https://opendocs.alipay.com/open/204/105296
implementation 'com.alipay.sdk:alipaysdk-android:+@aar'
//融云IM
//https://docs.rongcloud.cn/v4/5X/views/im/ui/guide/quick/include/android.html
implementation 'cn.rongcloud.sdk:im_lib:+'
//微信支付
//官方sdk下载文档:https://developers.weixin.qq.com/doc/oplatform/Downloads/Android_Resource.html
//官方集成文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5
implementation 'com.tencent.mm.opensdk:wechat-sdk-android:+'
//内存泄漏检测工具
//https://github.com/square/leakcanary
//只有调试模式下才添加该依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-android:+'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
7.用户协议对话框
使用自定义DialogFragment实现,内容是放到字符串文件中的,其中的链接是HTML标签,设置后就可以点击了,然后修改默认对话框宽度,因为默认的有点窄。
public class TermServiceDialogFragment extends BaseViewModelDialogFragment<FragmentDialogTermServiceBinding>
...
@Override
protected void initViews()
super.initViews();
//点击弹窗外边不能关闭
setCancelable(false);
SuperTextUtil.setLinkColor(binding.content, getActivity().getColor(R.color.link));
@Override
protected void initListeners()
super.initListeners();
binding.primary.setOnClickListener(view ->
dismiss();
onAgreementClickListener.onClick(view);
);
binding.disagree.setOnClickListener(view ->
dismiss();
SuperProcessUtil.killApp();
);
@Override
public void onResume()
super.onResume();
//修改宽度,默认比AlertDialog.Builder显示对话框宽度窄,看着不好看
//参考:https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height
ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
params.width = (int) (ScreenUtil.getScreenWith(getContext()) * 0.9);
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
8.动态权限
高版本必须要动态处理权限,这里在启动界面请求了一些权限,但推荐在用到的时候才获取,写法差不多,这里使用第三方框架实现,当然也可以直接使用系统API实现。
/**
* 权限授权了就会调用该方法
* 请求相机权限目的是扫描二维码,拍照
*/
@NeedsPermission(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
void onPermissionGranted()
//如果有权限就进入下一步
prepareNext();
/**
* 显示权限授权对话框
* 目的是提示用户
*/
@OnShowRationale(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
void showRequestPermission(PermissionRequest request)
new AlertDialog.Builder(getHostActivity())
.setMessage(R.string.permission_hint)
.setPositiveButton(R.string.allow, (dialog, which) -> request.proceed())
.setNegativeButton(R.string.deny, (dialog, which) -> request.cancel()).show();
/**
* 拒绝了权限调用
*/
@OnPermissionDenied(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
void showDenied()
//退出应用
finish();
/**
* 再次获取权限的提示
*/
@OnNeverAskAgain(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
void showNeverAsk()
//继续请求权限
checkPermission();
/**
* 授权后回调
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//将授权结果传递到框架
SplashActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
9.引导界面
引导界面比较简单,就是多个图片可以左右滚动,整体使用ViewPager+Fragment实现,也可以使用ViewPager2,后面有讲解。
/**
* 引导界面适配器
*/
public class GuideAdapter extends BaseFragmentStatePagerAdapter<Integer>
/***
* @param context 上下文
* @param fm Fragment管理器
*/
public GuideAdapter(Context context, @NonNull FragmentManager fm)
super(context, fm);
/**
* 返回当前位置Fragment
*
* @param position
* @return
*/
@NonNull
@Override
public Fragment getItem(int position)
return GuideFragment.newInstance(getData(position));
/**
* 引导界面Fragment
*/
public class GuideFragment extends BaseViewModelFragment<FragmentGuideBinding>
...
@Override
protected void initDatum()
super.initDatum();
int data = getArguments().getInt(Constant.ID);
binding.icon.setImageResource(data);
10.广告界面
实现图片广告和视频广告,广告数据是在首页是缓存到本地,目的是在启动界面加载更快,因为真实项目中,大部分项目启动页面广告时间一共就5秒,如果太长了用户体验不好,如果是从网络请求,那么网络可能就耗时2秒左右,所以导致就美哟多少时间显示广告了。
10.1下载广告
private void downloadAd(Ad data)
if (SuperNetworkUtil.isWifiConnected(getHostActivity()))
//wifi才下载
sp.setSplashAd(data);
//判断文件是否存在,如果存在就不下载
File targetFile = FileUtil.adFile(getHostActivity(), data.getIcon());
if (targetFile.exists())
return;
new Thread(
new Runnable()
@Override
public void run()
try
//FutureTarget会阻塞
//所以需要在子线程调用
FutureTarget<File> target = Glide.with(getHostActivity().getApplicationContext())
.asFile()
.load(ResourceUtil.resourceUri(data.getIcon()))
.submit();
//获取下载的文件
File file = target.get();
//将文件拷贝到我们需要的位置
FileUtils.moveFile(file, targetFile);
catch (Exception e)
e.printStackTrace();
).start();
10.2显示广告
/**
* 显示视频广告
*
* @param data
*/
private void showVideoAd(File data)
SuperViewUtil.show(binding.video);
SuperViewUtil.show(binding.preload);
//在要用到的时候在初始化,更节省资源,当然播放器控件也可以在这里动态创建
//设置播放监听器
//创建 player 对象
player = new TXVodPlayer(getHostActivity());
//静音,当然也可以在界面上添加静音切换按钮
player.setMute(true);
//关键 player 对象与界面 view
player.setPlayerView(binding.video);
//设置播放监听器
player.setVodListener(this);
//铺满
binding.video.setRenderMode(TXLiveConstants.RENDER_MODE_FULL_FILL_SCREEN);
//开启硬件加速
player.enableHardwareDecode(true);
player.startPlay(data.getAbsolutePath());
显示图片就是显示本地图片了,没什么难点,就不贴代码了。
11.首页/歌单详情/黑胶唱片界面
首页没有顶部是轮播图,然后是可以左右的菜单,接下来是热门歌单,推荐单曲,最后是首页排序模块;整体上使用RecycerView实现,轮播图:
Banner bannerView = holder.getView(R.id.banner);
BannerImageAdapter<Ad> bannerImageAdapter = new BannerImageAdapter<Ad>(data.getData())
@Override
public void onBindView(BannerImageHolder holder, Ad data, int position, int size)
ImageUtil.show(getContext(), (ImageView) holder.itemView, data.getIcon());
;
bannerView.setAdapter(bannerImageAdapter);
bannerView.setOnBannerListener(onBannerListener);
bannerView.setBannerRound(DensityUtil.dip2px(getContext(), 10));
//添加生命周期观察者
bannerView.addBannerLifecycleObserver(fragment);
bannerView.setIndicator(new CircleIndicator(getContext()));
推荐歌单
//设置标题,将标题放到每个具体的item上,好处是方便整体排序
holder.setText(R.id.title, R.string.recommend_sheet);
//显示更多容器
holder.setVisible(R.id.more, true);
holder.getView(R.id.more).setOnClickListener(v ->
);
RecyclerView listView = holder.getView(R.id.list);
if (listView.getAdapter() == null)
//设置显示3列
GridLayoutManager layoutManager = new GridLayoutManager(listView.getContext(), 3);
listView.setLayoutManager(layoutManager);
sheetAdapter = new SheetAdapter(R.layout.item_sheet);
//item点击
sheetAdapter.setOnItemClickListener(new OnItemClickListener()
@Override
public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position)
if (discoveryAdapterListener != null)
discoveryAdapterListener.onSheetClick((Sheet) adapter.getItem(position));
);
listView.setAdapter(sheetAdapter);
GridDividerItemDecoration itemDecoration = new GridDividerItemDecoration(getContext(), (int) DensityUtil.dip2px(getContext(), 5F));
listView.addItemDecoration(itemDecoration);
sheetAdapter.setNewInstance(data.getData());
11.1歌单详情
顶部是歌单信息,通过header实现,底部是列表,显示歌单内容的音乐,点击音乐进入黑胶唱片播放界面。
//添加头部
adapter.addHeaderView(createHeaderView());
/**
* 显示数据的方法
*
* @param holder
* @param data
*/
@Override
protected void convert(@NonNull BaseViewHolder holder, Song data)
//显示位置
holder.setText(R.id.index, String.valueOf(holder.getLayoutPosition() + offset));
//显示标题
holder.setText(R.id.title, data.getTitle());
//显示信息
holder.setText(R.id.info, data.getSinger().getNickname());
if (offset != 0)
holder.setImageResource(R.id.more, R.drawable.close);
holder.getView(R.id.more)
.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
SuperDialog.newInstance(fragmentManager)
.setTitleRes(R.以上是关于0.Android高仿网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM的主要内容,如果未能解决你的问题,请参考以下文章
0.Android高仿网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM
高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM