android 自定义MP4播放器
昨天,在网上找了好多资料,终于做了一个自定义的播发器。
视频播放方式
在Android中播放视频的方式有两种:
1、使用MediaPlayer结合SurfaceView进行播放。其中通过SurfaceView显示视频的画面,通过MediaPlayer来设置播放参数、并控制视频的播放操作。
该方式的好处是灵活性强,可随意定制。缺点是编码复杂,连开始/暂停的按钮都要自己实现。
2、使用VideoView结合MediaController进行播放。VideoView其实是从SurfaceView扩展而来,并在内部集成了MediaPlayer,从而实现视频画面与视频操作的统一管理;而MediaController则是一个简单的播放控制条,它实现了基本的控制按钮,如开始/暂停按钮、上一个/下一个按钮、快进/快退按钮,以及进度条等控件;把VideoView与MediaController关联起来,便是一个类似于Window Media Player的精简版播放器。
该方式的好处是简单易用,编码容易。缺点是可定制差,难以扩展,想给按钮换个样式都不行。
一。自定义一个activity继承SurfaceHolder和SeekBarMediaPlayer的所以接口。然后初始化界面。
package com.mymp4player; import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.HashMap; /** * 创建:dongshuaijun * 日期:2016/7/1 * 注释:视屏播放 */ public class MainActivity extends Activity implements SurfaceHolder.Callback, View.OnClickListener , SeekBar.OnSeekBarChangeListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener {//surfaceView private SurfaceView surfaceView; //视频最外层layout private RelativeLayout videoLayout; //控制台layout private LinearLayout controlLayout; //播放、全屏button private ImageButton playBtn, screenBtn; //进度条 private SeekBar seekBar; //加载视频进度progressBar private ProgressBar progressBar; //当前时间,总时间 private TextView currTime, countTime; //surface holder private SurfaceHolder mHolder; //媒体控制 mediaPlayer private MediaPlayer mediaPlayer; //是否全屏 private boolean isFullScreen = false; //是否正在播放 private boolean isPlay = false; //控制台是否显示 private boolean isControl = false; //是否正在拖动seekBar private boolean isSetProgress = false; //是否播放完成 private boolean isPlayCom = false; //是否是第一次加载视频 private boolean isFirstLoadVideo = true; //是否销毁activity private boolean isOnDestroy = false; //是否可见 private boolean isPause = false; //媒体音量管理 private AudioManager audioManager; //点击纵坐标 private float dY = 0; //点击横坐标 private float dX = 0; //抬起纵坐标 private float uY = 0; //抬起横坐标 private float uX = 0; //屏幕当前亮度 private float f = 0; //手机当前亮度模式 0 1 private int countLight; //系统当前亮度 1-255 private int currLight; private String width, height; private static final int HIDE_CONTROL_LAYOUT = -1; //这个地址是我抓的某平台的,我发现这个地址是变化的,所以有可能不能使用,如果不能播放,换个正常的就可以运行了,不要用模拟器运行 private static final String BASE_STORAGE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); public static final String RECORD_VIDEO_PATH = BASE_STORAGE_PATH + "/p2pviewcam/videos/"; private static final String VIDEO_URL = RECORD_VIDEO_PATH+"BZWYZN-031016-CBGTK_2017_06_12_17_01_37_CH_1.mp4"; private Handler handler = new Handler() {@Override public void handleMessage(Message msg) {super.handleMessage(msg); if (msg.what == HIDE_CONTROL_LAYOUT) {refreshControlLayout(); } else {currTime.setText(formatTime(msg.what)); seekBar.setProgress(msg.what); }}}; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initVideoSize(); initSurface(); setListener(); }private void initView() {surfaceView = (SurfaceView) findViewById(R.id.surface_view); videoLayout = (RelativeLayout) findViewById(R.id.video_layout); controlLayout = (LinearLayout) findViewById(R.id.control_layout); playBtn = (ImageButton) findViewById(R.id.playBtn); screenBtn = (ImageButton) findViewById(R.id.screenBtn); seekBar = (SeekBar) findViewById(R.id.seekBar); progressBar = (ProgressBar) findViewById(R.id.load_bar); currTime = (TextView) findViewById(R.id.curr_time); countTime = (TextView) findViewById(R.id.count_time); mHolder = surfaceView.getHolder(); mediaPlayer = new MediaPlayer(); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); initScreenLight(); refreshControlLayout(); }//初始化屏幕亮度 private void initScreenLight() {try {//获取亮度模式 0:手动 1:自动 countLight = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE); //设置手动设置 Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); //获取屏幕亮度,获取失败则返回255 currLight = android.provider.Settings.System.getInt(getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, 255); f = currLight / 255f; } catch (Settings.SettingNotFoundException e) {e.printStackTrace(); }}//刷新控制台 显示则隐藏 隐藏则显示 并5S之后隐藏 private void refreshControlLayout() {if (isControl) {controlLayout.setVisibility(View.INVISIBLE); isControl = false; } else {controlLayout.setVisibility(View.VISIBLE); isControl = true; handler.removeMessages(HIDE_CONTROL_LAYOUT); handler.sendEmptyMessageDelayed(HIDE_CONTROL_LAYOUT, 5000); }}private void setListener() {playBtn.setOnClickListener(this); screenBtn.setOnClickListener(this); seekBar.setOnSeekBarChangeListener(this); mediaPlayer.setOnCompletionListener(this); mediaPlayer.setOnErrorListener(this); mediaPlayer.setOnBufferingUpdateListener(this); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setOnSeekCompleteListener(this); surfaceView.setOnTouchListener(new View.OnTouchListener() {@Override public boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:dX = event.getX(); dY = event.getY(); refreshControlLayout(); break; case MotionEvent.ACTION_UP:break; case MotionEvent.ACTION_MOVE:if (isFullScreen) {uY = event.getY(); if (dX > getWidth() / 2) {//声音控制 if (Math.abs(uY - dY) > 25)setVolume(uY - dY); } else if (dX <= getWidth() / 2) {//亮度控制 setLight(dY - uY); }}break; }return true; }}); }//手势调节音量 private void setVolume(float vol) {if (vol < 0) {//增大音量 audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FX_FOCUS_NAVIGATION_UP); } else if (vol > 0) {//降低音量 audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FX_FOCUS_NAVIGATION_UP); }}/** * 手势设置屏幕亮度 * 设置当前的屏幕亮度值,及时生效 0.004-1 * 该方法仅对当前应用屏幕亮度生效 */ private void setLight(float vol) {Window localWindow = getWindow(); WindowManager.LayoutParams localLayoutParams = localWindow.getAttributes(); f += vol / getWidth(); if (f > 1) {f = 1f; } else if (f <= 0) {f = 0.004f; }localLayoutParams.screenBrightness = f; localWindow.setAttributes(localLayoutParams); }//初始化surfaceView private void initSurface() {//设置回调参数 mHolder.addCallback(this); //设置SurfaceView自己不管理的缓冲区 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //显示的分辨率,不设置为视频默认 // mHolder.setFixedSize(320, 220); }private void playUrl(String url) {try {//使mediaPlayer重新进入ide状态 mediaPlayer.reset(); //设置媒体类型 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); //将影像输出到surfaceView mediaPlayer.setDisplay(mHolder); //设置 视频资源 可以是本地视频 也可是网络资源 // mediaPlayer.setDataSource("/storage/sdcard1/DCIM/Camera/VID_20160629_164144.mp4"); mediaPlayer.setDataSource(url); //同步准备 // mediaPlayer.prepare(); //因为是网络视频 这里用异步准备 mediaPlayer.prepareAsync(); } catch (IOException e) {e.printStackTrace(); }}//初始化视频显示的大小 private void initVideoSize() {RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); getPlayTime(VIDEO_URL); // params.height = getWidth() / 10 * 6; if (width!=null && height!=null){params.height = getWidth() *Integer.parseInt(height)/Integer.parseInt(width); }else {params.height = getWidth() / 10 * 6; }params.addRule(RelativeLayout.CENTER_IN_PARENT); surfaceView.setLayoutParams(params); }//surfaceView创建完成 @Override public void surfaceCreated(SurfaceHolder holder) {Log.e("TAG", "surfaceCreated"); //等surfaceView创建完成再开始播放视频 if (!isPause) {playUrl(VIDEO_URL); } else {isPause = false; mediaPlayer.setDisplay(holder); if (isPlay) mediaPlayer.start(); }}//surfaceView改变 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.e("TAG", "surfaceChanged"); }//surfaceView销毁 @Override public void surfaceDestroyed(SurfaceHolder holder) {Log.e("TAG", "surfaceDestroyed"); }@Override public void onClick(View v) {isControl = false; refreshControlLayout(); if (isFirstLoadVideo) {return; }switch (v.getId()) {case R.id.playBtn:if (mediaPlayer.isPlaying()) {mediaPlayer.pause(); isPlay = false; playBtn.setBackgroundResource(R.mipmap.play); } else if (isPlayCom) {mediaPlayer.seekTo(0); isPlay = true; isPlayCom = false; playBtn.setBackgroundResource(R.mipmap.pause); } else {mediaPlayer.start(); isPlay = true; playBtn.setBackgroundResource(R.mipmap.pause); }break; case R.id.screenBtn:if (isFullScreen) {smallScreen(); screenBtn.setBackgroundResource(R.mipmap.large_screen); } else {fullScreen(); screenBtn.setBackgroundResource(R.mipmap.small_screen); }break; }}//横竖屏切换 @Override public void onConfigurationChanged(Configuration newConfig) {if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {Log.e("TAG", "当前屏幕为横屏"); isFullScreen = true; fullScreen(); screenBtn.setBackgroundResource(R.mipmap.small_screen); } else {Log.e("TAG", "当前屏幕为竖屏"); isFullScreen = false; smallScreen(); screenBtn.setBackgroundResource(R.mipmap.large_screen); }super.onConfigurationChanged(newConfig); }//全屏 private void fullScreen() {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置成全屏模式 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//强制为横屏 showFullSurface(); }//竖屏 private void smallScreen() {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//强制为竖屏 showSmallSurface(); }private void showFullSurface() {RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); params.width=getHeight() *Integer.parseInt(width)/Integer.parseInt(height); params.addRule(RelativeLayout.CENTER_IN_PARENT); surfaceView.setLayoutParams(params); }private void showSmallSurface() {RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // params.height = getWidth() / 10 * 6; if (width!=null && height!=null){params.height = getWidth() *Integer.parseInt(height)/Integer.parseInt(width); }else {params.height = getWidth() / 10 * 6; }params.addRule(RelativeLayout.CENTER_IN_PARENT); surfaceView.setLayoutParams(params); }//进度改变 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {currTime.setText(formatTime(seekBar.getProgress())); if (isSetProgress) {Log.e("TAG", "onProgressChanged:refreshControlLayout"); isControl = false; refreshControlLayout(); }}//开始拖动 @Override public void onStartTrackingTouch(SeekBar seekBar) {currTime.setText(formatTime(seekBar.getProgress())); isSetProgress = true; isControl = false; refreshControlLayout(); }//停止拖动 @Override public void onStopTrackingTouch(SeekBar seekBar) {isSetProgress = false; isControl = false; refreshControlLayout(); if (isFirstLoadVideo) {return; }mediaPlayer.seekTo(seekBar.getProgress()); currTime.setText(formatTime(seekBar.getProgress())); }public int getWidth() {WindowManager manager = getWindowManager(); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; }public int getHeight() {WindowManager manager = getWindowManager(); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.heightPixels; }//更新进度 private void updateSeekBar() {new Thread(new Runnable() {@Override public void run() {while (!isOnDestroy) { //结束线程标示 if (isPlay && !isPause) {try {Message message = new Message(); message.what = mediaPlayer.getCurrentPosition(); handler.sendMessage(message); Log.e("TAG", "while"); Thread.sleep(1000); } catch (Exception e) {e.printStackTrace(); }}}}}).start(); }//播放完成 @Override public void onCompletion(MediaPlayer mp) { // Log.e("TAG", "播放完成"); playBtn.setBackgroundResource(R.mipmap.play); isPlay = false; isPlayCom = true; isControl = false; Message message = new Message(); message.what = mediaPlayer.getDuration(); handler.sendMessage(message); refreshControlLayout(); }//播放出错 @Override public boolean onError(MediaPlayer mp, int what, int extra) {isPlay = false; return false; }private String formatTime(long time) {SimpleDateFormat format = new SimpleDateFormat("mm:ss"); return format.format(time); }@Override public void onBufferingUpdate(MediaPlayer mp, int percent) {Log.e("TAG", "onBufferingUpdate" + ",percent:" + percent); }//准备完成 @Override public void onPrepared(MediaPlayer mp) {//设置最大进度 seekBar.setMax(mediaPlayer.getDuration()); //设置按钮背景图片 playBtn.setBackgroundResource(R.mipmap.pause); //设置视频最大时间 countTime.setText(formatTime(mediaPlayer.getDuration())); //隐藏加载进度条 progressBar.setVisibility(View.INVISIBLE); //开始播放 mediaPlayer.start(); //更改播放状态 isPlay = true; //更改状态 if (isFirstLoadVideo)isFirstLoadVideo = false; //开启线程更新进度 updateSeekBar(); }@Override protected void onDestroy() {Log.e("TAG", "onDestroy"); isOnDestroy = true; if (mediaPlayer.isPlaying()) {mediaPlayer.stop(); isPlay = false; }mediaPlayer.release(); super.onDestroy(); }//seekTo()是异步的方法 在此监听是否执行完毕 @Override public void onSeekComplete(MediaPlayer mp) {Log.e("TAG", "onSeekComplete"); if (!isPlay) {mediaPlayer.pause(); } else {mediaPlayer.start(); }}//监听返回键 如果是全屏状态则返回竖屏 否则直接返回 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {if (isFullScreen) {smallScreen(); return false; }return super.onKeyDown(keyCode, event); }@Override protected void onPause() {Log.e("TAG", "onPause"); isPause = true; if (mediaPlayer.isPlaying()) {mediaPlayer.pause(); }super.onPause(); }@Override protected void onResume() {super.onResume(); if (isPause && isPlay && mHolder.getSurface().isValid()) {isPause = false; mediaPlayer.start(); }}private void getPlayTime(String mUri){android.media.MediaMetadataRetriever mmr = new android.media.MediaMetadataRetriever(); try {if (mUri != null){HashMap<String, String> headers = null; if (headers == null){headers = new HashMap<String, String>(); headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; zh-CN; MW-KW-001 Build/JRO03C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/1.0.0.001 U4/0.8.0 Mobile Safari/533.1"); }mmr.setDataSource(mUri, headers); } else {//mmr.setDataSource(mFD, mOffset, mLength); }String duration = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_DURATION);//时长(毫秒) width = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);//宽 height = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);//高 Toast.makeText(MainActivity.this, "playtime:"+ duration+"w="+width+"h="+height, Toast.LENGTH_SHORT).show(); } catch (Exception ex){Log.e("TAG", "MediaMetadataRetriever exception " + ex); } finally {mmr.release(); }}}
这里我是用的MediaPlayer结合SurfaceView进行播放,首先,定义一个SurfaceView,SurfaceHolder,MediaPlayer,这个是视频播放用到的关键技术。
由于activity继承了SurfaceHolder.Callback,所以必须实现三个接口代码。
//surfaceView创建完成@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.e("TAG", "surfaceCreated");//等surfaceView创建完成再开始播放视频if (!isPause) {playUrl(VIDEO_URL);} else {isPause = false;mediaPlayer.setDisplay(holder);if (isPlay) mediaPlayer.start();}}//surfaceView改变@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.e("TAG", "surfaceChanged");}//surfaceView销毁@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Log.e("TAG", "surfaceDestroyed");}
这里当SurfaceHolder初始化完成时,调用surfaceCreated代码,然后把MediaPlayer与SurfaceHolder进行关联,通过代码
mediaPlayer.setDisplay(mHolder);
mediaPlayer.setDataSource(url); //设置播放的路径
然后调用了mediaPlayer.prepareAsync();这个会回调public void onPrepared(MediaPlayer mp) ,在这个代码里,
//准备完成 @Override public void onPrepared(MediaPlayer mp) {//设置最大进度 seekBar.setMax(mediaPlayer.getDuration()); //设置按钮背景图片 playBtn.setBackgroundResource(R.mipmap.pause); //设置视频最大时间 countTime.setText(formatTime(mediaPlayer.getDuration())); //隐藏加载进度条 progressBar.setVisibility(View.INVISIBLE); //开始播放 mediaPlayer.start(); //更改播放状态 isPlay = true; //更改状态 if (isFirstLoadVideo)isFirstLoadVideo = false; //开启线程更新进度 updateSeekBar(); }
就进行了视频的播放。
这个内容讲起来比较麻烦,自己也感觉没有讲明白,大家有兴趣可以自己查找资料。
源码下载:http://download.csdn.net/detail/bzlj2912009596/9868742
这是第一次上传源码,资源分比较贵,大家需要就下载。
android 自定义MP4播放器就讲完了。
就这么简单。
android 自定义MP4播放器相关推荐
- Android 自定义音乐播放器实现
Android自定义音乐播放器 一:首先介绍用了哪些Android的知识点: 1 MediaPlayer工具来播放音乐 2 Handle.因为存在定时任务(歌词切换,动画,歌词进度条变换等)需要由Ha ...
- Android自定义一个播放器控件
介绍 最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了.在写VideoView播放视频时候定义控制的代码全写在Actv ...
- Android开发笔记(一百二十六)自定义音乐播放器
MediaRecorder/MediaPlayer 在Android手机上面,音频的处理比视频还要复杂,这真是出人意料.在前面的博文< Android开发笔记(五十七)录像录音与播放>中, ...
- android 调用系统播放器
今天,简单讲讲android如何调用手机自带的播放器. 昨天,从服务器下载一个AVI的视频,下载后需要进行播放,所以想调用系统自带的播放器.但是由于很少用到,所以自己当时不知道怎么写,于是在网上查找资 ...
- android集成EasyPlayer播放器播放实时流媒体视屏
android集成EasyPlayer播放器播放实时流媒体视屏 最近公司项目需要实现一个rtsp实时流媒体视频的播放,在移动端尝试了多个第三方能播放rtsp流实时视频的软件后发现EasyPlayer的 ...
- 自定义高性能播放器, 实现边下边播缓存等功能
VideoPlayerDemo 项目地址:Zhaoss/VideoPlayerDemo 简介:自定义高性能播放器, 实现边下边播缓存等功能 更多:作者 提 Bug 标签: 本项目使用播放器是ij ...
- H5 实现自定义video播放器,快来点我吧
效果 要实现自定义video播放器需要熟悉video的相关操作 视频播放它有哪些操作 1. 播放 play() 2. 暂停 pause() 3. 判断当 ...
- JS 用CANVAS自定义VIDEO播放器
JS 用CANVAS自定义VIDEO播放器 概述 CANVAS绘制核心代码 播放器代码 使用页面HTML代码 使用页面JS代码 使用示例 概述 HTML5用规范的VIDEO标签取代了以往需要借助外部控 ...
- android 音乐播放器 获取sd卡所有音乐文件,Android Studio音乐播放器无法读取SD卡,只有内部存储器...
我很抱歉,如果这原来是一个愚蠢的问题,它可能会成为一个快速修复,但我只是无法弄清楚.我在android studio中创建了音乐播放器,并且没有任何sdcard上的歌曲不会显示在列表视图中,只有内部内 ...
最新文章
- Docker 底层原理浅析
- 在android平板上取位置和天气的实现方式
- 分布式监控系统Zabbix3.2对数据库的连接数预警
- Activity的用法(一):Notification Activity
- zabbix安装报错
- mysql搭建主主_mysql主主配置
- 无需predetermine一条路
- mybatis jar包_springboot2整合mybatis-plus3踩到的坑
- vs连接oracle数据库报错,用VS连接oracle数据库时ORA-12504错误
- usermod命令,用户密码管理,mkpasswd命令
- spring和redis的整合-超越昨天的自己系列(7)
- MIT 18.03 写给初学者的微积分校对活动 | ApacheCN
- RedHat7安装及小红帽硬盘分区建议
- C语言(B站比特鹏哥)笔记
- #IP实验室,第二周复盘
- 做好拼多多的几个小技巧-拼多多出评技巧
- uniapp 在h5 模式下扫码
- 完成清除工作,可以Destory窗口标志
- 前端面试合集,20k+已经是妥妥的了
- Netty源码剖析之内存池和对象池设计流程