一、简介

Android的多媒体框架包括支持播放多种常见的媒体类型,使您可以轻松的把音频、视屏和图像集成到您的应用。您可以播放音频或视屏媒体文件,这些文件是存储在你的应用程序的资源文件中的。应用程序的资源文件可以是文件系统中独立的文件,或者通过网络连接获取的一个数据流,所有使用MediaPlayer APIS的资源文件。

注意:您只能在标准输出设备上播放音频数据。目前,标准输出设备是移动设备的扬声器或耳机。您不能在谈话音频调用期间播放声音文件。

1、基础
下面的类是Android框架中用于播放声音的类:
MediaPlayer:这个类主要用于播放声音和视屏
AudioManager:这个类主要管理音频和音频输出设备

2、声明Manifest文件
//网络权限申明

<uses-permission android:name="android.permission.INTERNET" />

如果您的播放应用需要阻止屏幕变暗或者阻止处理器睡眠,或者使用MediaPlayer.setScreenOnWhielePlaying()或MedidaPlayer.setWakeMode()方法,需要加入下面的权限。
//唤醒锁申明

<uses-permission android:name="android.permission.WAKE_LOCK" />

二、使用MediaPlayer

媒体框架中最重要的组件之一就是MediaPlayer类,此类的对象可以用少量的设置即能获取,解码和播放音频视屏,它支持多种媒体源,比如:

  • 本地资源
  • 内部URI,比如从您的ContentResolver取得的URI
  • 外部URI(媒体流)

1、播放本地资源

//播放本地资源文件,create方法中包括了setDataSource和prepare的执行
MediaPlayer _mp = MediaPlayer.create(this, R.raw.millets);
_mp.start();

2、播放一个本地URI

//播放文本文件的资源文件String _path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/millets.mp4";try {//也可以采用这种方式创建对象MediaPlayer _mpFile = new MediaPlayer();_mpFile.setDataSource(this, Uri.parse(_path));_mpFile.prepare();//同步执行_mpFile.start();} catch (IOException e) {e.printStackTrace();}

3、播放网络地址

//播放网络的资源String _netPath = "http://.mp4";MediaPlayer _netMp = new MediaPlayer();try {_netMp.setDataSource(this, Uri.parse(_netPath));_netMp.prepareAsync();//异步执行//需要等待prepareAsync准备结束后,回调通知才可以播放_netMp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}});} catch (IOException e) {e.printStackTrace();}

4、管理状态
MediaPlayer有一个内部的状态,因为特定的操作只能在特定的状态时才能有效。如果你在错误的状态下执行一个操作,系统可能抛出一个异常或者导致一个意外的行为。

5、释放MediaPlayer
MediaPlayer可能要消耗大量的系统资源。因此您应该总是采用一些额外的措施来确保在一个MediaPlayer实例上不会挂起太长的时间。当您用玩MediaPlayer时,您应该总是调用release()来保证任何分配给MeidaPlayer的系统资源被正确的释放。

mediaPlayer.release();
mediaPlayer = null;

6、完整例子:

public class MediaMainActivity extends BaseActivity implements View.OnClickListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {//UIprivate android.widget.Button buttonpre;private android.widget.Button buttonstart;private android.widget.Button buttonpause;private android.widget.Button buttonnext;private MediaPlayer mMediaPlayer;//dataprivate ArrayList<String> mMusicList;private int mMusicIndex = 0;private boolean isPaused = false;@Overridepublic void initData(Bundle savedInstanceState) {//init music dataString _musicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath();String _a = _musicPath + File.separator + "mbc.mp3";String _b = _musicPath + File.separator + "rgyzd.mp3";String _c = _musicPath + File.separator + "rga.mp3";String _d = _musicPath + File.separator + "zxk.mp3";String _e = _musicPath + File.separator + "aj.mp3";mMusicList = new ArrayList<>();mMusicList.add(_a);mMusicList.add(_b);mMusicList.add(_c);mMusicList.add(_d);mMusicList.add(_e);//init mediamMediaPlayer = new MediaPlayer();mMediaPlayer.setOnPreparedListener(this);mMediaPlayer.setOnCompletionListener(this);mMediaPlayer.setOnErrorListener(this);}@Overridepublic void initView(Bundle savedInstanceState) {setContentView(R.layout.activity_media_main);this.buttonnext = (Button) findViewById(R.id.button_next);this.buttonnext.setOnClickListener(this);this.buttonpause = (Button) findViewById(R.id.button_pause);this.buttonpause.setOnClickListener(this);this.buttonstart = (Button) findViewById(R.id.button_start);this.buttonstart.setOnClickListener(this);this.buttonpre = (Button) findViewById(R.id.button_pre);this.buttonpre.setOnClickListener(this);}@Overridepublic void loadData(Bundle savedInstanceState) {}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.button_start:if (isPaused) {mMediaPlayer.start();isPaused = false;} else {this.startPlayMusic();}break;case R.id.button_pause:pausePlay();break;case R.id.button_pre:this.startPre();break;case R.id.button_next:this.startNext();break;}}/*** 开始重新播放音乐*/private void startPlayMusic() {if (mMediaPlayer.isPlaying()) {mMediaPlayer.stop();}mMediaPlayer.reset();try {mMediaPlayer.setDataSource(this, Uri.parse(mMusicList.get(mMusicIndex)));mMediaPlayer.prepareAsync();isPaused = false;} catch (IOException e) {e.printStackTrace();}}/*** 暂停播放*/private void pausePlay() {if (mMediaPlayer.isPlaying()) {mMediaPlayer.pause();isPaused = true;}}/*** 播放上一首*/private void startPre() {if (mMusicIndex - 1 >= 0) {mMusicIndex--;} else {mMusicIndex = mMusicList.size() - 1;}this.startPlayMusic();}/*** 播放下一首*/private void startNext() {if (mMusicIndex + 1 < mMusicList.size()) {mMusicIndex++;} else {mMusicIndex = 0;}this.startPlayMusic();}@Overridepublic void onCompletion(MediaPlayer mp) {this.startNext();}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {if (null != mMediaPlayer) {mMediaPlayer.reset();}return true;}@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}@Overrideprotected void onDestroy() {super.onDestroy();if (null != mMediaPlayer) {if (mMediaPlayer.isPlaying()) {mMediaPlayer.stop();}mMediaPlayer.release();}}
}

三、在服务中使用MediaPlayer

如果您希望您的媒体在您的应用不出现在屏幕上时仍然能够在后台播放,也就是您希望当用户与其他应用交互时仍能继续播放,那么您必须启动一个Service并且通过它来控制MediaPlayer实例。
1、开启服务

public class MyService extends Service implements MediaPlayer.OnPreparedListener {private static final String ACTION_PLAY = "com.example.action.PLAY";MediaPlayer mMediaPlayer = null;public int onStartCommand(Intent intent, int flags, int startId) {...if (intent.getAction().equals(ACTION_PLAY)) {mMediaPlayer = ... // initialize it heremMediaPlayer.setOnPreparedListener(this);mMediaPlayer.prepareAsync(); // prepare async to not block main thread}}/** Called when MediaPlayer is ready */public void onPrepared(MediaPlayer player) {player.start();}
}

2、处理异步的错误:

public class MyService extends Service implements MediaPlayer.OnErrorListener {MediaPlayer mMediaPlayer;public void initMediaPlayer() {// ...initialize the MediaPlayer here...mMediaPlayer.setOnErrorListener(this);}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// ... react appropriately ...// The MediaPlayer has moved to the Error state, must be reset!}
}

3、使用唤醒锁
在后台播放媒体的应用时,当您的Service正在运行时,设备可能进入休眠,因为Android系统在休眠时会试着节省电能,那么系统会试着关闭电话的任何不必要的特性,包括CPU和WIFI,然而,如果您的Service正在播放或者接受音乐,您就想阻止系统干涉您的播放工作,为了在上述情况下保证您的service继续运行,您就必须使用”wakelocks”。一个wakelock是一种通知系统在手机空闲时也能为您的应用保留所用特性的途径。
注意:您总是应该保守的使用wekelocks并且尽在真正需要时才持有它,因为它会显著减少设备电池的寿命。
①设置唤醒锁:
当您的MediaPlayer播放时,要保持CPU持续运行,在初始化MediaPlayer时需要调用setWakeMode(),一旦您这样做了,MediaPlayer就会在播放时持有一个特定的锁,并在暂停或停止时释放它:

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

4、请求WIFILOCK:

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();

①释放锁
当您暂停或者停止媒体或者当你现在不需要网络时,您应该释放这个锁:

wifiLock.release();

5、使用Notification来在前台显示

String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,new Intent(getApplicationContext(), MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample","Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);

6、完整代码:
①Activity

public class MediaPlayServiceActivity extends BaseActivity implements View.OnClickListener {private android.widget.Button servicebuttonstart;private android.widget.Button servicebuttonpause;private android.widget.Button servicebuttonover;@Overridepublic void initData(Bundle savedInstanceState) {}@Overridepublic void initView(Bundle savedInstanceState) {setContentView(R.layout.activity_media_play_service);this.servicebuttonover = (Button) findViewById(R.id.service_button_over);this.servicebuttonover.setOnClickListener(this);this.servicebuttonpause = (Button) findViewById(R.id.service_button_pause);this.servicebuttonpause.setOnClickListener(this);this.servicebuttonstart = (Button) findViewById(R.id.service_button_start);this.servicebuttonstart.setOnClickListener(this);}@Overridepublic void loadData(Bundle savedInstanceState) {}@Overridepublic void onClick(View v) {switch (v.getId()) {//启动播放音乐服务case R.id.service_button_start:Intent _intentStart = new Intent(this, MediaPlayService.class);_intentStart.setAction(Common.ACTION_START);startService(_intentStart);break;//启动暂停音乐服务case R.id.service_button_pause:Intent _intentPause = new Intent(this, MediaPlayService.class);_intentPause.setAction(Common.ACTION_PAUSE);startService(_intentPause);break;//启动停止音乐服务case R.id.service_button_over:Intent _intentOver = new Intent(this, MediaPlayService.class);_intentOver.setAction(Common.ACTION_OVER);startService(_intentOver);break;}}@TargetApi(Build.VERSION_CODES.JELLY_BEAN)@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK) {}return super.onKeyDown(keyCode, event);}
}

②Service

public class MediaPlayService extends Service implements MediaPlayer.OnPreparedListener, AudioManager.OnAudioFocusChangeListener {//声明MediaPlayerprivate MediaPlayer mMP;//声明音乐的地址private String mMusicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + File.separator + "zxk.mp3";//声明WifiLockprivate WifiManager.WifiLock mWifiLock;//声明AudioManagerprivate AudioManager mAudioManager;@Overridepublic void onCreate() {super.onCreate();//获取音频对象mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);int _results = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);if (AUDIOFOCUS_REQUEST_GRANTED == _results) {initMediaPlayer();} else {}}/*** 初始化MediaPlayer中的一些配置*/public void initMediaPlayer() {mMP = new MediaPlayer();mMP.setOnPreparedListener(this);//保证不会被后台清理掉mMP.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//保证WIFI不会被休眠WifiManager _wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);mWifiLock = _wifiManager.createWifiLock("MyLock");mWifiLock.acquire();//设置NotificationcreateNotification();}/*** 创建一个通知,用于在前台显示*/@TargetApi(Build.VERSION_CODES.JELLY_BEAN)private void createNotification() {Notification.Builder _builder = new Notification.Builder(this);_builder.setTicker("我的第一个音乐播放器");_builder.setSmallIcon(R.mipmap.ic_launcher);_builder.setContentTitle("我的音乐神器");_builder.setContentInfo("正在播放");PendingIntent _pI = PendingIntent.getActivity(this, 0, new Intent(this, MediaPlayServiceActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);_builder.setContentIntent(_pI);NotificationManager _nM = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);Notification _notification = _builder.build();_nM.notify(0, _notification);startForeground(0, _notification);}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {String _stringAction = intent.getAction();switch (_stringAction) {//播放音乐actioncase Common.ACTION_START:mMP.reset();try {mMP.setDataSource(getApplicationContext(), Uri.parse(mMusicPath));mMP.prepareAsync();} catch (IOException e) {e.printStackTrace();}break;//暂停音乐actioncase Common.ACTION_PAUSE:if (mMP.isPlaying()) {mMP.pause();}break;//结束音乐actioncase Common.ACTION_OVER:if (mMP.isPlaying()) {mMP.stop();}mMP.release();break;case Common.ACTION_STOP:if (mMP.isPlaying()) {mMP.stop();}break;}return super.onStartCommand(intent, flags, startId);}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}@Overridepublic void onDestroy() {super.onDestroy();//释放WifiLockmWifiLock.release();//停止通知在前台显示stopForeground(true);//解绑监听if (null != this.mAudioManager) {this.mAudioManager.abandonAudioFocus(this);}}@Overridepublic void onAudioFocusChange(int focusChange) {switch (focusChange) {//已经获得焦点case AudioManager.AUDIOFOCUS_GAIN:if (null == mMP) {this.initMediaPlayer();} else if (!mMP.isPlaying()) {mMP.start();}mMP.setVolume(1.0f, 1.0f);break;//长期失去焦点case AudioManager.AUDIOFOCUS_LOSS:if (null != mMP) {if (mMP.isPlaying()) {mMP.stop();}mMP.release();mMP = null;}break;//失去焦点,但是很快将会获取case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:if (null != mMP) {if (mMP.isPlaying()) {mMP.pause();}}break;//失去焦点,允许以小声音播放case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:if (null != mMP) {if (mMP.isPlaying()) {mMP.setVolume(0.1f, 0.1f);}}break;}}}

四、处理音频的焦点

1、要请求音频焦点,您必须从AudioManager调用requestAudioFocus()

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {// could not get audio focus.
}

注意:requestAudioFocus()的第一个参数是一个AudioManager.OnAudioFocusChangeListener,它的onAudioFocusChange()方法在音频焦点发生改变时调用,因此,您也可以在您的Service和Activity上实现此接口
2、Service实现onAudioFocusChangeListener接口

class MyService extends Serviceimplements AudioManager.OnAudioFocusChangeListener {// ....public void onAudioFocusChange(int focusChange) {// Do something based on focus change...}
}

注意:参数focusChange告诉你焦点如何发生了变化

  • AUDIOFOCUS_GAIN: You have gained the audio focus.
  • AUDIOFOCUS_LOSS: You have lost the audio focus for a presumably long time. You must stop all audio playback. Because you should expect not to have focus back for a long time, this would be a good place to clean up your resources as much as possible. For example, you should release the MediaPlayer.
  • AUDIOFOCUS_LOSS_TRANSIENT: You have temporarily lost audio focus, but should receive it back shortly. You must stop all audio playback, but you can keep your resources because you will probably get focus back shortly.
  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: You have temporarily lost audio focus, but you are allowed to continue to play audio quietly (at a low volume) instead of killing audio completely.

注意:音频焦点API仅在API level8(Android2.2)及更高版本上可以使用

public void onAudioFocusChange(int focusChange) {switch (focusChange) {case AudioManager.AUDIOFOCUS_GAIN:// resume playbackif (mMediaPlayer == null) initMediaPlayer();else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();mMediaPlayer.setVolume(1.0f, 1.0f);break;case AudioManager.AUDIOFOCUS_LOSS:// Lost focus for an unbounded amount of time: stop playback and release media playerif (mMediaPlayer.isPlaying()) mMediaPlayer.stop();mMediaPlayer.release();mMediaPlayer = null;break;case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:// Lost focus for a short time, but we have to stop// playback. We don't release the media player because playback// is likely to resumeif (mMediaPlayer.isPlaying()) mMediaPlayer.pause();break;case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:// Lost focus for a short time, but it's ok to keep playing// at an attenuated levelif (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);break;}
}

3、完整代码见上

五、处理AUDIO_BECOMING_NOISY意图(耳机拔出)

很多良好的音频播放的应用都会在那些导致声音变成噪音(通过外部扬声器输出)的事件发生时自动停止播放。例如,这可能发生在当一个用户用耳机听音乐时突然断开了耳机连接,音频从扬声器播放可能不是用户所期望的。
1、可以通过处理ACTION_AUDIO_BECOMIING_NOISY intent来保证您的应用在此情况下停止播放音乐,您可以把如下代码添加到您的manifest来注册一个receiver。

<receiver android:name=".MusicIntentReceiver"><intent-filter><action android:name="android.media.AUDIO_BECOMING_NOISY" /></intent-filter>
</receiver>

2、创建一个receiver来接受

public class MusicIntentReceiver extends android.content.BroadcastReceiver {@Overridepublic void onReceive(Context ctx, Intent intent) {if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {// signal your service to stop playback// (via an Intent, for instance)}}
}

Android音频的播放相关推荐

  1. Android 音频系统播放延迟时间获取(latency)

    1.系统AudioManager类里面有一个隐藏接口: 可以用反射获取到系统播放硬件延迟 AudioManager am = (AudioManager) context.getSystemServi ...

  2. android语音播放工具类,Android开发之MediaPlayer多媒体(音频,视频)播放工具类

    本文实例讲述了Android开发之MediaPlayer多媒体(音频,视频)播放工具类.分享给大家供大家参考,具体如下: package com.android.imooc.chat; import ...

  3. Android音频开发(3):如何播放一帧音频

    本文重点关注如何在Android平台上播放一帧音频数据.阅读本文之前,建议先读一下<Android音频开发(1):基础知识>,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的 ...

  4. android 音频播放总结 soundlPool,MediaPlay

    soundlPool 用于小音频的播放多个同时播放. 使用步骤: 步骤一: 首先下载音频文件可以将其放入assets文件夹下或者res下的raw文件夹下,区别在于assets下可以再新建文件夹而raw ...

  5. Android音频实时传输与播放(四):源码下载(问题更新)【转】

    Android音频实时传输与播放(四):源码下载(问题更新) 激动人心的时刻到了有木有 ^_^ 服务端下载请点击这里,客户端下载请点击这里! 最近有朋友在下载源码使用之后,说播放出来的声音噪声很大.其 ...

  6. Android 音频开发(四) 如何播放一帧音频数据下

    再看这一篇文章前,如果你是小白,我建议你先看一下Android 音频开发(一) 基础入门篇这一篇.今天继续讲解如何通过Android SDK自带API实现播放一帧音频数据. 我们都知道,Android ...

  7. Android 音频开发(三) 如何播放一帧音频数据上

    上一篇只要介绍了如何采集一帧音频,本篇就讲述如何播放一帧音频数据,这一篇我将分倆篇来详细介绍. Android SDK 提供了3套音频播放的API,分别是:MediaPlayer,SoundPool, ...

  8. android 播放音乐卡顿,Android MediaPlayer+SeekBar播放音频出现卡顿边长可能问题

    开发过程中总是会碰到一些"什么鬼,原来这么简单"等等的问题,比如今天碰到 Android MediaPlayer+SeekBar播放音频出现卡顿可能问题? 代码段一: seekBa ...

  9. android声音播放函数双声道合并,Android音频编辑之音频合成功能

    前言 音频编辑系列: 本篇主要讲解音频PCM数据的合成,这里合成包括音频之间的拼接,混合. - 音频拼接:一段音频连接着另一段音频,两段音频不会同时播放,有先后顺序. - 音频混合:一段音频和另一段音 ...

  10. android 音频播放过程,一种Android系统中的音频播放方法与流程

    本申请涉及android系统技术,特别涉及一种android系统中的音频播放方法. 背景技术: 在android系统中,现有的使用audiotrack进行音频播放时,audiotrack应用与andr ...

最新文章

  1. Java Web的Maven项目中Properties文件的使用(2)
  2. javascript worker 多线程 简单示例
  3. spring中Constructor、@Autowired、@PostConstruct的顺序
  4. 【vim】Vim: Error detected while processing function SNR37_MRU_LoadList错误
  5. 复制一个文件夹中的所有文件和文件夹的java程序实现
  6. .Net中的事件处理模型
  7. 分治算法-最大子数组问题
  8. FZU 2169 shadow (用了一次邻接表存边,树形DP)
  9. html5+前端脸部识别采集,前端人脸识别框架Tracking.js
  10. Mac下Android 反编译
  11. javaWeb随机生成网页验证码图片
  12. VOIP Codec 三剑客之 ISAC/ILBC -- ISAC (1) 介绍
  13. 【MySQL】Online DDL详解
  14. 微信公众号开发之网页授权获取用户基本信息
  15. 基于SWT组件的IE内核Java简易浏览器
  16. 诺布酒店在希腊的第一家酒店餐厅圣托里尼诺布酒店餐厅今年春季开业;爱彼迎邀旅居体验者住进西西里乡村慢生活 | 全球旅报...
  17. Scratch基础(一):安装和了解软件
  18. BI报表工具--实现财务数据可视化分析
  19. 国产自研系统的用户突破4亿,打破美国企业的垄断,谷歌后悔不迭
  20. 时间字段加一秒_Mysql自动加1秒的问题

热门文章

  1. FRR BGP 协议分析 5 -- 路由更新(2)
  2. Android开发:菜单栏Menu用法讲解
  3. ubuntu怎么将Dash切换位bash
  4. 如何获取h.264码流的码率和帧率
  5. Linux acpi off学习
  6. 以snull为例分析linux网卡驱动的技术文档[转载]二
  7. G - Super Jumping! Jumping! Jumping!(动态规划)
  8. 点击按钮跳转页面_获取快手主页跳转url scheme 协议的方法(app通用方法)
  9. 4077. k显性字符
  10. 小白都能了解的聚类算法之四(谱聚类)