话不多说上代码
下面的代码应该是完整的,cv战士应该可以直接使用
Client配置
object OkClients {
val postFileClient = getOkHttpClients()
//kotlin里面int不能自动转换为long
val okHttpClient = getOkHttpClients(Constant.READ_SECONDS, Constant.WRITE_SECONDS, Constant.CONNECT_SECONDS)
private fun getOkHttpClients(read_seconds: Long = 0, write_seconds: Long = 0, connect_seconds: Long = 0): OkHttpClient {
val sslParams = HttpsUtils.getSslSocketFactory()
//根据源码可知道只有builder可以设置超时时间
return OkHttpClient.Builder()
.addInterceptor(if (BuildConfig.DEBUG) {
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
} else {
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.NONE)
})
.readTimeout(read_seconds, TimeUnit.SECONDS)
.connectTimeout(write_seconds, TimeUnit.SECONDS)
.writeTimeout(connect_seconds, TimeUnit.SECONDS)
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.hostnameVerifier(HttpsUtils.UnSafeHostnameVerifier)
.build()
}
}
HttpsUtils 用于干信任自己生成的证书
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:16/9/11
* 描 述:Https相关的工具类
* 修订历史:
* ================================================
*/
public class HttpsUtils {
public static class SSLParams {
public SSLSocketFactory sSLSocketFactory;
public X509TrustManager trustManager;
}
public static SSLParams getSslSocketFactory() {
return getSslSocketFactoryBase(null, null, null);
}
/**
* https单向认证
* 可以额外配置信任服务端的证书策略,否则默认是按CA证书去验证的,若不是CA可信任的证书,则无法通过验证
*/
public static SSLParams getSslSocketFactory(X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, null, null);
}
/**
* https单向认证
* 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream... certificates) {
return getSslSocketFactoryBase(null, null, null, certificates);
}
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* certificates -> 用含有服务端公钥的证书校验服务端证书
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream... certificates) {
return getSslSocketFactoryBase(null, bksFile, password, certificates);
}
/**
* https双向认证
* bksFile 和 password -> 客户端使用bks证书校验服务端证书
* X509TrustManager -> 如果需要自己校验,那么可以自己实现相关校验,如果不需要自己校验,那么传null即可
*/
public static SSLParams getSslSocketFactory(InputStream bksFile, String password, X509TrustManager trustManager) {
return getSslSocketFactoryBase(trustManager, bksFile, password);
}
private static SSLParams getSslSocketFactoryBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) {
SSLParams sslParams = new SSLParams();
try {
KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
TrustManager[] trustManagers = prepareTrustManager(certificates);
X509TrustManager manager;
if (trustManager != null) {
//优先使用用户自定义的TrustManager
manager = trustManager;
} else if (trustManagers != null) {
//然后使用默认的TrustManager
manager = chooseTrustManager(trustManagers);
} else {
//否则使用不安全的TrustManager
manager = UnSafeTrustManager;
}
// 创建TLS类型的SSLContext对象, that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
// 用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书
// 第一个参数是授权的密钥管理器,用来授权验证,比如授权自签名的证书验证。第二个是被授权的证书管理器,用来验证服务器端的证书
sslContext.init(keyManagers, new TrustManager[]{manager}, null);
// 通过sslContext获取SSLSocketFactory对象
sslParams.sSLSocketFactory = sslContext.getSocketFactory();
sslParams.trustManager = manager;
return sslParams;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new AssertionError(e);
}
}
private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
try {
if (bksFile == null || password == null) return null;
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(bksFile, password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientKeyStore, password.toCharArray());
return kmf.getKeyManagers();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static TrustManager[] prepareTrustManager(InputStream... certificates) {
if (certificates == null || certificates.length <= 0) return null;
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 创建一个默认类型的KeyStore,存储我们信任的证书
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certStream : certificates) {
String certificateAlias = Integer.toString(index++);
// 证书工厂根据证书文件的流生成证书 cert
Certificate cert = certificateFactory.generateCertificate(certStream);
// 将 cert 作为可信证书放入到keyStore中
keyStore.setCertificateEntry(certificateAlias, cert);
try {
if (certStream != null) certStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//我们创建一个默认类型的TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//用我们之前的keyStore实例初始化TrustManagerFactory,这样tmf就会信任keyStore中的证书
tmf.init(keyStore);
//通过tmf获取TrustManager数组,TrustManager也会信任keyStore中的证书
return tmf.getTrustManagers();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
return (X509TrustManager) trustManager;
}
}
return null;
}
/**
* 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查,
* 这是一种有很大安全漏洞的办法
*/
public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
};
/**
* 此类是用于主机名验证的基接口。 在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,
* 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。策略可以是基于证书的或依赖于其他验证方案。
* 当验证 URL 主机名使用的默认规则失败时使用这些回调。如果主机名是可接受的,则返回 true
*/
public static HostnameVerifier UnSafeHostnameVerifier = (hostname, session) -> true;
}
最重要的下载类
上面全是铺垫,只是为了下面这几句代码,大家只用调用OkDownload类的download的方法就行了,IHttpDownload和接口可以给基类实现,也可以单独用匿名对象,lambda表达式也行。
这里讲解下协程
launch(UI) {}//UI线程里面做事情可以有返回值
launch(CommonPool){}//子线程池里面做事情可以有返回值
这两个操作符可以做基本上所有的事情了,我尽量解释的简单点。我也只会用launch哈哈。不好意思哦,对了这里一定要用execute,不要用enqueue,因为enqueue本来就是异步的,execute才是同步的,用协程来控制线程就行了。
interface IHttpDownload {
fun onResult(file: File, type: String)
}
class OkDownload(private val response: IHttpDownload) {
val gson = Gson()
/**
* @param url 下载的连接
* @param file 目标下载的位置
* @param type 请求的类型作为接口回调的标识
*/
fun download(url: String, file: File, type: String) {
val request = Request.Builder()
.url(url)
.get()
.build()
val newCall = OkClients.okHttpClient.newCall(request)
launch(CommonPool) {
val response = newCall.execute()
response.body()?.contentLength()
val bytes = response.body()?.byteStream()
if (file.exists()) {
bytes?.apply {
val fos = file.outputStream()
val b = ByteArray(1024)
var len = this.read(b)
//var process = 0
while (len != -1) {
fos.write(b, 0, len)
len = this.read(b)
// process += ch
// view.downLoading(process) //这里就是关键的实时更新进度了!
//更新进度不能这么写
}
}
}
launch(UI) { this@OkDownload.response.onResult(file, type) }
}
}
}
gradle需要配置的东西如下
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.5"
implementation 'com.google.code.gson:gson:2.8.1'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
kotlin {
experimental {
coroutines "enable"
}
}