博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android截屏方案
阅读量:6574 次
发布时间:2019-06-24

本文共 24368 字,大约阅读时间需要 81 分钟。

Android截屏

Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途

在截屏功能中,有时需要截取全屏的内容,有时需要截取超过一屏的内容(比如:Listview,Scrollview,RecyclerView)。下面介绍各种场景获取Bitmap的方法

普通截屏的实现

获取当前Window的DrawingCache的方式,即decorView的DrawingCache

/**   * shot the current screen ,with the status but the status is trans *   *   * @param ctx current activity   */  public static Bitmap shotActivity(Activity ctx) {    View view = ctx.getWindow().getDecorView();    view.setDrawingCacheEnabled(true);    view.buildDrawingCache();    Bitmap bp = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(),        view.getMeasuredHeight());    view.setDrawingCacheEnabled(false);    view.destroyDrawingCache();    return bp;  }复制代码

获取当前View的DrawingCache

public static Bitmap getViewBp(View v) {        if (null == v) {            return null;        }        v.setDrawingCacheEnabled(true);        v.buildDrawingCache();        if (Build.VERSION.SDK_INT >= 11) {            v.measure(MeasureSpec.makeMeasureSpec(v.getWidth(),                    MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(                    v.getHeight(), MeasureSpec.EXACTLY));            v.layout((int) v.getX(), (int) v.getY(),                    (int) v.getX() + v.getMeasuredWidth(),                    (int) v.getY() + v.getMeasuredHeight());        } else {            v.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));            v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());        }        Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());        v.setDrawingCacheEnabled(false);        v.destroyDrawingCache();        return b;    }复制代码

开源方案

在滚动视图中,如果当前View并没有在视图中全部绘制出来,我们可以利用View的ScrollTo()和ScrollBy()方法来移动画布,同时获取当前View的可视部分的DrawingCache,最后进行拼接得到其Bitmap,参考:。

Scrollview截屏

三个截屏中,ScrollView最简单,因为ScrollView只有一个childView,虽然没有全部显示在界面上,但是已经全部渲染绘制,因此可以直接 调用scrollView.draw(canvas)来完成截图

public static Bitmap shotScrollView(ScrollView scrollView) {    int h = 0;    Bitmap bitmap = null;    for (int i = 0; i < scrollView.getChildCount(); i++) {      h += scrollView.getChildAt(i).getHeight();      scrollView.getChildAt(i).setBackgroundColor(Color.parseColor("#ffffff"));    }    bitmap = Bitmap.createBitmap(scrollView.getWidth(), h, Bitmap.Config.RGB_565);    final Canvas canvas = new Canvas(bitmap);    scrollView.draw(canvas);    return bitmap;  }复制代码

listview截屏

而ListView就是会回收与重用Item,并且只会绘制在屏幕上显示的ItemView,根据stackoverflow上大神的建议,采用一个List来存储Item的视图,这种方案依然不够好,当Item足够多的时候,可能会发生oom。

public static Bitmap shotListView(ListView listview) {    ListAdapter adapter = listview.getAdapter();    int itemscount = adapter.getCount();    int allitemsheight = 0;    List
bmps = new ArrayList
(); for (int i = 0; i < itemscount; i++) { View childView = adapter.getView(i, null, listview); childView.measure( View.MeasureSpec.makeMeasureSpec(listview.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); childView.setDrawingCacheEnabled(true); childView.buildDrawingCache(); bmps.add(childView.getDrawingCache()); allitemsheight += childView.getMeasuredHeight(); } Bitmap bigbitmap = Bitmap.createBitmap(listview.getMeasuredWidth(), allitemsheight, Bitmap.Config.ARGB_8888); Canvas bigcanvas = new Canvas(bigbitmap); Paint paint = new Paint(); int iHeight = 0; for (int i = 0; i < bmps.size(); i++) { Bitmap bmp = bmps.get(i); bigcanvas.drawBitmap(bmp, 0, iHeight, paint); iHeight += bmp.getHeight(); bmp.recycle(); bmp = null; } return bigbitmap; }复制代码

RecyclerView截屏

我们都知道,在新的Android版本中,已经可以用RecyclerView来代替使用ListView的场景,相比较ListView,RecyclerView对Item View的缓存支持的更好。可以采用和ListView相同的方案,这里也是在stackoverflow上看到的方案。

public static Bitmap shotRecyclerView(RecyclerView view) {    RecyclerView.Adapter adapter = view.getAdapter();    Bitmap bigBitmap = null;    if (adapter != null) {      int size = adapter.getItemCount();      int height = 0;      Paint paint = new Paint();      int iHeight = 0;      final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);      // Use 1/8th of the available memory for this memory cache.      final int cacheSize = maxMemory / 8;      LruCache
bitmaCache = new LruCache<>(cacheSize); for (int i = 0; i < size; i++) { RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i)); adapter.onBindViewHolder(holder, i); holder.itemView.measure( View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight()); holder.itemView.setDrawingCacheEnabled(true); holder.itemView.buildDrawingCache(); Bitmap drawingCache = holder.itemView.getDrawingCache(); if (drawingCache != null) { bitmaCache.put(String.valueOf(i), drawingCache); } height += holder.itemView.getMeasuredHeight(); } bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888); Canvas bigCanvas = new Canvas(bigBitmap); Drawable lBackground = view.getBackground(); if (lBackground instanceof ColorDrawable) { ColorDrawable lColorDrawable = (ColorDrawable) lBackground; int lColor = lColorDrawable.getColor(); bigCanvas.drawColor(lColor); } for (int i = 0; i < size; i++) { Bitmap bitmap = bitmaCache.get(String.valueOf(i)); bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint); iHeight += bitmap.getHeight(); bitmap.recycle(); } } return bigBitmap; }复制代码

上面的方法在截取存在异步加载图片的RecyclerView时候会出现加载不出图片的情况,这里再补充一种滚动式截屏的方法

public static void screenShotRecycleView(final RecyclerView mRecyclerView, final  RecycleViewRecCallback callBack) {        if (mRecyclerView == null) {            return;        }        BaseListFragment.MyAdapter adapter = (BaseListFragment.MyAdapter) mRecyclerView.getAdapter();        final Paint paint = new Paint();        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);        // Use 1/8th of the available memory for this memory cache.        final int cacheSize = maxMemory / 8;        LruCache
bitmaCache = new LruCache<>(cacheSize); final int oneScreenHeight = mRecyclerView.getMeasuredHeight(); int shotHeight = 0; if (adapter != null && adapter.getData().size() > 0) { int headerSize = adapter.getHeaderLayoutCount(); int dataSize = adapter.getData().size(); for (int i = 0; i < headerSize + dataSize; i++) { BaseViewHolder holder = (BaseViewHolder) adapter.createViewHolder(mRecyclerView, adapter.getItemViewType(i)); if (i >= headerSize) adapter.startConvert(holder, adapter.getData().get(i - headerSize)); holder.itemView.measure( View.MeasureSpec.makeMeasureSpec(mRecyclerView.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight()); holder.itemView.setDrawingCacheEnabled(true); holder.itemView.buildDrawingCache(); Bitmap drawingCache = holder.itemView.getDrawingCache(); //holder.itemView.destroyDrawingCache();//释放缓存占用的资源 if (drawingCache != null) { bitmaCache.put(String.valueOf(i), drawingCache); } shotHeight += holder.itemView.getHeight(); if (shotHeight > 12000) { //设置截图最大值 if (callBack != null) callBack.onRecFinished(null); return; } } //添加底部高度(加载更多或loading布局高度,此处为固定值:) final int footHight = Util.dip2px(mRecyclerView.getContext(), 42); shotHeight += footHight; //返回到顶部 while (mRecyclerView.canScrollVertically(-1)) { mRecyclerView.scrollBy(0, -oneScreenHeight); } //绘制截图的背景 final Bitmap bigBitmap = Bitmap.createBitmap(mRecyclerView.getMeasuredWidth(), shotHeight, Bitmap.Config.ARGB_8888); final Canvas bigCanvas = new Canvas(bigBitmap); Drawable lBackground = mRecyclerView.getBackground(); if (lBackground instanceof ColorDrawable) { ColorDrawable lColorDrawable = (ColorDrawable) lBackground; int lColor = lColorDrawable.getColor(); bigCanvas.drawColor(lColor); } final int[] drawOffset = {0}; final Canvas canvas = new Canvas(); if (shotHeight <= oneScreenHeight) { //仅有一页 Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888); canvas.setBitmap(bitmap); mRecyclerView.draw(canvas); if (callBack != null) callBack.onRecFinished(bitmap); } else { //超过一页 final int finalShotHeight = shotHeight; mRecyclerView.postDelayed(new Runnable() { @Override public void run() { if ((drawOffset[0] + oneScreenHeight < finalShotHeight)) { //超过一屏 Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888); canvas.setBitmap(bitmap); mRecyclerView.draw(canvas); bigCanvas.drawBitmap(bitmap, 0, drawOffset[0], paint); drawOffset[0] += oneScreenHeight; mRecyclerView.scrollBy(0, oneScreenHeight); try { bitmap.recycle(); } catch (Exception ex) { ex.printStackTrace(); } mRecyclerView.postDelayed(this, 10); } else { //不足一屏时的处理 int leftHeight = finalShotHeight - drawOffset[0] - footHight; mRecyclerView.scrollBy(0, leftHeight); int top = oneScreenHeight - (finalShotHeight - drawOffset[0]); if (top > 0 && leftHeight > 0) { Bitmap bitmap = Bitmap.createBitmap(mRecyclerView.getWidth(), mRecyclerView.getHeight(), Bitmap.Config.ARGB_8888); canvas.setBitmap(bitmap); mRecyclerView.draw(canvas); //截图,只要补足的那块图 bitmap = Bitmap.createBitmap(bitmap, 0, top, bitmap.getWidth(), leftHeight, null, false); bigCanvas.drawBitmap(bitmap, 0, drawOffset[0], paint); try { bitmap.recycle(); } catch (Exception ex) { ex.printStackTrace(); } } if (callBack != null) callBack.onRecFinished(bigBitmap); } } }, 10); } } } public interface RecycleViewRecCallback { void onRecFinished(Bitmap bitmap); }复制代码

相信有不少小伙伴用BRVH第三方库来做recycleview的适配器的。使用这个库的话再用上面的方法会报角标越界的错误,看了BRVH的源码

public void onBindViewHolder(ViewHolder holder, int positions) {        int viewType = holder.getItemViewType();        switch(viewType) {        case 0:            this.convert((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));        case 273:        case 819:        case 1365:            break;        case 546:            this.addLoadMore(holder);            break;        default:            this.convert((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));            this.onBindDefViewHolder((BaseViewHolder)holder, this.mData.get(holder.getLayoutPosition() - this.getHeaderLayoutCount()));        }    }复制代码

在调用adapter.onBindViewHolder时,因为里面的position参数未使用,里面用的计算holder.getLayoutPosition() - this.getHeaderLayoutCount()的值一直是-1导致角标越界报错。

本人理解,RecyclerView的截屏原理是,首先构造每个item的ViewHolder,然后调用具体设置数据到每个item的方法,此时cache中就存有item的内容,此时绘制就能获取到完整的内容。采用v7包中的onBindViewHolder方法即可,或者是BRVH的convert方法,可以看到BRVH中没有暴露出这个方法,而且唯一暴露出的onBindViewHolder还会报角标越界错误,此时我们就需要在BRVH的基础上暴露出convert即可,代码如下

public class MyAdapter extends BaseQuickAdapter
{ public MyAdapter() { super(getItemLayoutResId(), datas); } /** * 用于对外暴露convert方法,构造缓存视图(截屏用) * @param viewHolder * @param t */ public void startConvert(BaseViewHolder viewHolder, T t){ convert(viewHolder,t); } @Override protected void convert(BaseViewHolder viewHolder, T t) { bindView(viewHolder, t); } }复制代码

然后将上面所述的获取Bitmap方法修改一下

/**     * 截取recycler view     */    public static Bitmap getRecyclerViewScreenshot(RecyclerView view) {        BaseListFragment.MyAdapter adapter = (BaseListFragment.MyAdapter) view.getAdapter();        Bitmap bigBitmap = null;        if (adapter != null) {            int size = adapter.getData().size();            int height = 0;            Paint paint = new Paint();            int iHeight = 0;            final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);            // Use 1/8th of the available memory for this memory cache.            final int cacheSize = maxMemory / 8;            LruCache
bitmaCache = new LruCache<>(cacheSize); for (int i = 0; i < size; i++) { BaseViewHolder holder = (BaseViewHolder) adapter.createViewHolder(view, adapter.getItemViewType(i)); //此处需要调用convert方法,否则绘制出来的都是空的item adapter.startConvert(holder, adapter.getData().get(i)); holder.itemView.measure( View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(), holder.itemView.getMeasuredHeight()); holder.itemView.setDrawingCacheEnabled(true); holder.itemView.buildDrawingCache(); Bitmap drawingCache = holder.itemView.getDrawingCache(); if (drawingCache != null) { bitmaCache.put(String.valueOf(i), drawingCache); } height += holder.itemView.getMeasuredHeight(); } bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888); Canvas bigCanvas = new Canvas(bigBitmap); Drawable lBackground = view.getBackground(); if (lBackground instanceof ColorDrawable) { ColorDrawable lColorDrawable = (ColorDrawable) lBackground; int lColor = lColorDrawable.getColor(); bigCanvas.drawColor(lColor); } for (int i = 0; i < size; i++) { Bitmap bitmap = bitmaCache.get(String.valueOf(i)); bigCanvas.drawBitmap(bitmap, 0f, iHeight, paint); iHeight += bitmap.getHeight(); bitmap.recycle(); } } return bigBitmap; }复制代码

合成Bitmap

比如四张合成一张

/**     * 将四张图拼成一张     *     * @param pic1 图一     * @param pic2 图二     * @param pic3 图三     * @param pic4 图四     * @return only_bitmap     * 详情见说明:{@link com.bertadata.qxb.util.ScreenShotUtils}     */    public static Bitmap combineBitmapsIntoOnlyOne(Bitmap pic1, Bitmap pic2, Bitmap pic3, Bitmap pic4, Activity context) {        int w_total = pic2.getWidth();        int h_total = pic1.getHeight() + pic2.getHeight() + pic3.getHeight() + pic4.getHeight();        int h_pic1 = pic1.getHeight();        int h_pic4 = pic4.getHeight();        int h_pic12 = pic1.getHeight() + pic2.getHeight();        //此处为防止OOM需要对高度做限制        if (h_total > HEIGHTLIMIT) {            return null;        }        Bitmap only_bitmap = Bitmap.createBitmap(w_total, h_total, Bitmap.Config.ARGB_4444);        Canvas canvas = new Canvas(only_bitmap);        canvas.drawColor(ContextCompat.getColor(context, R.color.color_content_bg));        canvas.drawBitmap(pic1, 0, 0, null);        canvas.drawBitmap(pic2, 0, h_pic1, null);        canvas.drawBitmap(pic3, 0, h_pic12, null);        canvas.drawBitmap(pic4, 0, h_total - h_pic4, null);        return only_bitmap;    }复制代码

图片后期处理

/**     * 将传入的Bitmap合理压缩后输出到系统截屏目录下     * 命名格式为:Screenshot+时间戳+启信宝报名.jpg     * 同时通知系统重新扫描系统文件     *     * @param pic1    图一 标题栏截图     * @param pic2    图二 scrollview截图     * @param context 用于通知重新扫描文件系统,为提升性能可去掉     *                详情见说明:{@link com.bertadata.qxb.util.ScreenShotUtils}     */    public static void savingBitmapIntoFile(final Bitmap pic1, final Bitmap pic2, final Activity context, final BitmapAndFileCallBack callBack) {        if (context == null || context.isFinishing()) {            return;        }        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                String fileReturnPath = "";                int w = pic1.getWidth();                Bitmap bottom = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_picture_combine_bottom);                Bitmap top_banner = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_picture_combine_top);                Bitmap bitmap_bottom = anyRatioCompressing(bottom, (float) w / bottom.getWidth(), (float) w / bottom.getWidth());                Bitmap bitmap_top = anyRatioCompressing(top_banner, (float) w / bottom.getWidth(), (float) w / bottom.getWidth());                final Bitmap only_bitmap = combineBitmapsIntoOnlyOne(bitmap_top, pic1, pic2, bitmap_bottom, context);                // 获取当前时间                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-ms", Locale.getDefault());                String data = sdf.format(new Date());                // 获取内存路径                // 设置图片路径+命名规范                // 声明输出文件                String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath();                String fileTitle = "Screenshot_" + data + "_com.bertadata.qxb.biz_info.jpg";                String filePath = storagePath + "/DCIM/";                final String fileAbsolutePath = filePath + fileTitle;                File file = new File(fileAbsolutePath);                /**                 * 质压与比压结合                 * 分级压缩                 * 输出文件                 */                if (only_bitmap != null) {                    try {                        // 首先,对原图进行一步质量压缩,形成初步文件                        FileOutputStream fos = new FileOutputStream(file);                        only_bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);                        // 另建一个文件other_file预备输出                        String other_fileTitle = "Screenshot_" + data + "_com.bertadata.qxb.jpg";                        String other_fileAbsolutePath = filePath + other_fileTitle;                        File other_file = new File(other_fileAbsolutePath);                        FileOutputStream other_fos = new FileOutputStream(other_file);                        // 其次,要判断质压之后的文件大小,按文件大小分级进行处理                        long file_size = file.length() / 1024; // size of file(KB)                        if (file_size < 0 || !(file.exists())) {                            // 零级: 文件判空                            throw new NullPointerException();                        } else if (file_size > 0 && file_size <= 256) {                            // 一级: 直接输出                            deleteFile(other_file);                            // 通知刷新文件系统,显示最新截取的图文件                            fileReturnPath = fileAbsolutePath;                            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + fileAbsolutePath)));                        } else if (file_size > 256 && file_size <= 768) {                            // 二级: 简单压缩:压缩为原比例的3/4,质压为50%                            anyRatioCompressing(only_bitmap, (float) 3 / 4, (float) 3 / 4).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);                            deleteFile(file);                            // 通知刷新文件系统,显示最新截取的图文件                            fileReturnPath = other_fileAbsolutePath;                            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));                        } else if (file_size > 768 && file_size <= 1280) {                            // 三级: 中度压缩:压缩为原比例的1/2,质压为40%                            anyRatioCompressing(only_bitmap, (float) 1 / 2, (float) 1 / 2).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);                            deleteFile(file);                            // 通知刷新文件系统,显示最新截取的图文件                            fileReturnPath = other_fileAbsolutePath;                            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));                        } else if (file_size > 1280 && file_size <= 2048) {                            // 四级: 大幅压缩:压缩为原比例的1/3,质压为40%                            anyRatioCompressing(only_bitmap, (float) 1 / 3, (float) 1 / 3).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);                            deleteFile(file);                            // 通知刷新文件系统,显示最新截取的图文件                            fileReturnPath = other_fileAbsolutePath;                            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));                        } else if (file_size > 2048) {                            // 五级: 中度压缩:压缩为原比例的1/2,质压为40%                            anyRatioCompressing(only_bitmap, (float) 1 / 2, (float) 1 / 2).compress(Bitmap.CompressFormat.JPEG, 40, other_fos);                            deleteFile(file);                            // 通知刷新文件系统,显示最新截取的图文件                            fileReturnPath = other_fileAbsolutePath;                            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + other_fileAbsolutePath)));                        }                        // 注销fos;                        fos.flush();                        other_fos.flush();                        other_fos.close();                        fos.close();                        //callback用于回传保存成功的路径以及Bitmap                        callBack.onSuccess(only_bitmap, fileReturnPath);                    } catch (Exception e) {                        e.printStackTrace();                    }                } else callBack.onSuccess(null, "");            }        });        thread.start();    }复制代码
/**     * 可实现任意宽高比例压缩(宽高压比可不同)的压缩方法(主要用于微压)     *     * @param bitmap       源图     * @param width_ratio  宽压比(float)(0<&&<1)     * @param height_ratio 高压比(float)(0<&&<1)     * @return 目标图片     * 

*/ public static Bitmap anyRatioCompressing(Bitmap bitmap, float width_ratio, float height_ratio) { Matrix matrix = new Matrix(); matrix.postScale(width_ratio, height_ratio); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); }复制代码

本文参考http://www.cnblogs.com/BoBoMEe/p/4556917.html,并结合自己实际项目操作完成

下面是本人的微信公众号,欢迎大家关注??????~~

转载于:https://juejin.im/post/5a33403b6fb9a045132abdb6

你可能感兴趣的文章
【机器学习】--关联规则算法从初识到应用
查看>>
windows 下nginx php安装
查看>>
MOTO XT702添加开机音乐
查看>>
Codeforces Round #565 (Div. 3) C. Lose it!
查看>>
Python脚本日志系统
查看>>
Spring异常——BeanNotOfRequiredTypeException
查看>>
B0BO TFS 安装指南(转载)
查看>>
gulp常用命令
查看>>
TCP(Socket基础编程)
查看>>
RowSet的使用
查看>>
表单提交中的input、button、submit的区别
查看>>
每日一记--cookie
查看>>
约瑟夫环
查看>>
S5:桥接模式 Bridge
查看>>
线程池-Executors
查看>>
WPF and Silverlight 学习笔记(十二):WPF Panel内容模型、Decorator内容模型及其他...
查看>>
FLUSH TABLES WITH READ LOCK 和 LOCK TABLES比较
查看>>
MySQL:创建、修改和删除表
查看>>
Java多线程程序设计详细解析
查看>>
IOS 7 Study - UISegmentedControl
查看>>