添加okhttp, retrofit配合协程使用

pull/31/head
Invinciblelee 6 years ago
parent 654d510efd
commit b8bc0e8b59
  1. 13
      app/build.gradle
  2. 58
      app/src/main/java/io/legado/app/base/BaseViewModel.kt
  3. 35
      app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
  4. 107
      app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt
  5. 49
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  6. 175
      app/src/main/java/io/legado/app/help/http/SSLHelper.kt
  7. 16
      app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt
  8. 1
      app/src/main/res/layout/activity_search.xml

@ -41,6 +41,12 @@ kapt {
}
}
kotlin{
experimental{
coroutines "enable"
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@ -68,12 +74,17 @@ dependencies {
implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
//
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
//
implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.jayway.jsonpath:json-path:2.4.0'
implementation 'org.jsoup:jsoup:1.12.1'
//Retrofit
implementation 'com.squareup.okhttp3:logging-interceptor:3.14.0'//
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

@ -0,0 +1,58 @@
package io.legado.app.base
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
open class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private val launchManager: MutableList<Job> = mutableListOf()
protected fun launchOnUI(
tryBlock: suspend CoroutineScope.() -> Unit,
cacheBlock: suspend CoroutineScope.(Throwable) -> Unit,
finallyBlock: suspend CoroutineScope.() -> Unit,
handleCancellationExceptionManually: Boolean
) {
launchOnUI {
tryCatch(tryBlock, cacheBlock, finallyBlock, handleCancellationExceptionManually)
}
}
/**
* add launch task to [launchManager]
*/
private fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
val job = launch { block() }
launchManager.add(job)
job.invokeOnCompletion { launchManager.remove(job) }
}
private suspend fun tryCatch(
tryBlock: suspend CoroutineScope.() -> Unit,
catchBlock: suspend CoroutineScope.(Throwable) -> Unit,
finallyBlock: suspend CoroutineScope.() -> Unit,
handleCancellationExceptionManually: Boolean = false
) {
try {
coroutineScope { tryBlock() }
} catch (e: Throwable) {
if (e !is CancellationException || handleCancellationExceptionManually) {
coroutineScope { catchBlock(e) }
} else {
throw e
}
} finally {
coroutineScope { finallyBlock() }
}
}
override fun onCleared() {
super.onCleared()
launchManager.clear()
}
}

@ -257,7 +257,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return footerItems?.size() ?: 0
}
fun getItem(position: Int): ITEM = items[position % items.size]
fun getItem(position: Int): ITEM? = if (position in 0 until items.size) items[position] else null
fun getItems(): List<ITEM> = items
@ -280,7 +280,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return when {
isHeader(position) -> TYPE_HEADER_VIEW + position
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()
else -> getItemViewType(getItem(getRealPosition(position)), getRealPosition(position))
else -> getItem(getActualPosition(position))?.let { getItemViewType(it, getActualPosition(position)) } ?: 0
}
}
@ -299,13 +299,17 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
itemClickListener!!.invoke(holder, getItem(holder.layoutPosition))
getItem(holder.layoutPosition)?.let {
itemClickListener?.invoke(holder, it)
}
}
}
if (itemLongClickListener != null) {
holder.itemView.setOnLongClickListener {
itemLongClickListener!!.invoke(holder, getItem(holder.layoutPosition))
getItem(holder.layoutPosition)?.let {
itemLongClickListener?.invoke(holder, it) ?: true
} ?: true
}
}
@ -315,14 +319,15 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
onBindViewHolder(holder, position, mutableListOf())
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
itemDelegates.getValue(getItemViewType(holder.layoutPosition))
.convert(holder, getItem(holder.layoutPosition), payloads)
getItem(holder.layoutPosition)?.let {
itemDelegates.getValue(getItemViewType(holder.layoutPosition))
.convert(holder, it, payloads)
}
}
}
@ -339,15 +344,17 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
if (manager is GridLayoutManager) {
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize(
getItem(position), getItemViewType(position), position
)
return getItem(position)?.let {
if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize(
it, getItemViewType(position), position
)
} ?: manager.spanCount
}
}
}
}
fun setAnimationConfig(item: ItemAnimation) {
fun setItemAnimation(item: ItemAnimation) {
itemAnimation = item
}
@ -359,7 +366,7 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : Rec
return position >= getActualItemCount() + getHeaderCount()
}
private fun getRealPosition(position: Int): Int {
private fun getActualPosition(position: Int): Int {
return position - getHeaderCount()
}

@ -0,0 +1,107 @@
package io.legado.app.help.http
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import retrofit2.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class CoroutineCallAdapterFactory private constructor() : CallAdapter.Factory() {
companion object {
@JvmStatic @JvmName("create")
operator fun invoke() = CoroutineCallAdapterFactory()
}
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (Deferred::class.java != getRawType(returnType)) {
return null
}
if (returnType !is ParameterizedType) {
throw IllegalStateException(
"Deferred return type must be parameterized as Deferred<Foo> or Deferred<out Foo>")
}
val responseType = getParameterUpperBound(0, returnType)
val rawDeferredType = getRawType(responseType)
return if (rawDeferredType == Response::class.java) {
if (responseType !is ParameterizedType) {
throw IllegalStateException(
"Response must be parameterized as Response<Foo> or Response<out Foo>")
}
ResponseCallAdapter<Any>(
getParameterUpperBound(
0,
responseType
)
)
} else {
BodyCallAdapter<Any>(responseType)
}
}
private class BodyCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<T>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<T> {
val deferred = CompletableDeferred<T>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
}
private class ResponseCallAdapter<T>(
private val responseType: Type
) : CallAdapter<T, Deferred<Response<T>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Response<T>> {
val deferred = CompletableDeferred<Response<T>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(response)
}
})
return deferred
}
}
}

@ -0,0 +1,49 @@
package io.legado.app.help.http
import okhttp3.*
import java.util.*
import java.util.concurrent.TimeUnit
object HttpHelper {
val client: OkHttpClient = getOkHttpClient()
private fun getOkHttpClient(): OkHttpClient {
val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs = ArrayList<ConnectionSpec>()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
val sslParams = SSLHelper.getSslSocketFactory()
return OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier)
.connectionSpecs(specs)
.followRedirects(true)
.followSslRedirects(true)
.protocols(listOf(Protocol.HTTP_1_1))
.addInterceptor(getHeaderInterceptor())
.build()
}
private fun getHeaderInterceptor(): Interceptor {
return Interceptor { chain ->
val request = chain.request()
.newBuilder()
.addHeader("Keep-Alive", "300")
.addHeader("Connection", "Keep-Alive")
.addHeader("Cache-Control", "no-cache")
.build()
chain.proceed(request)
}
}
}

@ -0,0 +1,175 @@
package io.legado.app.help.http
import javax.net.ssl.*
import java.io.IOException
import java.io.InputStream
import java.security.KeyManagementException
import java.security.KeyStore
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object SSLHelper {
val sslSocketFactory: SSLParams
get() = getSslSocketFactoryBase(null, null, null)
/**
* 为了解决客户端不信任服务器数字证书的问题网络上大部分的解决方案都是让客户端不对证书做任何检查
* 这是一种有很大安全漏洞的办法
*/
val unsafeTrustManager: X509TrustManager = object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}
/**
* 此类是用于主机名验证的基接口 在握手期间如果 URL 的主机名和服务器的标识主机名不匹配
* 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接策略可以是基于证书的或依赖于其他验证方案
* 当验证 URL 主机名使用的默认规则失败时使用这些回调如果主机名是可接受的则返回 true
*/
val unsafeHostnameVerifier: HostnameVerifier = HostnameVerifier { _, _ -> true }
class SSLParams {
var sSLSocketFactory: SSLSocketFactory? = null
var trustManager: X509TrustManager? = null
}
/**
* https单向认证
* 可以额外配置信任服务端的证书策略否则默认是按CA证书去验证的若不是CA可信任的证书则无法通过验证
*/
fun getSslSocketFactory(trustManager: X509TrustManager): SSLParams {
return getSslSocketFactoryBase(trustManager, null, null)
}
/**
* https单向认证
* 用含有服务端公钥的证书校验服务端证书
*/
fun getSslSocketFactory(vararg certificates: InputStream): SSLParams {
return getSslSocketFactoryBase(null, null, null, *certificates)
}
/**
* https双向认证
* bksFile password -> 客户端使用bks证书校验服务端证书
* certificates -> 用含有服务端公钥的证书校验服务端证书
*/
fun getSslSocketFactory(bksFile: InputStream, password: String, vararg certificates: InputStream): SSLParams {
return getSslSocketFactoryBase(null, bksFile, password, *certificates)
}
/**
* https双向认证
* bksFile password -> 客户端使用bks证书校验服务端证书
* X509TrustManager -> 如果需要自己校验那么可以自己实现相关校验如果不需要自己校验那么传null即可
*/
fun getSslSocketFactory(bksFile: InputStream, password: String, trustManager: X509TrustManager): SSLParams {
return getSslSocketFactoryBase(trustManager, bksFile, password)
}
private fun getSslSocketFactoryBase(
trustManager: X509TrustManager?,
bksFile: InputStream?,
password: String?,
vararg certificates: InputStream
): SSLParams {
val sslParams = SSLParams()
try {
val keyManagers = prepareKeyManager(bksFile, password)
val trustManagers = prepareTrustManager(*certificates)
val manager: X509TrustManager?
manager = //优先使用用户自定义的TrustManager
trustManager ?: if (trustManagers != null) {
//然后使用默认的TrustManager
chooseTrustManager(trustManagers)
} else {
//否则使用不安全的TrustManager
unsafeTrustManager
}
// 创建TLS类型的SSLContext对象, that uses our TrustManager
val sslContext = SSLContext.getInstance("TLS")
// 用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书
// 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书
sslContext.init(keyManagers, manager?.let { arrayOf<TrustManager>(it) }, null)
// 通过sslContext获取SSLSocketFactory对象
sslParams.sSLSocketFactory = sslContext.socketFactory
sslParams.trustManager = manager
return sslParams
} catch (e: NoSuchAlgorithmException) {
throw AssertionError(e)
} catch (e: KeyManagementException) {
throw AssertionError(e)
}
}
private fun prepareKeyManager(bksFile: InputStream?, password: String?): Array<KeyManager>? {
try {
if (bksFile == null || password == null) return null
val clientKeyStore = KeyStore.getInstance("BKS")
clientKeyStore.load(bksFile, password.toCharArray())
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(clientKeyStore, password.toCharArray())
return kmf.keyManagers
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun prepareTrustManager(vararg certificates: InputStream): Array<TrustManager>? {
if (certificates.isEmpty()) return null
try {
val certificateFactory = CertificateFactory.getInstance("X.509")
// 创建一个默认类型的KeyStore,存储我们信任的证书
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null)
var index = 0
for (certStream in certificates) {
val certificateAlias = Integer.toString(index++)
// 证书工厂根据证书文件的流生成证书 cert
val cert = certificateFactory.generateCertificate(certStream)
// 将 cert 作为可信证书放入到keyStore中
keyStore.setCertificateEntry(certificateAlias, cert)
try {
certStream?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
//我们创建一个默认类型的TrustManagerFactory
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
//用我们之前的keyStore实例初始化TrustManagerFactory,这样tmf就会信任keyStore中的证书
tmf.init(keyStore)
//通过tmf获取TrustManager数组,TrustManager也会信任keyStore中的证书
return tmf.trustManagers
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun chooseTrustManager(trustManagers: Array<TrustManager>): X509TrustManager? {
for (trustManager in trustManagers) {
if (trustManager is X509TrustManager) {
return trustManager
}
}
return null
}
}

@ -1,6 +1,18 @@
package io.legado.app.ui.search
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.SearchBook
class SearchViewModel(application: Application) : AndroidViewModel(application)
class SearchViewModel(application: Application) : BaseViewModel(application){
val searchBooks: LiveData<List<SearchBook>> = MutableLiveData()
public fun search(){
}
}

@ -2,7 +2,6 @@
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".ui.search.SearchDataBinding">
<variable name="SearchViewModel" type="io.legado.app.ui.search.SearchViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout

Loading…
Cancel
Save