DC音乐播放器开发日记
DC音乐播放器开发日记
MediaPlayer介绍:
MediaPlayer的状态转换图也表征了它的生命周期,搞清楚这个图可以帮助我们在使用MediaPlayer时考虑情况更周全,写出的代码也更具健壮性。
这张状态转换图清晰的描述了MediaPlayer的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常。
Idle 状态:当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态。这两种方法的一个重要差别就是:如果在这个状态下调用了getDuration()等方法(相当于调用时机不正确),通过reset()方法进入idle状态的话会触发OnErrorListener.onError(),并且MediaPlayer会进入Error状态;如果是新创建的MediaPlayer对象,则并不会触发onError(),也不会进入Error状态。
End 状态:通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉,以释放相关的软硬件组件资源,这其中有些资源是只有一份的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会在进入任何其他状态了。
Initialized 状态:这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。
Prepared 状态:初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。
Preparing 状态:这个状态比较好理解,主要是和prepareAsync()配合,如果异步准备完成,会触发OnPreparedListener.onPrepared(),进而进入Prepared状态。
Started 状态:显然,MediaPlayer一旦准备好,就可以调用start()方法,这样MediaPlayer就处于Started状态,这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态,类似的,如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。
Paused 状态:Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer,从而进入Paused状态,MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放,转到Started状态,暂停状态时可以调用seekTo()方法,这是不会改变状态的。
Stop 状态:Started或者Paused状态下均可调用stop()停止MediaPlayer,而处于Stop状态的MediaPlayer要想重新播放,需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。
PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。
Error状态:如果由于某种原因MediaPlayer出现了错误,会触发OnErrorListener.onError()事件,此时MediaPlayer即进入Error状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener)可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到Idle状态。
参考文档:AndroidSDK1.5官方文档:android-sdk-windows-1.5_r3/docs/reference/android/media/MediaPlayer.html
2016-3-31
总结:
用Application存储系统级的全局变量,这样每个activity都可以得到service的实例,然后对音乐进行操作了
//onBackPressed()会在其它activity在上面的时候也执行(这条是错的,见4-4)
2016-4-1
总结:
create table favorite (id integer primary key autoincrement,name text,artist text,album text,path text,order integer)
order是sqlite的关键字,不能用,所以我改成了ordered
工作:
增加了“我喜欢”功能,将喜欢的本地音乐添加到我喜欢列表
2016-4-4
总结:
遇到了点击LyricActivity的返回就退出的bug,从Activity的生命周期去考虑,最后发现是在onStop()方法里的解除注册广播接收器的部分,解除了一个从来没有注册过的广播造成的。说明android的生命周期的概念很重要。
两种方法实现进度条的更新:
一种是service发出广播(这种方法貌似会很坑,因为一直在更新进度条,所以一直在发送广播,可是我偏就采用广播来做,手机性能好,任性,你来打我呀~);
一种是使用回调接口,service里执行onMusicEventListener接口定义的函数,具体的更新进度条的方法在activity执行到onResume时再通过setOnMusicEventListener()设置回调接口(LitePlayer很机智的搞了个抽象类BaseActivity,然后再让那些跟音乐播放有关的activity继承它,在BaseActivity里绑定service,然后在绑定成功后设置监听器,监听器的函数执行自己的onPublish和onChange,然后这两个方法再在继承它的具体的activity子类里具体的定义执行内容,这是最骚的。。)
还有就是LitePlayer的service里,更新进度的线程是在service的onCreate方法里就开启的,而我的更新线程是在play函数里开始的,就是第一次点击播放音乐后开启了线程,并且他用了一个ExcutorService.excute(Runnable)(不知道是什么黑魔法- -||),而我用的是timer.schedule(TimerTask)(貌似TimerTask也是实现了Runnable接口,殊途同归?不过ExcutorService涉及到线程池ThreadPool什么的,不懂,以后再说吧。。。)
啊,不一样,想明白了,我是每次播放一个新音乐都会执行play函数,然后就会创建一个新的timer,然后执行新的TimerTask,这样会不会浪费内存?但是java不是有垃圾回收器吗?
2016-4-5
目标:
了解昨天cv大法解决的增加导航菜单的具体执行原理,
学会LitePlayer从音乐文件中提取图片的方法,
了解动画(Animation)的使用方法,并且试着加在我的播放器里
了解LitePlayer可以搜索音乐的原理,并且试着加在我的播放器里
试着增加豆瓣的API功能(增加功能关于歌曲,关于歌手,关于专辑)和新浪网盘的功能到APP里。
结果:
理想很丰满,现实却很骨感。今天由于有各种事(点名,上课),导致没有研究太多,把导航菜单的原理原理研究了个大概。界面布局打算重新用Material Design风格重做,仿照bilibili,bilibili在界面美观程度上真的没的说,秒杀大多数现在的APP。
2016-4-6
目标和昨天的一样。
有点小小的完成了类似MVC思想的功能,点击导航菜单某一项就显示选中该项
//这里有点MVC模式的意思,item是M,holder是V,onBindViewHolder是C,
//item保存有这一项是否被选中的属性,holder用来控制这一项的背景色,
// onBindViewHolder函数通过获取item的属性,来确定holder应该执行哪种动作
增加导航菜单总结:
Main:
Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(myToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
这一步设置好ActionBar,然后让ActionBar有了可以按出导航菜单的按钮,然后获取到drawerFragment,执行drawerFragment的setUp函数(自定义的函数,具体的增加导航菜单的步骤都在里面),然后加了个监听器,监听导航菜单里按下某个项目的操作。
DrawerFragment:
onCreateView里,找到RecyclerView,然后给它设置adapter,然后recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));(这是干啥的我现在也不清楚),然后给它加按键监听,封装了三层
//这个自己创建的RecyclerTouchListener把原来的接口变成了新的接口,变成了ClickListener,这是典型的adapter pattern啊同学们
//然后这个ClickListener的onClick函数又执行drawerListener的函数,吗的这个drawerListener也是我们自己创建的
//所以这个ClickListener又变成了FragmentDrawerListener的接口,最后再外面就直接设置drawerFragment.setDrawerListener(this);
//RecyclerTouchListener -> ClickListener 是为了把那些复杂的接口函数转换成简单的onClick函数
//ClickListener -> FragmentDrawerListener 是为了封装mDrawerLayout.closeDrawer(containerView)收起菜单的动作
setUp():
回到setUp函数中,定义了一个ActionBarDrawerToggle(这才是核心,是最骚的),定义好了以后,这逼的构造函数里貌似会自己去找gravity = “start”的组件,然后滑动出来,所以不用设置,我黑盒测试了一个小时,日你哥。
mDrawerToggle = new ActionBarDrawerToggle(getActivity(), drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close)//然后在这里在装有DrawerFragment的DrawerLayout里,设置了监听器,setDrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
//下面这一步开始了一个线程,执行了一个函数,不知道是干嘛的,
// 测试了下,应该是那个按钮的动画,检测有没有被按到,如果被按到就执行动画效果
mDrawerLayout.post(new Runnable() {@Overridepublic void run() {mDrawerToggle.syncState();}
});
综上,其实就是ActionBarDrawerToggle这个东西产生了导航菜单,有空可以看下源码。
2016-4-7
学会LitePlayer从音乐文件中提取图片的方法,
了解动画(Animation)的使用方法,并且试着加在我的播放器里
了解LitePlayer可以搜索音乐的原理,并且试着加在我的播放器里
试着增加豆瓣的API功能(增加功能关于歌曲,关于歌手,关于专辑)和新浪网盘的功能到APP里。
结果:
完成了从音乐文件中提取图片的功能,在完成的过程中又解决了不少的坑。
从音乐文件中提取图片,首先在MusicInfo实体类中增加一个String Image,然后在初始化musicList的时候增加对这个成员变量的设置(具体是从MediaStore数据库中取出来albumId,然后根据albumId去另一个表里找出音乐文件图片的地址),然后发现我的musicList不是全局统一的,而是几个类之间互相在传,这样的话耦合性太高,所以我新建了一个MusicUtils类用来保存musicList和以后可能会对musicList进行的一些操作。这样相互之间就不必设置musicList,而是统一用一个。
在设置service里发出广播的时候,我打算把提取出的bitmap图片放入广播的intent里,然后发送过去,debug时发现这样的话广播接收器不会收到这个广播,也不知道是为啥。。。
所以我改成了广播发送正在播放的音乐在musicList中的序号,然后在广播接收函数中进行更新控制界面的操作,更新控制界面的操作引入了一个叫MusicIconLoader的类,用来载入音乐的专辑图片。
MusicIconLoader类,
private LruCache<String,Bitmap> mCache;
搞了个LruCache缓存(高大上- -||),然后给缓存设置了最大值为总内存的1/8。
MusicIconLoader类有一个load函数用来根据Image地址来获取歌曲专辑图片bitmap,显示把Image地址提取MD5码,将MD5码作为键,在cache中找到对应的值(bitmap),如果找到了,就直接返回;否则,再根据Image地址从地址中载入bitmap,然后将其加入到缓存中。(通过搞了这么一个缓存的机制,如果缓存中有东西,则省略了从地址中载入bitmap的费时步骤,我估摸着那些高大上的图片缓存框架picasso啥的基本原理应该也就是个这把,以后可以读下那些开源库的源码看看)。
最后,在获取到bitmap以后,我们想要更新控制界面还需要一步,因为这些图片的尺寸都是不统一的,所以又增加了一个新的工具类—ImageTools,用来对图片进行缩放,全部缩放的统一的大小尺寸,Common.sScreenWidth * 0.13)
统一缩放到长宽都为屏幕大小的0.13倍,然后将缩放完成的bitmap返回,设置成控制界面的专辑图片。
2016-4-8
有事,什么都没干。
2016-4-9
目标:
了解动画(Animation)的使用方法,并且试着加在我的播放器里
修改播放器的布局,仿照phonograph来做
了解LitePlayer可以搜索音乐的原理,并且试着加在我的播放器里
试着增加豆瓣的API功能(增加功能关于歌曲,关于歌手,关于专辑)和新浪网盘的功能到APP里。
结果:
LitePlayer使用的动画很简单,就是自己写了个xml文件做了透明度渐变的过程,然后对view执行mPopShownView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.layer_show_anim));
很简单,没有代码定义的动画效果,至于复杂的自定义的动画效果以后再在其它的开源控件库的源码里去学习吧。
2016-4-10
目标:
修改播放器的布局,仿照phonograph来做
了解LitePlayer可以搜索音乐的原理,并且试着加在我的播放器里
试着增加豆瓣的API功能(增加功能关于歌曲,关于歌手,关于专辑)和新浪网盘的功能到APP里。
自制LyricView,实现类似LitePlayer的毛玻璃效果。
结果:
采用了github上的开源库com.ogaclejapan.smarttablayout把原来的布局改成了TabLayout的布局,然后我们的问题就出现了,原来是在activity的onCreate函数里执行绑定service,然后service异步绑定完成,当我们点击本地音乐后才打开新的Fragment显示音乐列表,然而这时service早已异步绑定完毕,所以我们后续在Fragment里对service执行任何操作都没有问题。可是当我改成了这种TabLayout后,这些Fragment其实是在activity的onCreate执行前先创建的,所以出现了service还没有绑定完的坑。我开始回忆自己问什么要在MusicListFragment里会需要service,原来是因为要给service传musicList和adapter,前面的那个已经不需要了,因为我们之前已经解决;后面那个其实就是需要adapter来notifyDatasetChaged,播放音乐时显示哪个音乐在播放中。这样貌似耦合度又太高了(?)所以我改成了在点击列表事件时就更新列表的显示。解除掉了service和Fragment之间藕断丝连的联系。
还有一个坑就是生命周期的问题,我在Fragment的构造函数里写了mApp = (Common) getActivity().getApplicationContext();返回空指针错误,发现构造函数构造Fragment时还没和activity绑定呢,所以查看生命周期,在onAttach之后都可以正确的getActivity了。
重新归拢一下各个主要类执行的功能:
Main类:
负责绑定service,设置各种view以及点击事件(包括导航菜单,播放控制界面),还有收到service发来的广播后对播放控制界面的显示更新(进度条,播放暂停)。
MusicService类:
负责控制MediaPlayer,主要负责控制音乐的播放,暂停,上一曲,下一曲等,并且发送广播通知当前的音乐和进度。
MusicListFragment类:
负责显示本地音乐,从数据库中找到本地音乐列表,并且通过adapter适配到listView上。控制listview的点击事件和显示更新操作。
MusicService通过广播来通知其它类相关的消息执行相关的操作,其它类通过application全局变量获取MusicService实例来控制音乐的播放。
大致了解了LitePlayer搜索音乐的原理,使用了百度MP3的搜索引擎,使用了jsoup库解析html,大概的步骤就和上网页搜索一个音乐一样,弄好网址,get方式,各种参数,然后再接收到一个document里,通过jsoup这个库来对接收到的html文件进行解析,得到搜索的结果,然后再显示到listview上。
2016-4-11
目标:
了解LitePlayer可以搜索音乐的原理,并且试着加在我的播放器里
采用虾米音乐作为服务器端,增加网络搜索音乐的页面,增加播放在线音乐的功能
试着增加豆瓣的API功能(增加功能关于歌曲,关于歌手,关于专辑)和新浪网盘的功能到APP里。
自制LyricView,实现类似LitePlayer的毛玻璃效果。
结果:
花了很大的时间去配置一个开源的播放器JamsMusicPlayer,结果还他妈失败了(哭T.T),然后又下了一个google官方的UniversalMusicPlayer,又花费了大量的时间去解决环境问题,最后总算是能够在虚拟机上跑了,在平板上不知道为何跑不成(?),然后阅读了google播放器的源码,了解了它的导航菜单相关的实现方法,比较巧妙(弄了一个Activity基类,然后所有需要导航菜单的Activity都去继承它,这样大家就都可以统一的去设置toolbar和导航菜单以及相应的事件,UniversalMusicPlayer是在导航菜单收起的监听器回调函数中检测一些参数从而确定跳转的页面,而我们这里是给recyclerView设置点击事件响应来跳转页面,而且不同的是UniversalMusicPlayer没有用recycler,而是用了一个support包的函数android.support.design.widget.NavigationView)
2016-4-13
目标:
采用虾米音乐作为服务器端,增加网络搜索音乐的页面,增加播放在线音乐的功能
试着增加豆瓣的API功能(增加功能关于歌曲,关于歌手,关于专辑)和新浪网盘的功能到APP里。
自制LyricView,实现类似LitePlayer的毛玻璃效果。(学习下自定义view的方法)
结果:
增加了导航菜单并且又增加了一个虾米音乐搜索的页面后,发现所有有导航菜单的Activity有很多共同的设置导航菜单的代码,觉得自己每个页面都这么写并不科学,太蠢了。遂模仿了下UniversalMusicPlayer的设计方式,给这些Activity都弄了一个父类BaseActivity,将公共的关于导航菜单的代码弄到父类里,不同的部分再在各自继承后的子类里写。
仿着LitPlayer的样子稍微修改了一下searh_music_activity的布局,点击文本框才会出现输入框(小改动而已)。在util里增加了SearchMusic类,单例模式,用来处理搜索音乐的相关事务。
SearchMusic:
单例模式,执行search函数创建新的线程用来向虾米音乐发出http请求(必须创建新线程,如果在UI线程这么搞的话救版本会卡,新版本会出错,还记得做毕设那年被socket支配了一天一夜的恐惧吗?)PS:我们这里也学人家LitePlayer弄了个高大上的线程池,ExecutorService mThreadPool = Executors.newSingleThreadExecutor();(我猜是为了单例模式吧,大概。。)
搜索的url地址为:
http://www.xiami.com/search/song?key=龙卷风
这是我们解析的html页面,可以看到,在input的value属性里藏着我们需要的id号,而解析html页面可以使用开源的库jsoup。
/*** 根据关键字找到搜索列表,然后把每项的id记录下来* @param key*/
private void getMusicList(String key){key = EnCodeKey(key);try {Document doc = Jsoup.connect(KEY_SEARCH_URL + key).timeout(60 * 1000).get();//这里用elements保存了类名是track_list的元素,但其实类名是track_list的元素也就一个Elements elements = doc.getElementsByClass("track_list");if (elements.size() != 0){//所以这里取出第一个元素,然后再在取出来的部分里取出类名为"chkbox"的元素Elements all = elements.get(0).getElementsByClass("chkbox");int size = all.size();for (int i = 0;i < size;i++){//然后再每一个chkbox中,都有子控件input,从input的属性value中可以取出我们想要的idString id = all.get(i).select("input").attr("value");if (!TextUtils.isEmpty(id)){Ids.add(id);}}}} catch (IOException e) {e.printStackTrace();}
}
注意到这里有个EncodeKey()函数
private static String EnCodeKey(String key) {try {return URLEncoder.encode(key,"UTF-8");//return URLDecoder.decode(key,"UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();return null;}
}
这也是个小坑,我原来在网上找的那个哥们用反了
private static String deCondeKey(String input) {try {String key = URLEncoder.encode(input, "UTF-8");return key;} catch (UnsupportedEncodingException e) {return null;}}
把我坑了好久,为什么一直搜不到,一直是???,原来是弄反了,应该是将UTF-8的输入编码(encode)成网络字符,不是解码(decode)。(致敬IT韦神)
然后还有一个getOnlineSearchList函数,通过前面获得的Ids列表,通过里面的id来获取歌曲的详细信息,保存起来,供我们今后设置Listview和播放网络音乐时使用。
然后获取到某个id号对应的歌曲详细xml信息的地址为:
http://www.xiami.com/song/playlist/id/369149
Xml文件打开如下:
同样可以使用jsoup来解析xml文件,获得歌曲信息保存到ArrayList中
/*** 根据id的列表来获得每个id对应的歌曲的详细信息* @return*/
public ArrayList<SearchResult> getOnlineSearchList(){int size = Ids.size();for (int i = 0;i < size;i++){String postUrl = ID_SEARCH_URL + Ids.get(i);try {Document doc = Jsoup.connect(postUrl).get();Elements elements = doc.select("trackList");//一般情况下这个循环只执行一次for (Element e : elements){SearchResult result = new SearchResult();result.setId(Ids.get(i));result.setMusicName(e.select("title").text());result.setArtist(e.select("artist").text());result.setSmallAlbumUrl(e.select("pic").text());result.setBigAlbumUrl(e.select("album_pic").text());result.setLrcUrl(e.select("lyric").text());result.setPath(StringUtils.decodeMusicUrl(e.select("location").text()));results.add(result);}} catch (IOException e) {e.printStackTrace();}}return results;
}
注意到上面xml文件中带有歌曲地址信息location部分被加密了(阴险万恶的资本主义虾米音乐!)
但是没有关系,计算机研究生高材生的我已经成功解密了地址信息(然而并不是我解密的,我也是从网上找的,其实我是个菜鸡。。。心疼自己)
StringUtils.decodeMusicUrl(e.select("location").text())
decodeMusicUrl的详细内容见代码。
以上已经借助Jsoup库实现了音乐搜索的后台主要功能,明天继续,将获取到的音乐信息适配到ListView上,并且试着实现网络音乐的播放功能。
DC音乐播放器开发日记相关推荐
- 【基于Python+tkinter的音乐播放器开发-哔哩哔哩】 https://b23.tv/eG2TwOL
[基于Python+tkinter的音乐播放器开发-哔哩哔哩] https://b23.tv/eG2TwOL https://b23.tv/eG2TwOL
- MetroMusic音乐播放器开发心得
MetroMusic音乐播放器开发心得 在这个假期,我独立开发了自己的音乐播放器MetroMusic,之所以叫做MetroMusic,是因为这个播放器的界面采用目前最为流行的win8Metro风格.这 ...
- 微信公众号教程(18)微信音乐播放器开发 上
微信音乐播放器开发 原文:http://www.cnblogs.com/imaker/p/6252638.html 一.Html5的audio组件 HTML5 有两个很炫的元素,就是Audio和 ...
- Android音乐播放器开发(2)—登录
1. 说明 本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下.之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍 ...
- Android音乐播放器开发(3)—注册
1. 说明 本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下.之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍 ...
- Android音乐播放器开发(4)—修改密码
1. 说明 本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下.之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍 ...
- 仿酷狗音乐播放器开发日志二十二 动态调色板控件第二版(性能大幅提升附源码)...
转载请说明原出处,谢谢~~ 在上次写的博客<仿酷狗音乐播放器开发日志二十一 开发动态调色板控件(附源码)>发布后,我在群里和网友讨论这个控件的性能和优 缺点,发现了他很多不足,还有很多提升 ...
- Android音乐播放器开发(6)—ListView组件创建歌曲播放列表(内含原理分析)
1. 说明 源码已同步到Gitee仓库,GitHub仓库,觉得还不错的话帮忙点个"star"吧,非常感谢. 以往的文章 服务端:Android音乐播放器开发–服务端 登录:Andr ...
- Qt+MPlayer音乐播放器开发笔记(二):交叉编译MPlayer以及部署到开发板播放演示
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/119991329 长期持续带来更多项目与技术分享,咨询请 ...
最新文章
- g-git 相关命令 及其 基本原理探索 (一)
- Google148亿元收购Fitbit,抢占苹果、三星可穿戴设备市场地盘
- English trip -- Review Unit1 Personal Information 个人信息
- 网易笔试——混合颜料
- 吴恩达深度学习课程deeplearning.ai课程作业:Class 2 Week 1 1.Initialization
- 实战能力|一文看懂GDB调试上层实现
- 带wiringPi库的交叉编译
- nssl1258-naive的瓶子【贪心】
- java_basic_review(5)java反射荔枝
- 入职第一天:接口自动化测试怎么测
- 向爸爸借了500,向妈妈借了500,买了双皮鞋用了970。剩下30元,还爸爸10块,还妈妈10块,自己剩下了10块,欠爸爸490,欠妈妈490,490+490=980。加上自己的10块=990。还有1
- python 网格搜索_网格搜索查找AUC参数
- html计算平方米,平方怎么算(面积怎么算平方米)
- python小游戏之超级玛丽进阶版(1~4关)。好玩到爆炸~【内附github源码,及其详细备注】
- 【知识图谱】知识图谱的本质是什么?
- 访问网上计算机要输入密码,教大家访问电脑显示输入网络密码怎么办
- 深度学习模型轻量化(上)
- linux lp命令,Linux lp 命令 command not found lp 命令详解 lp 命令未找到 lp 命令安装 - CommandNotFound ⚡️ 坑否...
- stm32f767的fft
- Codeforces Round #766 (Div. 2) B. Not Sitting