Android 善用Okio简化处理I/O操作

Okio库是一个由square公司开发的,它补充了java.io和java.nio的不足,以便能够更加方便,快速的访问、存储和处理你的数据。而OkHttp的底层也使用该库作为支持。而在开发中,使用该库可以大大给你带来方便。

目前,Okio的最新版本是1.6.0,gradle的引用如下


compile ‘com.squareup.okio:okio:1.6.0‘

Okio中有两个关键的接口,SinkSource,这两个接口都继承了Closeable接口;而Sink可以简单的看做OutputStream,Source可以简单的看做InputStream。而这两个接口都是支持读写超时设置的。结构图如下

它们各自有一个支持缓冲区的子类接口,BufferedSink和BufferedSource,而BufferedSink有一个实现类RealBufferedSink,BufferedSource有一个实现类RealBufferedSource;此外,Sink和Source它门还各自有一个支持gzip压缩的实现类GzipSink和GzipSource;一个具有委托功能的抽象类ForwardingSink和ForwardingSource;还有一个实现类便是InflaterSource和DeflaterSink,这两个类主要用于压缩,为GzipSink和GzipSource服务;整体的结构图如下

BufferedSink中定义了一系列写入缓存区的方法,比如write方法写byte数组,writeUtf8写字符串,还有一些列的writeByte,writeString,writeShort,writeInt,writeLong,writeDecimalLong等等方法;

BufferedSource定义的方法和BufferedSink极为相似,只不过一个是写一个是读,基本上都是一一对应的,如readUtf8,readByte,readString,readShort,readInt等等等等。这两个接口中的方法有兴趣的点源码进去看就可以了。

而这两个支持缓冲区的接口的实现类RealBufferedSink和RealBufferedSource都是通过包装一个Sink+Buffer或者Source+Buffer来进行实现的。如下图所示

拿RealBufferedSink来举例,实际调用的write的一系列方法,都是直接的对成员变量buffer进行的操作,当写入buffer成功后,最后会调用一个方法将buffer中的内容写入到sink中去,我们随便拿一个方法看一下


    public BufferedSink writeLong(long v) throws IOException {

        if(this.closed) {

            throw new IllegalStateException("closed");

        } else {

            this.buffer.writeLong(v);

            return this.emitCompleteSegments();

        }

    }

可以看到,首先会判断closed成员变量是否是标记着关闭,如果已经关闭了则扔出一个异常,否则将内容写入到buffer,写入完成后调用了一个emitCompleteSegments的方法,该方法中做了什么呢,没错,就是将buffer中的内容写入到sink成员变量中去,然后将自身返回。


    public BufferedSink emitCompleteSegments() throws IOException {

        if(this.closed) {

            throw new IllegalStateException("closed");

        } else {

            long byteCount = this.buffer.completeSegmentByteCount();

            if(byteCount > 0L) {

                this.sink.write(this.buffer, byteCount);

            }

            return this;

        }

    }

这两个实现类的内部的所有方法都是类似的,这里不一一展开。

而这一切的背后都是一个叫做Buffer的类在支持着缓冲区,Buffer是BufferedSink和BufferedSource的实现类,因此它既可以用来读数据,也可以用来写数据,其内部使用了一个Segment和SegmentPool,维持着一个链表,其循环利用的机制和Android中Message的利用机制是一模一样的。


final class SegmentPool {

    static final long MAX_SIZE = 65536L;

    static Segment next;

    static long byteCount;

    private SegmentPool() {

    }

    static Segment take() {

        Class var0 = SegmentPool.class;

        synchronized(SegmentPool.class) {

            if(next != null) {

                Segment result = next;

                next = result.next;

                result.next = null;

                byteCount -= 2048L;

                return result;

            }

        }

        return new Segment();

    }

    static void recycle(Segment segment) {

        if(segment.next == null && segment.prev == null) {

            if(!segment.shared) {

                Class var1 = SegmentPool.class;

                synchronized(SegmentPool.class) {

                    if(byteCount + 2048L <= 65536L) {

                        byteCount += 2048L;

                        segment.next = next;

                        segment.pos = segment.limit = 0;

                        next = segment;

                    }

                }

            }

        } else {

            throw new IllegalArgumentException();

        }

    }

}

内部一个成员变量next指向链表下一个元素,take方法首先判断池中是否存在可用的,存在则返回,不存在则new一个,而recycle则是将不再使用的Segment重新扔到池中去。从而达到一个Segment池的作用。

而Okio暴露给外部使用的类便是Okio这个类,其内部有大量的静态方法,包括通过一个Source获得BufferedSource,通过一个Sink获得一个BufferedSink。这个过程很简单,我们调用Okio的buffer方法即可返回我们需要的,如下


Okio.buffer(sink)

Okio.buffer(source)

但是上面两个方法需要传递一个Sink或者Source,那么这个Sink和Source又是如何获得的呢。其实方法也在Okio这个类中。我们可以调用sink方法获得一个Sink,调用source方法获得一个Source,而数据的来源或者目的可以是一个File,一个输入或者输出流,一个Socket链接等等。如下


Okio.sink(new File("***"));

Okio.sink(new FileOutputStream(new File("***")));

Okio.sink(new Socket("***",8888));

Okio.source(new File("***"));

Okio.source(new FileInputStream(new File("***")));

Okio.source(new Socket("****",8888));

这样你可能还不过瘾,那么让我们连起来应用一下,现在我们从本地读一个文件,读完后再往另一个文件中写入内容。


public static void main(String[] args) {

    Source source = null;

    BufferedSource bufferedSource = null;

    try {

        File file = new File("resources/test.txt");

        source = Okio.source(file);

        bufferedSource = Okio.buffer(source);

        String content = bufferedSource.readUtf8();

        System.out.println(content);

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    } finally {

        closeQuietly(bufferedSource);

    }

    Sink sink = null;

    BufferedSink bufferedSink = null;

    try {

        File dest = new File("resources/dest.txt");

        sink = Okio.sink(dest);

        bufferedSink = Okio.buffer(sink);

        bufferedSink.writeUtf8("11111111111");

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    } finally {

        closeQuietly(bufferedSink);

    }

}

public static void closeQuietly(Closeable closeable) {

    if (closeable != null) {

        try {

            closeable.close();

        } catch (RuntimeException rethrown) {

            throw rethrown;

        } catch (Exception ignored) {

        }

    }

}

或许有时候网络请求中,我们需要使用到Gzip的功能,那么,我们可以简单的使用一下gzip的功能



public static void main(String[] args) {

    Sink sink = null;

    BufferedSink bufferedSink = null;

    GzipSink gzipSink=null;

    try {

        File dest = new File("resources/gzip.txt");

        sink = Okio.sink(dest);

        gzipSink=new GzipSink(sink);

        bufferedSink = Okio.buffer(gzipSink);

        bufferedSink.writeUtf8("android vs ios");

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    } finally {

        closeQuietly(bufferedSink);

    }

    Source source = null;

    BufferedSource bufferedSource = null;

    GzipSource gzipSource=null;

    try {

        File file = new File("resources/gzip.txt");

        source = Okio.source(file);

        gzipSource=new GzipSource(source);

        bufferedSource = Okio.buffer(gzipSource);

        String content = bufferedSource.readUtf8();

        System.out.println(content);

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    } finally {

        closeQuietly(bufferedSource);

    }

}

public static void closeQuietly(Closeable closeable) {

    if (closeable != null) {

        try {

            closeable.close();

        } catch (RuntimeException rethrown) {

            throw rethrown;

        } catch (Exception ignored) {

        }

    }

}

验证是否正确的方法便是查看该写入的文件是否是乱码,以及读出来是否是原来的字符串。

对比一下原来的gzip压缩与解压缩的方式,你就会发现还是简单了不少的


public class GzipUtil {

    /**

     * GZIP压缩

     *

     * @param data

     * @return

     */

    public static byte[] gzip(byte[] data) throws Exception {

        if (data == null || data.length == 0) {

            return null;

        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        GZIPOutputStream zos;

        BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));

        byte[] buf = new byte[512];

        int len;

        try {

            zos = new GZIPOutputStream(out);

            while ((len = bis.read(buf)) != -1) {

                zos.write(buf, 0, len);

                zos.flush();

            }

            bis.close();

            zos.close();

            return out.toByteArray();

        } finally {

            if (out != null) {

                try {

                    out.close();

                } catch (Exception e2) {

                }

            }

        }

    }

    /**

     * Gzip解压缩

     * @param b

     * @return

     */

    public static byte[] unGzip(byte[] b) {

        if (b == null || b.length == 0) {

            return null;

        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        ByteArrayInputStream in = new ByteArrayInputStream(b);

        try {

            GZIPInputStream gunzip = new GZIPInputStream(in);

            byte[] buffer = new byte[256];

            int n;

            while ((n = gunzip.read(buffer)) >= 0) {

                out.write(buffer, 0, n);

            }

            return out.toByteArray();

        } catch (IOException e) {

            Log.e(WDCore.getInstance().getConfiguration().getLogTag(), "uncompress error", e);

        } finally {

            try {

                if (out != null) {

                    out.close();

                }

                if (in != null) {

                    in.close();

                }

            } catch (Exception e2) {

            }

        }

        return null;

    }

}

此外还有一个ByteString类,这个类可以用来做各种变化,它将byte转会为String,而这个String可以是utf8的值,也可以是base64后的值,也可以是md5的值,也可以是sha256的值,总之就是各种变化,最后取得你想要的值。

总之,在合适的地方适当使用一下Okio这个库,一定能给你开发带来诸多便利,何乐而不为呢!

时间: 2024-08-23 15:18:04

Android 善用Okio简化处理I/O操作的相关文章

【Android】内嵌数据库IDE(可视化操作类)

Android开发的朋友应该对数据库内容的管理深有体会,想看一下放入数据库的内容都不是很方便,要么用root的设备导出来看或用第三方的手机版的ide.但是都要求root之后.最近一直在想android方便快捷的方法,今天刚好弄到了数据库这块.就写了一个Activity专门用来看数据库的,功能就是看对应数据库的表及表中的数据库. 效果图 刚写还没来得及美化,后面在使用过程中再时行完善. DBIDEActivity.java import java.util.ArrayList; import ja

Android:日常学习笔记(10)———使用LitePal操作数据库

Android:日常学习笔记(10)---使用LitePal操作数据库 引入LitePal 什么是LitePal LitePal是一款开源的Android数据库框架,采用了对象关系映射(ORM)的模式,将平时开发时最常用的一些数据库功能进行了封装,使得开发者不用编写一行SQL语句就可以完成各种建表.増删改查的操作.并且LitePal很"轻",jar包大小不到100k,而且近乎零配置,这一点和Hibernate这类的框架有很大区别.目前LitePal的源码已经托管到了GitHub上. 关

Android Afinal框架学习(一) FinalDb 数据库操作

框架地址:https://github.com/yangfuhai/afinal 对应源码: net.tsz.afinal.annotation.sqlite.* net.tsz.afinal.db.sqlite.* net.tsz.afinal.db.table.* net.tsz.afinal.utils.ClassUtils.net.tsz.afinal.utils.FieldUtils FinalDb 建库 FinalDb db = FinalDb.create(context, "my

Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤

原文地址: Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤 - 网络资源是无限的 - 博客频道 - CSDN.NET http://blog.csdn.net/fengbingchun/article/details/11192189   1.  先按照http://blog.csdn.net/fengbingchun/article/details/10439281中操作搭建好基本的Android开发环境: 2.  打开Eclipse,-->Window-->

Android学习--------实现增删改查数据库操作以及实现类似微信好友对话管理操作

最近的一个实验用到东西挺多,特地总结一下. 要求功能: 1.第一个页面添加歌曲名和歌手,跳到第二个页面列表显示所有记录,使用sqlite数据库 2.对于第二个页面显示的信息可以进行删除和修改操作,并自动刷新 最后我做的效果: 长按列表项弹出单个管理的菜单,像微信好友对话管理一样. 删除时可以直接删除这一条并在列表中直接显示,更新时弹出对话框更新内容提交后在列表中重新显示. 做此例遇到的困难: 1.菜单获取上下文 2.获取对话框中的内容 3.对话框按钮添加监听事件-----注意包不要导错:impo

Android学习笔记进阶14之像素操作

在我们玩的游戏中我们会经常见到一些图像的特效,比如半透明等效果.要实现这种半透明效果其实并不难,需要我们懂得图像像素的操作. 不要怕,其实在Android中Bitmap为我们提供了操作像素的基本方法. 我们可以通过getPixels()方法获得该图像的像素并放到一个数组中去,我们操作这个数组就可以了.最后通过setPixels()方法设置这个数组到Bitmap中. 在Android中,每一个图像像素通过一个4字节整数来展现:最高位字节用作Alpha通道,即用来实现透明与不透明控制,·255代表完

Android官方入门文档[8]重叠操作栏

Android官方入门文档[8]重叠操作栏 Overlaying the Action Bar重叠操作栏 This lesson teaches you to1.Enable Overlay Mode 1.For Android 3.0 and higher only 2.For Android 2.1 and higher 2.Specify Layout Top-margin You should also read?Styles and Themes 这节课教你1.启用重叠模式 1.对An

Android官方入门文档[5]建立操作栏

Android官方入门文档[5]建立操作栏 Setting Up the Action Bar建立操作栏 This lesson teaches you to1.Support Android 3.0 and Above Only2.Support Android 2.1 and Above You should also read?Setting Up the Support Library 这节课教你1.仅支持Android3.0及以上2.支持Android2.1及以上 你也应该阅读?设置支

Android:创建可穿戴应用 - 语音操作

添加语音处理能力(Adding Voice Capabilities) 语音操作是可穿戴用户体验的重要部分,可以让用户以快捷.免提的方式执行动作. Wear提供两种类型的语音操作: 系统提供(System-provided)这些语音操作是基于任务的,且内置于Wear平台.语音命令到达时,在你想启动的活动(Activity)中进行动作过滤.比如"记一下"(Take a note)或"提醒一下"(Set an alarm).应用提供(App-provided)这些语音操