传统的VideoView是基于SurfaceView实现的,SurfaceView是在屏幕上指定区域新加的一个绘制区域,不在layout布局上,不受布局限制,绘制效率高,但在ListView等可滑动布局中使用时,会有一定的延时黑影,快速拖动时、低端机上出现的很明显。为解决这个问题,下面基于TextureView重写VideoView--TextureVideoView。

1.查看VideoView源码,发现它是直接继承于SurfaceView,实现MediaPlayerControl接口的,所以我们类似的实现它。好,现在的代码长这样子。

/**

* 基于texture的播放器。可放在listview的item中

*

* Created by cailingxiao on 2017/9/26.

*/

public class TextureVideoView extends TextureView implements MediaController.MediaPlayerControl {

public TextureVideoView(Context context) {

super(context);

}

public TextureVideoView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public TextureVideoView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

@Override

public void start() {

}

@Override

public void pause() {

}

@Override

public int getDuration() {

return 0;

}

@Override

public int getCurrentPosition() {

return 0;

}

@Override

public void seekTo(int pos) {

}

@Override

public boolean isPlaying() {

return false;

}

@Override

public int getBufferPercentage() {

return 0;

}

@Override

public boolean canPause() {

return false;

}

@Override

public boolean canSeekBackward() {

return false;

}

@Override

public boolean canSeekForward() {

return false;

}

@Override

public int getAudioSessionId() {

return 0;

}

}

2.复制下VideoView通用逻辑。调整下面几个地方:

2.1 将SurfaceView相关的地方删除;

2.2 将subtitle相关的东西删除;

2.3 在init中加入对TextureView的初始化,添加监听SurfaceTextureListener并实现;

2.4 增加对尺寸变动的支持,之前surfaceView里面有setFixedSize方法来修改大小,所以我们也需要实现个;

最终代码:

public class TextureVideoView extends TextureView implements MediaController.MediaPlayerControl {

private static final String TAG = "VideoView";

// all possible internal states

private static final int STATE_ERROR = -1;

private static final int STATE_IDLE = 0;

private static final int STATE_PREPARING = 1;

private static final int STATE_PREPARED = 2;

private static final int STATE_PLAYING = 3;

private static final int STATE_PAUSED = 4;

private static final int STATE_PLAYBACK_COMPLETED = 5;

// settable by the client

private Uri mUri;

private Map mHeaders;

// mCurrentState is a VideoView object's current state.

// mTargetState is the state that a method caller intends to reach.

// For instance, regardless the VideoView object's current state,

// calling pause() intends to bring the object to a target state

// of STATE_PAUSED.

private int mCurrentState = STATE_IDLE;

private int mTargetState = STATE_IDLE;

// All the stuff we need for playing and showing a video

private MediaPlayer mMediaPlayer = null;

private int mAudioSession;

private int mVideoWidth;

private int mVideoHeight;

private int mSurfaceWidth;

private int mSurfaceHeight;

private MediaController mMediaController;

private MediaPlayer.OnCompletionListener mOnCompletionListener;

private MediaPlayer.OnPreparedListener mOnPreparedListener;

private int mCurrentBufferPercentage;

private MediaPlayer.OnErrorListener mOnErrorListener;

private MediaPlayer.OnInfoListener mOnInfoListener;

private int mSeekWhenPrepared; // recording the seek position while preparing

private boolean mCanPause;

private boolean mCanSeekBack;

private boolean mCanSeekForward;

private Context mContext;

private SurfaceTexture mSurfaceTexture;

public TextureVideoView(Context context) {

this(context, null);

}

public TextureVideoView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public TextureVideoView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init();

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

public TextureVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "

// + MeasureSpec.toString(heightMeasureSpec) + ")");

int width = getDefaultSize(mVideoWidth, widthMeasureSpec);

int height = getDefaultSize(mVideoHeight, heightMeasureSpec);

if (mVideoWidth > 0 && mVideoHeight > 0) {

int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {

// the size is fixed

width = widthSpecSize;

height = heightSpecSize;

// for compatibility, we adjust size based on aspect ratio

if (mVideoWidth * height < width * mVideoHeight) {

//Log.i("@@@", "image too wide, correcting");

width = height * mVideoWidth / mVideoHeight;

} else if (mVideoWidth * height > width * mVideoHeight) {

//Log.i("@@@", "image too tall, correcting");

height = width * mVideoHeight / mVideoWidth;

}

} else if (widthSpecMode == MeasureSpec.EXACTLY) {

// only the width is fixed, adjust the height to match aspect ratio if possible

width = widthSpecSize;

height = width * mVideoHeight / mVideoWidth;

if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {

// couldn't match aspect ratio within the constraints

height = heightSpecSize;

}

} else if (heightSpecMode == MeasureSpec.EXACTLY) {

// only the height is fixed, adjust the width to match aspect ratio if possible

height = heightSpecSize;

width = height * mVideoWidth / mVideoHeight;

if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {

// couldn't match aspect ratio within the constraints

width = widthSpecSize;

}

} else {

// neither the width nor the height are fixed, try to use actual video size

width = mVideoWidth;

height = mVideoHeight;

if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {

// too tall, decrease both width and height

height = heightSpecSize;

width = height * mVideoWidth / mVideoHeight;

}

if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {

// too wide, decrease both width and height

width = widthSpecSize;

height = width * mVideoHeight / mVideoWidth;

}

}

} else {

// no size yet, just adopt the given spec sizes

}

setMeasuredDimension(width, height);

}

@Override

public CharSequence getAccessibilityClassName() {

return TextureVideoView.class.getName();

}

/**

* Sets video path.

*

* @param path the path of the video.

*/

public void setVideoPath(String path) {

setVideoURI(Uri.parse(path));

}

/**

* Sets video URI.

*

* @param uri the URI of the video.

*/

public void setVideoURI(Uri uri) {

setVideoURI(uri, null);

}

/**

* Sets video URI using specific headers.

*

* @param uri the URI of the video.

* @param headers the headers for the URI request.

* Note that the cross domain redirection is allowed by default, but that can be

* changed with key/value pairs through the headers parameter with

* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value

* to disallow or allow cross domain redirection.

*/

public void setVideoURI(Uri uri, Map headers) {

mUri = uri;

mHeaders = headers;

mSeekWhenPrepared = 0;

openVideo();

requestLayout();

invalidate();

}

public void stopPlayback() {

if (mMediaPlayer != null) {

mMediaPlayer.stop();

mMediaPlayer.release();

mMediaPlayer = null;

mCurrentState = STATE_IDLE;

mTargetState = STATE_IDLE;

AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);

am.abandonAudioFocus(null);

}

}

private void openVideo() {

if (mUri == null || null == mSurfaceTexture) {

// not ready for playback just yet, will try again later

return;

}

// we shouldn't clear the target state, because somebody might have

// called start() previously

release(false);

AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

try {

mMediaPlayer = new MediaPlayer();

// TODO: create SubtitleController in MediaPlayer, but we need

// a context for the subtitle renderers

final Context context = getContext();

if (mAudioSession != 0) {

mMediaPlayer.setAudioSessionId(mAudioSession);

} else {

mAudioSession = mMediaPlayer.getAudioSessionId();

}

mMediaPlayer.setOnPreparedListener(mPreparedListener);

mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);

mMediaPlayer.setOnCompletionListener(mCompletionListener);

mMediaPlayer.setOnErrorListener(mErrorListener);

mMediaPlayer.setOnInfoListener(mInfoListener);

mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);

mCurrentBufferPercentage = 0;

mMediaPlayer.setDataSource(mContext, mUri, mHeaders);

// modified

mMediaPlayer.setSurface(new Surface(mSurfaceTexture));

mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

mMediaPlayer.setScreenOnWhilePlaying(true);

mMediaPlayer.prepareAsync();

// we don't set the target state here either, but preserve the

// target state that was there before.

mCurrentState = STATE_PREPARING;

attachMediaController();

} catch (IOException ex) {

Log.w(TAG, "Unable to open content: " + mUri, ex);

mCurrentState = STATE_ERROR;

mTargetState = STATE_ERROR;

mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);

return;

} catch (IllegalArgumentException ex) {

Log.w(TAG, "Unable to open content: " + mUri, ex);

mCurrentState = STATE_ERROR;

mTargetState = STATE_ERROR;

mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);

return;

} finally {

}

}

public void setMediaController(MediaController controller) {

if (mMediaController != null) {

mMediaController.hide();

}

mMediaController = controller;

attachMediaController();

}

private void attachMediaController() {

if (mMediaPlayer != null && mMediaController != null) {

mMediaController.setMediaPlayer(this);

View anchorView = this.getParent() instanceof View ?

(View) this.getParent() : this;

mMediaController.setAnchorView(anchorView);

mMediaController.setEnabled(isInPlaybackState());

}

}

MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =

new MediaPlayer.OnVideoSizeChangedListener() {

public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {

mVideoWidth = mp.getVideoWidth();

mVideoHeight = mp.getVideoHeight();

if (mVideoWidth != 0 && mVideoHeight != 0) {

setFixedSize(mVideoWidth, mVideoHeight);

requestLayout();

}

}

};

MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {

public void onPrepared(MediaPlayer mp) {

mCurrentState = STATE_PREPARED;

// 有条件的可以实现,这里我就不实现啦

// Get the capabilities of the player for this stream

// Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,

// MediaPlayer.BYPASS_METADATA_FILTER);

//

// if (data != null) {

// mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)

// || data.getBoolean(Metadata.PAUSE_AVAILABLE);

// mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)

// || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);

// mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)

// || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);

// } else {

// }

mCanPause = mCanSeekBack = mCanSeekForward = true;

if (mOnPreparedListener != null) {

mOnPreparedListener.onPrepared(mMediaPlayer);

}

if (mMediaController != null) {

mMediaController.setEnabled(true);

}

mVideoWidth = mp.getVideoWidth();

mVideoHeight = mp.getVideoHeight();

int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call

if (seekToPosition != 0) {

seekTo(seekToPosition);

}

if (mVideoWidth != 0 && mVideoHeight != 0) {

//Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);

setFixedSize(mVideoWidth, mVideoHeight);

if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {

// We didn't actually change the size (it was already at the size

// we need), so we won't get a "surface changed" callback, so

// start the video here instead of in the callback.

if (mTargetState == STATE_PLAYING) {

start();

if (mMediaController != null) {

mMediaController.show();

}

} else if (!isPlaying() &&

(seekToPosition != 0 || getCurrentPosition() > 0)) {

if (mMediaController != null) {

// Show the media controls when we're paused into a video and make 'em stick.

mMediaController.show(0);

}

}

}

} else {

// We don't know the video size yet, but should start anyway.

// The video size might be reported to us later.

if (mTargetState == STATE_PLAYING) {

start();

}

}

}

};

private MediaPlayer.OnCompletionListener mCompletionListener =

new MediaPlayer.OnCompletionListener() {

public void onCompletion(MediaPlayer mp) {

mCurrentState = STATE_PLAYBACK_COMPLETED;

mTargetState = STATE_PLAYBACK_COMPLETED;

if (mMediaController != null) {

mMediaController.hide();

}

if (mOnCompletionListener != null) {

mOnCompletionListener.onCompletion(mMediaPlayer);

}

}

};

private MediaPlayer.OnInfoListener mInfoListener =

new MediaPlayer.OnInfoListener() {

public boolean onInfo(MediaPlayer mp, int arg1, int arg2) {

if (mOnInfoListener != null) {

mOnInfoListener.onInfo(mp, arg1, arg2);

}

return true;

}

};

private MediaPlayer.OnErrorListener mErrorListener =

new MediaPlayer.OnErrorListener() {

public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {

Log.d(TAG, "Error: " + framework_err + "," + impl_err);

mCurrentState = STATE_ERROR;

mTargetState = STATE_ERROR;

if (mMediaController != null) {

mMediaController.hide();

}

/* If an error handler has been supplied, use it and finish. */

if (mOnErrorListener != null) {

if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {

return true;

}

}

return true;

}

};

private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =

new MediaPlayer.OnBufferingUpdateListener() {

public void onBufferingUpdate(MediaPlayer mp, int percent) {

mCurrentBufferPercentage = percent;

}

};

/**

* Register a callback to be invoked when the media file

* is loaded and ready to go.

*

* @param l The callback that will be run

*/

public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {

mOnPreparedListener = l;

}

/**

* Register a callback to be invoked when the end of a media file

* has been reached during playback.

*

* @param l The callback that will be run

*/

public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) {

mOnCompletionListener = l;

}

/**

* Register a callback to be invoked when an error occurs

* during playback or setup. If no listener is specified,

* or if the listener returned false, VideoView will inform

* the user of any errors.

*

* @param l The callback that will be run

*/

public void setOnErrorListener(MediaPlayer.OnErrorListener l) {

mOnErrorListener = l;

}

/**

* Register a callback to be invoked when an informational event

* occurs during playback or setup.

*

* @param l The callback that will be run

*/

public void setOnInfoListener(MediaPlayer.OnInfoListener l) {

mOnInfoListener = l;

}

/*

* release the media player in any state

*/

private void release(boolean cleartargetstate) {

if (mMediaPlayer != null) {

mMediaPlayer.reset();

mMediaPlayer.release();

mMediaPlayer = null;

mCurrentState = STATE_IDLE;

if (cleartargetstate) {

mTargetState = STATE_IDLE;

}

AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

am.abandonAudioFocus(null);

}

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

if (isInPlaybackState() && mMediaController != null) {

toggleMediaControlsVisiblity();

}

return false;

}

@Override

public boolean onTrackballEvent(MotionEvent ev) {

if (isInPlaybackState() && mMediaController != null) {

toggleMediaControlsVisiblity();

}

return false;

}

@Override

public boolean onKeyDown(int keyCode, KeyEvent event) {

boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&

keyCode != KeyEvent.KEYCODE_VOLUME_UP &&

keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&

keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&

keyCode != KeyEvent.KEYCODE_MENU &&

keyCode != KeyEvent.KEYCODE_CALL &&

keyCode != KeyEvent.KEYCODE_ENDCALL;

if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {

if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||

keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {

if (mMediaPlayer.isPlaying()) {

pause();

mMediaController.show();

} else {

start();

mMediaController.hide();

}

return true;

} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {

if (!mMediaPlayer.isPlaying()) {

start();

mMediaController.hide();

}

return true;

} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP

|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {

if (mMediaPlayer.isPlaying()) {

pause();

mMediaController.show();

}

return true;

} else {

toggleMediaControlsVisiblity();

}

}

return super.onKeyDown(keyCode, event);

}

@Override

public void start() {

if (isInPlaybackState()) {

mMediaPlayer.start();

mCurrentState = STATE_PLAYING;

}

mTargetState = STATE_PLAYING;

}

@Override

public void pause() {

if (isInPlaybackState()) {

if (mMediaPlayer.isPlaying()) {

mMediaPlayer.pause();

mCurrentState = STATE_PAUSED;

}

}

mTargetState = STATE_PAUSED;

}

public void suspend() {

release(false);

}

public void resume() {

openVideo();

}

@Override

public int getDuration() {

if (isInPlaybackState()) {

return mMediaPlayer.getDuration();

}

return -1;

}

@Override

public int getCurrentPosition() {

if (isInPlaybackState()) {

return mMediaPlayer.getCurrentPosition();

}

return 0;

}

@Override

public void seekTo(int msec) {

if (isInPlaybackState()) {

mMediaPlayer.seekTo(msec);

mSeekWhenPrepared = 0;

} else {

mSeekWhenPrepared = msec;

}

}

@Override

public boolean isPlaying() {

return isInPlaybackState() && mMediaPlayer.isPlaying();

}

@Override

public int getBufferPercentage() {

if (mMediaPlayer != null) {

return mCurrentBufferPercentage;

}

return 0;

}

private boolean isInPlaybackState() {

return (mMediaPlayer != null &&

mCurrentState != STATE_ERROR &&

mCurrentState != STATE_IDLE &&

mCurrentState != STATE_PREPARING);

}

@Override

public boolean canPause() {

return mCanPause;

}

@Override

public boolean canSeekBackward() {

return mCanSeekBack;

}

@Override

public boolean canSeekForward() {

return mCanSeekForward;

}

@Override

public int getAudioSessionId() {

if (mAudioSession == 0) {

MediaPlayer foo = new MediaPlayer();

mAudioSession = foo.getAudioSessionId();

foo.release();

}

return mAudioSession;

}

private void toggleMediaControlsVisiblity() {

if (mMediaController.isShowing()) {

mMediaController.hide();

} else {

mMediaController.show();

}

}

private void init() {

mVideoWidth = 0;

mVideoHeight = 0;

setFocusable(true);

setFocusableInTouchMode(true);

requestFocus();

mCurrentState = STATE_IDLE;

mTargetState = STATE_IDLE;

mContext = getContext();

setSurfaceTextureListener(mSurfaceTextureListener);

}

private void setFixedSize(int width, int height) {

mSurfaceWidth = this.mVideoWidth;

mSurfaceHeight = this.mVideoHeight;

if (this.mVideoWidth != width && this.mVideoHeight != height) {

this.mVideoWidth = width;

this.mVideoHeight = height;

requestLayout();

}

}

/**

* surface回调,需要在这里获取mSurfaceTexture

*/

private SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() {

@Override

public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

mSurfaceTexture = surface;

openVideo();

}

@Override

public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

mSurfaceWidth = width;

mSurfaceHeight = height;

boolean isValidState = (mTargetState == STATE_PLAYING);

boolean hasValidSize = (mVideoWidth == width && mVideoHeight == height);

if (mMediaPlayer != null && isValidState && hasValidSize) {

if (mSeekWhenPrepared != 0) {

seekTo(mSeekWhenPrepared);

}

start();

}

}

@Override

public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {

mSurfaceTexture = null;

if (mMediaController != null) {

mMediaController.hide();

}

release(true);

return false;

}

@Override

public void onSurfaceTextureUpdated(SurfaceTexture surface) {

}

};

}

android texturevideoview 缓存,TextureView实现VideoView相关推荐

  1. android texturevideoview 缓存,Android TextureView与VideoView性能

    Because a SurfaceView's content does not live in the application's window, it cannot be transformed ...

  2. android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估

    文章目录 android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估 引言 使用方 ...

  3. Android 图片缓存之内存缓存技术LruCache,软引用

    Android 图片缓存之内存缓存技术LruCache,软引用

  4. Android图片缓存之Lru算法

    前言: 上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小.我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发 ...

  5. Android图片缓存框架Glide

    Android图片缓存框架Glide Glide是Google提供的一个组件.它具有获取.解码和展示视频剧照.图片.动画等功能.它提供了灵活的API,帮助开发者将Glide应用在几乎任何网络协议栈中. ...

  6. 浅谈Android视频缓存库

    背景 我们都了解播放器的作用就是把音视频压缩数据转换成原始的音视频数据渲染出来,这样我们就可以看到画面.听到声音了.这里的播放器就存在两个问题,第一个问题是视频源存在云端,我们每次看完视频之后重新观看 ...

  7. android图片缓存,直接应用项目中的Android图片缓存技术

    前不久搞的Android图片缓存,刚开始引入开源的框架,用着还行,但是在开发中遇到问题,就比如universal-image-loader-1.9.5.jar这个框架吧,在加载图片的时候自定义imag ...

  8. 将android studio产生的.gradle .android .androidStudio缓存从默认C盘移动到D盘

    启动AVD时:haxm device is not found 解决办法:下载haxm补丁包安装即可. why? android studio 在编译的时候会下载 builde.gradle 文件下 ...

  9. android强制缓存写磁盘,优雅的构建 Android 项目之磁盘缓存(DiskLruCache)

    Android 的缓存技术 一个优秀的应用首先它的用户体验是优秀的,在 Android 应用中恰当的使用缓存技术不仅可以缓解服务器压力还可以优化用户的使用体验,减少用户流量的使用.在 Android ...

最新文章

  1. ubuntu安装新字体
  2. php对手时间戳判断,PHP 中判断一个数字是否是Unix 时间戳
  3. nyoj-68--三点顺序
  4. 3种方法来卸载Ubuntu软件
  5. 华为交换机查看端口流量_华为交换机限速及流量统计配置
  6. 服务器精益改善系列,精益生产改善的内容是什么?
  7. 关于QueryRunner数据查询以及常用方法
  8. Golang Import使用入门
  9. linux红帽7修改时间,CentOS 7 and RedHat 7 时间同步即chrony服务配置
  10. 电厂数字化进阶之路(一):光明的使者
  11. php变形的itf条码,itf14条码生成器 第14章生成器.doc
  12. Adobe Flash Player 不是最新版本
  13. 周报—FPGA(浩子)
  14. 手机破解并连接WiFi后,可以通过USB数据线与电脑共享WiFi
  15. 明明是那么好的人,却又是那么伤人的人
  16. [emerg] 30766#0: unknown directive rtmp in /usr/local/etc/nginx/nginx.conf:16
  17. 【清华集训2014】【线段树】玄学
  18. Dropbox免费网盘,实现多台电脑上文件共享和同步文件!!
  19. 3Ds Max快速入门(一)
  20. PW4053M原厂芯片5V升压12.6V1A,三节串联锂电池充电管理板

热门文章

  1. 【MySQL 数据的操作二】
  2. 一文读懂正则化:LASSO回归、Ridge回归、ElasticNet 回归
  3. 计算机专用单词缩写汇总
  4. C语言自学完备手册(01)——开篇和入门
  5. 伪随机与真随机有什么不同?
  6. 不看后悔系列:怎么能贴到真龙膜
  7. java injection_java安全编码指南之:输入注入injection
  8. 无需加好友,即可实现QQ对话。
  9. linux下查看文件的修改时间和文件大小排序排列
  10. namenode -format时org.apache.hadoop.ipc.Client:Retrying connect to serverAlready tried time(s)解决方案