隐藏

Android Studio 开发项目中Okio的使用简介

发布:2023/2/24 16:01:25作者:管理员 来源:本站 浏览次数:795

简介


Okio 是由square公司开发的用于IO读取。补充了Java.io和java.nio的不足,以便能够更加方便,快速的访问、存储和处理数据。内部的读写操作是在内存中进行的。是OkHttp的底层IO库。


.


Okio的核心类


       ByteStrings: 是不可变的字节序列。它会自动将自己编码和解码为十六进制、base64和utf-8。


       Buffers: 是一个可变的字节序列。像Arraylist一样,你不需要预先设置缓冲区的大小。你可以将缓冲区读写为一个队列:将数据写到队尾,然后从队头读取。


       Source: 类似于java的Inputstream(输入流),但也有所区别。


       Sink: 类似于java的Outputstream(输入流),但也有所区别。


.


Okio的流与Java的区别 :


       超时(Timeouts): 流提供了对底层I/O超时机制的访问。与java.io的socket字流不同,read()和write()方法都给予超时机制。


       易于实施: source 只声明了三个方法:read()、close()和timeout()。没有像available()或单字节读取这样会导致正确性和性能意外的危险操作。


       使用方便: 虽然source和sink的实现只有三种方法可写,但是调用方可以实现Bufferedsource和Bufferedsink接口, 这两个接口提供了丰富API能够满足你所需的一切。


       字节流和字符流之间没有人为的区别: 都是数据。你可以以字节、UTF-8字符串、big-endian的32位整数、little-endian的短整数等任何你想要的形式进行读写;不再有InputStreamReader!。


       易于测试: Buffer类同时实现了BufferedSource和BufferedSink接口,因此测试代码简单明了。


.


.

添加的Okio的依赖


在项目的 build.gradle 中添加下面的依赖:


dependencies {

implementation("com.squareup.okio:okio:2.10.0")

}


 

Okio的使用


.


1. 将数据写到文件中


@Throws(IOException::class)

fun writeFile(file: File) {


 file.sink().buffer().use { sink ->

 

//循环的向文件中写入数据

   for ((key, value) in System.getenv()) {


 //数据格式:`key = value + 换行`

     sink.writeUtf8(key)

     sink.writeUtf8("=")

     sink.writeUtf8(value)

     sink.writeUtf8(system.lineeseseparator())


 //等价于(但前者性能更优)

 //因为VM不必创建和回收临时字符串

 //sink.writeUtf8(key + "=" + value + "\n");

   }


sink.close()

 }

}


//在main()中调用方法来向文件中写入数据

fun main() {


var file:File = File("文件路径")


if(!file.exists()){

try {

        file.createNewFile();

       } catch (IOException e) {

           e.printStackTrace();

       }

}


//向文件中写入数据

writeFile(file)

}


 


   注:

   在进行数据的写入的时候 sink.writeUtf8()方法只会在一行上输入不会自动换行,所以需要我们手动插入换行符;在大多数的程序都是所以"\n"来作为换行符的。在极少数情况下,你可以使用system.lineeseseparator()来代替"\n";它在Windows上返回"\r\n",在其他操作系统上返回"\n"。


.


2. 逐行读取文件


@Throws(IOException::class)

fun readLinesFile(file: File) {

 file.source().use { fileSource ->

   fileSource.buffer().use { bufferedFileSource ->

     while (true) {

//读取文件的内容

       val line = bufferedFileSource.readUtf8Line() ?: break

 

//将文件的内容打印到控制台

println(line)


     }


bufferedFileSource.close()

   }

 }

}



//在main()中调用方法来读取指定的文件

fun main() {


var file:File = File("文件路径")


if(!file.exists()){

return;

}


//读取已经

readLinesFile(file)

}


 


   注:

   使用 readUtf8Line()方法来读取文件的所有数据,在读取数据的时候,直到读取下一个数据为\n,\r\n或文件的结尾前。它都以字符串的形式返回读取到的数据,并在最后省略定界符。当遇到空行时,该方法将返回一个空字符串。如果文件读取完成,将返回null。


.


3. 把二进制数据写入文件中


下面的示例代码是按照 BMP文件格式 对文件进行编码:


 //提供给外部调用的写入方法

 @Throws(IOException::class)

 fun writeBitmap(bitmap: Bitmap, file: File) {

   file.sink().buffer().use { sink -> encode(bitmap, sink) }

 }



@Throws(IOException::class)

 fun encode(bitmap: Bitmap, sink: BufferedSink) {

   val height = bitmap.height

   val width = bitmap.width

   val bytesPerPixel = 3

   val rowByteCountWithoutPadding = bytesPerPixel * width

   val rowByteCount = (rowByteCountWithoutPadding + 3) / 4 * 4

   val pixelDataSize = rowByteCount * height

   val bmpHeaderSize = 14

   val dibHeaderSize = 40


   // BMP Header

   sink.writeUtf8("BM") // ID.

   sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize) // File size.

   sink.writeShortLe(0) // Unused.

   sink.writeShortLe(0) // Unused.

   sink.writeIntLe(bmpHeaderSize + dibHeaderSize) // Offset of pixel data.


   // DIB Header

   sink.writeIntLe(dibHeaderSize)

   sink.writeIntLe(width)

   sink.writeIntLe(height)

   sink.writeShortLe(1) // Color plane count.

   sink.writeShortLe(bytesPerPixel * Byte.SIZE_BITS)

   sink.writeIntLe(0) // No compression.

   sink.writeIntLe(16) // Size of bitmap data including padding.

   sink.writeIntLe(2835) // Horizontal print resolution in pixels/meter. (72 dpi).

   sink.writeIntLe(2835) // Vertical print resolution in pixels/meter. (72 dpi).

   sink.writeIntLe(0) // Palette color count.

   sink.writeIntLe(0) // 0 important colors.


   // Pixel data.

   for (y in height - 1 downTo 0) {

     for (x in 0 until width) {

       sink.writeByte(bitmap.blue(x, y))

       sink.writeByte(bitmap.green(x, y))

       sink.writeByte(bitmap.red(x, y))

     }


     // Padding for 4-byte alignment.

     for (p in rowByteCountWithoutPadding until rowByteCount) {

       sink.writeByte(0)

     }

}


fun main() {


 val bitmap = Bitmap数据


 var file:File = File("文件路径")


if(!file.exists()){

try {

        file.createNewFile();

       } catch (IOException e) {

           e.printStackTrace();

       }

}


 writeBitmap(bitmap, file)

}


  


   说明:


   代码中对文件按照BMP的格式写入二进制数据,这会生成一个bmp格式的图片文件,BMP格式要求每行以4字节开始,所以代码中加了很多0来做字节对齐。


.


.

使用Okio进行Socket通信


下面代码是基于Okio来实现的本地Socket客户端,并与服务器的Socket进行通信。


//Socket服务的代理类

class SocksProxyServer {


 //创建线程池

 private val executor = Executors.newCachedThreadPool()

 //创建Socket服务

 private lateinit var serverSocket: ServerSocket

 //创建Set集合

 private val openSockets: MutableSet<Socket> = Collections.newSetFromMap(ConcurrentHashMap())


 //启动Socket服务

 @Throws(IOException::class)

 fun start() {

   serverSocket = ServerSocket(0)

   executor.execute {

acceptSockets()

}

 }


 //关闭Socket服务

 @Throws(IOException::class)

 fun shutdown() {

   serverSocket.close()

   executor.shutdown()

 }


 

 fun proxy(): Proxy = Proxy(

   Proxy.Type.SOCKS,

   InetSocketAddress.createUnresolved("localhost", serverSocket.localPort)

 )


 //连接Socket远程服务

 private fun acceptSockets() {

   try {

     while (true) {

       val from = serverSocket.accept()

       openSockets.add(from)

       executor.execute { handleSocket(from) }

     }

   } catch (e: IOException) {

     println("shutting down: $e")

   } finally {

     for (socket in openSockets) {

       socket.close()

     }

   }

 }



 //连接Socket服务

 private fun handleSocket(fromSocket: Socket) {

   try {

     val fromSource = fromSocket.source().buffer()

     val fromSink = fromSocket.sink().buffer()


     // Read the hello.

     val socksVersion = fromSource.readByte().toInt() and 0xff

     if (socksVersion != VERSION_5) throw ProtocolException()

     val methodCount = fromSource.readByte().toInt() and 0xff

     var foundSupportedMethod = false

     for (i in 0 until methodCount) {

       val method: Int = fromSource.readByte().toInt() and 0xff

       foundSupportedMethod = foundSupportedMethod or (method == METHOD_NO_AUTHENTICATION_REQUIRED)

     }

     if (!foundSupportedMethod) throw ProtocolException()


     // Respond to hello.

     fromSink.writeByte(VERSION_5)

       .writeByte(METHOD_NO_AUTHENTICATION_REQUIRED)

       .emit()


     // Read a command.

     val version = fromSource.readByte().toInt() and 0xff

     val command = fromSource.readByte().toInt() and 0xff

     val reserved = fromSource.readByte().toInt() and 0xff

     if (version != VERSION_5 || command != COMMAND_CONNECT || reserved != 0) {

       throw ProtocolException()

     }


     // Read an address.

     val addressType = fromSource.readByte().toInt() and 0xff

     val inetAddress = when (addressType) {

       ADDRESS_TYPE_IPV4 -> InetAddress.getByAddress(fromSource.readByteArray(4L))

       ADDRESS_TYPE_DOMAIN_NAME -> {

         val domainNameLength: Int = fromSource.readByte().toInt() and 0xff

         InetAddress.getByName(fromSource.readUtf8(domainNameLength.toLong()))

       }

       else -> throw ProtocolException()

     }

     val port = fromSource.readShort().toInt() and 0xffff


     // Connect to the caller's specified host.

     val toSocket = Socket(inetAddress, port)

     openSockets.add(toSocket)

     val localAddress = toSocket.localAddress.address

     if (localAddress.size != 4) throw ProtocolException()


     // Write the reply.

     fromSink.writeByte(VERSION_5)

       .writeByte(REPLY_SUCCEEDED)

       .writeByte(0)

       .writeByte(ADDRESS_TYPE_IPV4)

       .write(localAddress)

       .writeShort(toSocket.localPort)

       .emit()


     // Connect sources to sinks in both directions.

     val toSink = toSocket.sink()

     executor.execute { transfer(fromSocket, fromSource, toSink) }

     val toSource = toSocket.source()

     executor.execute { transfer(toSocket, toSource, fromSink) }

   } catch (e: IOException) {

     fromSocket.close()

     openSockets.remove(fromSocket)

     println("connect failed for $fromSocket: $e")

   }

 }


 /**

  *从source读取数据并将其写入sink

  */

 private fun transfer(sourceSocket: Socket, source: Source, sink: Sink) {

   try {

     val buffer = Buffer()

     var byteCount: Long

     while (source.read(buffer, 8192L).also { byteCount = it } != -1L) {

       sink.write(buffer, byteCount)

       sink.flush()

     }

   } catch (e: IOException) {

     println("transfer failed from $sourceSocket: $e")

   } finally {

     sink.close()

     source.close()

     sourceSocket.close()

     openSockets.remove(sourceSocket)

   }

 }

}



fun main() {


//定义Socket代理对象并初始化

 val proxyServer = SocksProxyServer()


 //启动Socket服务

 proxyServer.start()


 //连接Socket远程服务器

 val url = URL("https://publicobject.com/helloworld.txt")

 val connection = url.openConnection(proxyServer.proxy())


 //接收Socket远程服务器返回的数据

 connection.getInputStream().source().buffer().use { source ->

     while (true) {

//读取文件的内容

       val line = source.readUtf8Line() ?: break

 

//将文件的内容打印到控制台

println(line)


     }


source.close()

 }


 //关闭Socket服务

 proxyServer.shutdown()

}


 


   说明:


   通过上述代码就可以实现与远程服务器的Socket进行通信,完成客户端与服务器的长链接通信。


.


.

Hashing(哈希散列)


Okio 可以对字节字符串,Buffer,source输入流,sink输出流等进行哈希加密。


   下面介绍Okio支持加密哈希函数:


       MD5: 128位(16字节)加密哈希。它既不安全又是过时的,因为它的逆向成本很低!之所以提供此哈希,是因为它在安全性较低的系统中使用比较非常流行并且方便。


       SHA-1: 160位(20字节)加密散列。最近的研究表明,创建SHA-1碰撞是可行的。考虑从sha-1升级到sha-256。


       SHA-256: 256位(32字节)加密哈希。SHA-256被广泛理解,逆向操作成本较高。这是大多数系统应该使用的哈希。


       SHA-512: 512位(64字节)加密哈希。逆向操作成本很高。


1. 对字节字符串进行加密


println("ByteString")

val byteString = readByteString(File("文件路径"))

println("       md5: " + byteString.md5().hex())

println("      sha1: " + byteString.sha1().hex())

println("    sha256: " + byteString.sha256().hex())

println("    sha512: " + byteString.sha512().hex())



 @Throws(IOException::class)

 fun readByteString(file: File): ByteString {

   return file.source().buffer().use { it.readByteString() }

 }


  


2. 对Buffer进行加密


println("Buffer")

val buffer = readBuffer(File("文件路径"))

println("       md5: " + buffer.md5().hex())

println("      sha1: " + buffer.sha1().hex())

println("    sha256: " + buffer.sha256().hex())

println("    sha512: " + buffer.sha512().hex())



 @Throws(IOException::class)

 fun readBuffer(file: File): Buffer {

   return file.source().use { source ->

     Buffer().also { it.writeAll(source) }

   }

 }


 


3. 对source输入流进行加密


println("HashingSource")

sha256(File("文件路径").source()).use { hashingSource ->

 hashingSource.buffer().use { source ->

   source.readAll(blackholeSink())

   println("    sha256: " + hashingSource.hash.hex())

 }

}


 


4. 对sink输出流进行加密


println("HashingSink")

sha256(blackholeSink()).use { hashingSink ->

 hashingSink.buffer().use { sink ->

   File("文件路径").source().use { source ->

     sink.writeAll(source)

     sink.close() // Emit anything buffered.

     println("    sha256: " + hashingSink.hash.hex())

   }

 }

}


  


5. 支持秘钥和hash值加密


println("HMAC")

val byteString = readByteString(File("文件路径"))

val secret = "7065616e7574627574746572".decodeHex()秘钥

println("hmacSha256: " + byteString.hmacSha256(secret).hex())


 


   补充:


   你可以从ByteString, Buffer, HashingSource, 和HashingSink中生成HMAC,但Okio没有为MD5实现HMAC。


.


.

加密和解密


   Okio可以通过使用使用Okio.cipherSink(Sink,Cipher)或Okio.cipherSource(Source,Cipher)让区块加密算法对Stream进行加密或解密。


   以下示例显示了AES加密的典型用法,其中key和iv参数都应为16个字节长度:


对Sink输入流进行加密


fun encryptAes(bytes: ByteString, file: File, key: ByteArray, iv: ByteArray) {

 val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

 cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))


 //解密

 val cipherSink = file.sink().cipherSink(cipher)

 cipherSink.buffer().use {

   it.write(bytes)

 }

}


 


对Source输入流进行解密


fun decryptAesToByteString(file: File, key: ByteArray, iv: ByteArray): ByteString {

 val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

 cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))


 //解密

 val cipherSource = file.source().cipherSource(cipher)

 return cipherSource.buffer().use {

   it.readByteString()

 }

}