From f221e23641d1ce526d14f8679cd9de20a4542ffd Mon Sep 17 00:00:00 2001 From: Invinciblelee <1760316362@qq.com> Date: Fri, 24 May 2019 09:46:10 +0800 Subject: [PATCH 1/5] ViewModelExtensions --- .../main/java/io/legado/app/ui/main/MainActivity.kt | 6 +++--- .../java/io/legado/app/ui/search/SearchActivity.kt | 6 +++--- .../java/io/legado/app/utils/ViewModelExtensions.kt | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index cf4a2329d..a1a9cdd76 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -7,18 +7,18 @@ import android.view.MenuItem import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout -import androidx.lifecycle.ViewModelProvider import com.google.android.material.navigation.NavigationView import io.legado.app.R import io.legado.app.base.BaseActivity import io.legado.app.ui.search.SearchActivity +import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.app_bar_main.* -import kotlinx.android.synthetic.main.view_titlebar.* class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { override val viewModel: MainViewModel - get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(MainViewModel::class.java) + get() = getViewModel(MainViewModel::class.java) + override val layoutID: Int get() = R.layout.activity_main diff --git a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt index 14994c535..27a8450d8 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt @@ -1,15 +1,15 @@ package io.legado.app.ui.search import android.os.Bundle -import androidx.lifecycle.ViewModelProvider import io.legado.app.R import io.legado.app.base.BaseActivity -import io.legado.app.help.LayoutManager +import io.legado.app.search.SearchDataBinding +import io.legado.app.utils.getViewModel class SearchActivity : BaseActivity() { override val viewModel: SearchViewModel - get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(SearchViewModel::class.java) + get() = getViewModel(SearchViewModel::class.java) override val layoutID: Int get() = R.layout.activity_search diff --git a/app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt new file mode 100644 index 000000000..ed963ec8e --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ViewModelExtensions.kt @@ -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 AppCompatActivity.getViewModel(clazz: Class) = ViewModelProviders.of(this).get(clazz) + +fun Fragment.getViewModel(clazz: Class) = ViewModelProviders.of(this).get(clazz) + +fun Fragment.getViewModelOfActivity(clazz: Class) = ViewModelProviders.of(requireActivity()).get(clazz) From 1c82acdf982459f29b1c55e494f66b3cf62744cc Mon Sep 17 00:00:00 2001 From: Invinciblelee <1760316362@qq.com> Date: Fri, 24 May 2019 09:52:49 +0800 Subject: [PATCH 2/5] ViewModelExtensions --- .../java/io/legado/app/ui/main/MainActivity.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index a1a9cdd76..ce40a7e33 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -58,21 +58,6 @@ class MainActivity : BaseActivity(), NavigationV override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { - R.id.nav_home -> { - // Handle the camera action - } - R.id.nav_gallery -> { - - } - R.id.nav_slideshow -> { - - } - R.id.nav_tools -> { - - } - R.id.nav_share -> { - - } R.id.nav_send -> { } From 654d510efdca5e331d2079b04d0870dc8b6a0c87 Mon Sep 17 00:00:00 2001 From: Invinciblelee <1760316362@qq.com> Date: Fri, 24 May 2019 09:53:58 +0800 Subject: [PATCH 3/5] ViewModelExtensions --- app/src/main/java/io/legado/app/ui/search/SearchActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt index 27a8450d8..226c9207d 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt @@ -3,7 +3,6 @@ package io.legado.app.ui.search import android.os.Bundle import io.legado.app.R import io.legado.app.base.BaseActivity -import io.legado.app.search.SearchDataBinding import io.legado.app.utils.getViewModel class SearchActivity : BaseActivity() { From b8bc0e8b595766ada7f1561df82ae3b067b4f04f Mon Sep 17 00:00:00 2001 From: Invinciblelee <1760316362@qq.com> Date: Fri, 24 May 2019 12:06:20 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0okhttp,=20retrofit?= =?UTF-8?q?=E9=85=8D=E5=90=88=E5=8D=8F=E7=A8=8B=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 13 +- .../java/io/legado/app/base/BaseViewModel.kt | 58 ++++++ .../app/base/adapter/CommonRecyclerAdapter.kt | 35 ++-- .../help/http/CoroutineCallAdapterFactory.kt | 107 +++++++++++ .../io/legado/app/help/http/HttpHelper.kt | 49 +++++ .../java/io/legado/app/help/http/SSLHelper.kt | 175 ++++++++++++++++++ .../legado/app/ui/search/SearchViewModel.kt | 16 +- app/src/main/res/layout/activity_search.xml | 1 - 8 files changed, 436 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/io/legado/app/base/BaseViewModel.kt create mode 100644 app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt create mode 100644 app/src/main/java/io/legado/app/help/http/HttpHelper.kt create mode 100644 app/src/main/java/io/legado/app/help/http/SSLHelper.kt diff --git a/app/build.gradle b/app/build.gradle index 3a59b113a..966ba8516 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/io/legado/app/base/BaseViewModel.kt b/app/src/main/java/io/legado/app/base/BaseViewModel.kt new file mode 100644 index 000000000..009aafe7d --- /dev/null +++ b/app/src/main/java/io/legado/app/base/BaseViewModel.kt @@ -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 = 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt index c9b9f0cf2..ce7b3d4a6 100644 --- a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt +++ b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt @@ -257,7 +257,7 @@ abstract class CommonRecyclerAdapter(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 = items @@ -280,7 +280,7 @@ abstract class CommonRecyclerAdapter(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(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(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) { + final override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList) { 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(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(protected val context: Context) : Rec return position >= getActualItemCount() + getHeaderCount() } - private fun getRealPosition(position: Int): Int { + private fun getActualPosition(position: Int): Int { return position - getHeaderCount() } diff --git a/app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt b/app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt new file mode 100644 index 000000000..78e0aa7a0 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt @@ -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, + 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 or Deferred") + } + 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 or Response") + } + ResponseCallAdapter( + getParameterUpperBound( + 0, + responseType + ) + ) + } else { + BodyCallAdapter(responseType) + } + } + + private class BodyCallAdapter( + private val responseType: Type + ) : CallAdapter> { + + override fun responseType() = responseType + + override fun adapt(call: Call): Deferred { + val deferred = CompletableDeferred() + + deferred.invokeOnCompletion { + if (deferred.isCancelled) { + call.cancel() + } + } + + call.enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + deferred.completeExceptionally(t) + } + + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + deferred.complete(response.body()!!) + } else { + deferred.completeExceptionally(HttpException(response)) + } + } + }) + + return deferred + } + } + + private class ResponseCallAdapter( + private val responseType: Type + ) : CallAdapter>> { + + override fun responseType() = responseType + + override fun adapt(call: Call): Deferred> { + val deferred = CompletableDeferred>() + + deferred.invokeOnCompletion { + if (deferred.isCancelled) { + call.cancel() + } + } + + call.enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + deferred.completeExceptionally(t) + } + + override fun onResponse(call: Call, response: Response) { + deferred.complete(response) + } + }) + + return deferred + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt new file mode 100644 index 000000000..1ef3b7d7b --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -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() + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/SSLHelper.kt b/app/src/main/java/io/legado/app/help/http/SSLHelper.kt new file mode 100644 index 000000000..dcf5b7709 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/SSLHelper.kt @@ -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, authType: String) { + } + + @Throws(CertificateException::class) + override fun checkServerTrusted(chain: Array, authType: String) { + } + + override fun getAcceptedIssuers(): Array { + 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(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? { + 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? { + 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): X509TrustManager? { + for (trustManager in trustManagers) { + if (trustManager is X509TrustManager) { + return trustManager + } + } + return null + } +} diff --git a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt index a9a847a5b..fb7b213f7 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt @@ -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> = MutableLiveData() + + public fun search(){ + + + } + +} diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index da4d4a9c6..9e2a9e07c 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -2,7 +2,6 @@ - Date: Fri, 24 May 2019 12:54:13 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0okhttp,=20retrofit?= =?UTF-8?q?=E9=85=8D=E5=90=88=E5=8D=8F=E7=A8=8B=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/base/BaseViewModel.kt | 22 ++++++---------- .../io/legado/app/data/api/CommonHttpApi.kt | 14 +++++++++++ ...ory.kt => CoroutinesCallAdapterFactory.kt} | 4 +-- .../io/legado/app/help/http/HttpHelper.kt | 15 +++++++++++ .../legado/app/ui/search/SearchViewModel.kt | 25 +++++++++++++++++-- 5 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt rename app/src/main/java/io/legado/app/help/http/{CoroutineCallAdapterFactory.kt => CoroutinesCallAdapterFactory.kt} (95%) diff --git a/app/src/main/java/io/legado/app/base/BaseViewModel.kt b/app/src/main/java/io/legado/app/base/BaseViewModel.kt index 009aafe7d..74bc4bfc2 100644 --- a/app/src/main/java/io/legado/app/base/BaseViewModel.kt +++ b/app/src/main/java/io/legado/app/base/BaseViewModel.kt @@ -13,13 +13,12 @@ open class BaseViewModel(application: Application) : AndroidViewModel(applicatio private val launchManager: MutableList = mutableListOf() protected fun launchOnUI( - tryBlock: suspend CoroutineScope.() -> Unit, - cacheBlock: suspend CoroutineScope.(Throwable) -> Unit, - finallyBlock: suspend CoroutineScope.() -> Unit, - handleCancellationExceptionManually: Boolean + tryBlock: suspend CoroutineScope.() -> Unit,//成功 + errorBlock: suspend CoroutineScope.(Throwable) -> Unit,//失败 + finallyBlock: suspend CoroutineScope.() -> Unit//结束 ) { launchOnUI { - tryCatch(tryBlock, cacheBlock, finallyBlock, handleCancellationExceptionManually) + tryCatch(tryBlock, errorBlock, finallyBlock) } } @@ -27,25 +26,20 @@ open class BaseViewModel(application: Application) : AndroidViewModel(applicatio * add launch task to [launchManager] */ private fun launchOnUI(block: suspend CoroutineScope.() -> Unit) { - val job = launch { block() } + 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 + errorBlock: suspend CoroutineScope.(Throwable) -> Unit, + finallyBlock: suspend CoroutineScope.() -> Unit ) { try { coroutineScope { tryBlock() } } catch (e: Throwable) { - if (e !is CancellationException || handleCancellationExceptionManually) { - coroutineScope { catchBlock(e) } - } else { - throw e - } + coroutineScope { errorBlock(e) } } finally { coroutineScope { finallyBlock() } } diff --git a/app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt b/app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt new file mode 100644 index 000000000..08e06f52f --- /dev/null +++ b/app/src/main/java/io/legado/app/data/api/CommonHttpApi.kt @@ -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): Deferred + + @FormUrlEncoded + @POST + fun post(@Url url: String, @FieldMap map: Map): Deferred +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt b/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt similarity index 95% rename from app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt rename to app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt index 78e0aa7a0..bf296f5bb 100644 --- a/app/src/main/java/io/legado/app/help/http/CoroutineCallAdapterFactory.kt +++ b/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt @@ -6,10 +6,10 @@ import retrofit2.* import java.lang.reflect.ParameterizedType import java.lang.reflect.Type -class CoroutineCallAdapterFactory private constructor() : CallAdapter.Factory() { +class CoroutinesCallAdapterFactory private constructor() : CallAdapter.Factory() { companion object { @JvmStatic @JvmName("create") - operator fun invoke() = CoroutineCallAdapterFactory() + operator fun invoke() = CoroutinesCallAdapterFactory() } override fun get( diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index 1ef3b7d7b..59d92b07d 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -1,6 +1,7 @@ package io.legado.app.help.http import okhttp3.* +import retrofit2.Retrofit import java.util.* import java.util.concurrent.TimeUnit @@ -9,6 +10,20 @@ object HttpHelper { val client: OkHttpClient = getOkHttpClient() + fun getApiService(baseUrl: String, clazz: Class): T { + return getRetrofit(baseUrl).create(clazz) + } + + fun getRetrofit(baseUrl: String): Retrofit { + return Retrofit.Builder().baseUrl(baseUrl) + //增加返回值为字符串的支持(以实体类返回) +// .addConverterFactory(EncodeConverter.create()) + //增加返回值为Observable的支持 + .addCallAdapterFactory(CoroutinesCallAdapterFactory.invoke()) + .client(client) + .build() + } + private fun getOkHttpClient(): OkHttpClient { val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2) diff --git a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt index fb7b213f7..0f9f2072b 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt @@ -1,18 +1,39 @@ package io.legado.app.ui.search import android.app.Application +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) : BaseViewModel(application){ +class SearchViewModel(application: Application) : BaseViewModel(application) { val searchBooks: LiveData> = MutableLiveData() - public fun search(){ + 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 { +// +// } } }