Merge branch 'master' of https://github.com/gedoor/legado
commit
18c649c2ab
@ -0,0 +1,52 @@ |
|||||||
|
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,//成功 |
||||||
|
errorBlock: suspend CoroutineScope.(Throwable) -> Unit,//失败 |
||||||
|
finallyBlock: suspend CoroutineScope.() -> Unit//结束 |
||||||
|
) { |
||||||
|
launchOnUI { |
||||||
|
tryCatch(tryBlock, errorBlock, finallyBlock) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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, |
||||||
|
errorBlock: suspend CoroutineScope.(Throwable) -> Unit, |
||||||
|
finallyBlock: suspend CoroutineScope.() -> Unit |
||||||
|
) { |
||||||
|
try { |
||||||
|
coroutineScope { tryBlock() } |
||||||
|
} catch (e: Throwable) { |
||||||
|
coroutineScope { errorBlock(e) } |
||||||
|
} finally { |
||||||
|
coroutineScope { finallyBlock() } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCleared() { |
||||||
|
super.onCleared() |
||||||
|
launchManager.clear() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package io.legado.app.data.api |
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred |
||||||
|
import retrofit2.http.* |
||||||
|
|
||||||
|
interface CommonHttpApi { |
||||||
|
|
||||||
|
@GET |
||||||
|
fun get(@Url url: String, @QueryMap map: Map<String, String>): Deferred<String> |
||||||
|
|
||||||
|
@FormUrlEncoded |
||||||
|
@POST |
||||||
|
fun post(@Url url: String, @FieldMap map: Map<String, String>): Deferred<String> |
||||||
|
} |
@ -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 CoroutinesCallAdapterFactory private constructor() : CallAdapter.Factory() { |
||||||
|
companion object { |
||||||
|
@JvmStatic @JvmName("create") |
||||||
|
operator fun invoke() = CoroutinesCallAdapterFactory() |
||||||
|
} |
||||||
|
|
||||||
|
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,64 @@ |
|||||||
|
package io.legado.app.help.http |
||||||
|
|
||||||
|
import okhttp3.* |
||||||
|
import retrofit2.Retrofit |
||||||
|
import java.util.* |
||||||
|
import java.util.concurrent.TimeUnit |
||||||
|
|
||||||
|
object HttpHelper { |
||||||
|
|
||||||
|
val client: OkHttpClient = getOkHttpClient() |
||||||
|
|
||||||
|
|
||||||
|
fun <T> getApiService(baseUrl: String, clazz: Class<T>): T { |
||||||
|
return getRetrofit(baseUrl).create(clazz) |
||||||
|
} |
||||||
|
|
||||||
|
fun getRetrofit(baseUrl: String): Retrofit { |
||||||
|
return Retrofit.Builder().baseUrl(baseUrl) |
||||||
|
//增加返回值为字符串的支持(以实体类返回) |
||||||
|
// .addConverterFactory(EncodeConverter.create()) |
||||||
|
//增加返回值为Observable<T>的支持 |
||||||
|
.addCallAdapterFactory(CoroutinesCallAdapterFactory.invoke()) |
||||||
|
.client(client) |
||||||
|
.build() |
||||||
|
} |
||||||
|
|
||||||
|
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,39 @@ |
|||||||
package io.legado.app.ui.search |
package io.legado.app.ui.search |
||||||
|
|
||||||
import android.app.Application |
import android.app.Application |
||||||
import androidx.lifecycle.AndroidViewModel |
import android.util.Log |
||||||
|
import androidx.lifecycle.LiveData |
||||||
|
import androidx.lifecycle.MutableLiveData |
||||||
|
import io.legado.app.base.BaseViewModel |
||||||
|
import io.legado.app.data.api.CommonHttpApi |
||||||
|
import io.legado.app.data.entities.SearchBook |
||||||
|
import io.legado.app.help.http.HttpHelper |
||||||
|
import kotlinx.coroutines.Dispatchers.IO |
||||||
|
import kotlinx.coroutines.withContext |
||||||
|
|
||||||
class SearchViewModel(application: Application) : AndroidViewModel(application) |
class SearchViewModel(application: Application) : BaseViewModel(application) { |
||||||
|
|
||||||
|
val searchBooks: LiveData<List<SearchBook>> = MutableLiveData() |
||||||
|
|
||||||
|
public fun search(start: () -> Unit, finally: () -> Unit) { |
||||||
|
launchOnUI( |
||||||
|
{ |
||||||
|
start() |
||||||
|
val searchResponse = withContext(IO) { |
||||||
|
HttpHelper.getApiService( |
||||||
|
"http:www.baidu.com", |
||||||
|
CommonHttpApi::class.java |
||||||
|
).get("", mutableMapOf()) |
||||||
|
} |
||||||
|
|
||||||
|
val result = searchResponse.await() |
||||||
|
}, |
||||||
|
{ Log.i("TAG", "${it.message}") }, |
||||||
|
{ finally() }) |
||||||
|
|
||||||
|
// GlobalScope.launch { |
||||||
|
// |
||||||
|
// } |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
@ -0,0 +1,12 @@ |
|||||||
|
package io.legado.app.utils |
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import androidx.lifecycle.ViewModel |
||||||
|
import androidx.lifecycle.ViewModelProviders |
||||||
|
|
||||||
|
fun <T : ViewModel> AppCompatActivity.getViewModel(clazz: Class<T>) = ViewModelProviders.of(this).get(clazz) |
||||||
|
|
||||||
|
fun <T : ViewModel> Fragment.getViewModel(clazz: Class<T>) = ViewModelProviders.of(this).get(clazz) |
||||||
|
|
||||||
|
fun <T : ViewModel> Fragment.getViewModelOfActivity(clazz: Class<T>) = ViewModelProviders.of(requireActivity()).get(clazz) |
Loading…
Reference in new issue