前段时间看了一下Okhttp的源码,我是从Okhttp的一般使用作为看源码的线索,即从OkHttpclient开始。
简简单单的一个OkHttpclient就用了两种设计模式–Builder模式和责任链模式,这里只对Builder模式进行
讨论。
一般情况下,我们使用OkHttpclient是这样的:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(6_000, TimeUnit.MILLISECONDS) //http连接超时
.readTimeout(30_000,TimeUnit.MILLISECONDS) //http读超时
.writeTimeout(60_000,TimeUnit.MILLISECONDS) //http写超时
.build(); //最终返回一个OkHttpClient对象
可以很清楚的看到,OkHttpclient所需要的一些属性是通过其一个内部类Builder去build()起来的。
接下来,看一下OkHttpClient这个类的部分源码吧。
public OkHttpClient() {
this(new Builder());
}
private OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
...........
}
可见,OkHttpClient的属性是通过一个Builder内部类去设置的,那就看看这个Builder的源码呗
public static final class Builder {
Dispatcher dispatcher;
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
............
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
...........
}
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
this.followSslRedirects = okHttpClient.followSslRedirects;
this.followRedirects = okHttpClient.followRedirects;
this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
this.connectTimeout = okHttpClient.connectTimeout;
this.readTimeout = okHttpClient.readTimeout;
this.writeTimeout = okHttpClient.writeTimeout;
.......
}
/**
* Sets the default connect timeout for new connections. A value of 0 means no timeout,
* otherwise values must be between 1 and {@link Integer#MAX_VALUE} when converted to
* milliseconds.
*/
public Builder connectTimeout(long timeout, TimeUnit unit) {
if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
if (unit == null) throw new NullPointerException("unit == null");
long millis = unit.toMillis(timeout);
if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
connectTimeout = (int) millis;
return this;
}
/**
* Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
* values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
*/
public Builder readTimeout(long timeout, TimeUnit unit) {
if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
if (unit == null) throw new NullPointerException("unit == null");
long millis = unit.toMillis(timeout);
if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
readTimeout = (int) millis;
return this;
}
/**
* Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
* values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
*/
public Builder writeTimeout(long timeout, TimeUnit unit) {
if (timeout < 0) throw new IllegalArgumentException("timeout < 0");
if (unit == null) throw new NullPointerException("unit == null");
long millis = unit.toMillis(timeout);
if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException("Timeout too large.");
if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
writeTimeout = (int) millis;
return this;
}
.......
public OkHttpClient build() {
return new OkHttpClient(this);
}
源码比较多,这里就只贴部分了。
OkHttpClient的构造方法里需要一个Builder, 而Builder内部类最重要的一个方法就是build(),
这个方法会返回一个OkHttpClient对象。值得注意的是,build()方法里,return new OkHttpClient(this),这个this就是在第一段代码里设置http连接超时,http读超时,http写超时等等属性的Builder对象,这样一个完整的OkHttpClient对象就build好了。
之前在项目里需要把日志信息写到SDcard上,当时是用IntentService去实现的,因为考虑到IO是耗时操作,而且IntentService干完活会自动关闭,所以就用IntentService暴力的实现了,日志的存放位置和日志名称都是写死的,现在决定用Builder模式去实现一个可以自定义日志存放位置和日志名的日志生成工具类,并改用线程池去实现IO操作,接下来,我就直接上代码了,也就100来行….哈哈
public class Loggger implements Closeable{
private static final StringBuilder DEFAULTPATH = new StringBuilder(Environment.getExternalStorageDirectory()
+ File.separator + "Loggger"); //默认的日志存储文件夹名
private static final String DEFAULTLOGNAME = Environment.getExternalStorageDirectory()
+ File.separator + "Loggger" + File.separator + "log.txt"; //默认的日志名
private StringBuilder savePath; //日志存放文件名
private String logName; //日志名
private String logContent; //日志内容
private StringBuilder finalLogContent; //最终的日志内容,时分秒毫秒+logContent
private ExecutorService writeLogService;
private boolean okToClose = false;
public Loggger(){
this(new LogBuilder());
}
Loggger(LogBuilder logBuilder){
this.savePath = logBuilder.savePath;
this.logName = logBuilder.logName;
}
public synchronized void writeLog(String content){
this.logContent = content;
writeLogService = Executors.newSingleThreadExecutor();
writeLogService.execute(runnable);
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
try {
finalLogContent = new StringBuilder();
String originSavePath = savePath.toString();
File logFile = new File(savePath.append(File.separator).append(logName).toString());
if (!logFile.exists()) {
File dir = new File(originSavePath);
if (!dir.exists()) {
dir.mkdirs();
}
logFile.createNewFile();
FileOutputStream fos = new FileOutputStream(logFile,true);
fos.write(finalLogContent.append(getCurrentTime()).append(" ").
append(logContent).append("\n").toString().getBytes("utf-8"));
fos.flush();
fos.close();
setOkToClose(true);
} else {
FileOutputStream fos = new FileOutputStream(logFile,true);
fos.write(finalLogContent.append(getCurrentTime()).append(" ").
append(logContent).append("\n").toString().getBytes("utf-8"));
fos.flush();
fos.close();
setOkToClose(true);
}
}catch (IOException e){
e.printStackTrace();
}
}
};
public static class LogBuilder{
StringBuilder savePath;
String logName;
public LogBuilder(){
savePath = DEFAULTPATH;
logName = DEFAULTLOGNAME;
}
public LogBuilder savePath(String path){
if (TextUtils.isEmpty(path)){
this.savePath = DEFAULTPATH;
throw new IllegalArgumentException("save path cannot be empty!");
}else {
this.savePath = new StringBuilder(path);
}
return this;
}
/**
* 自命名日志名(We can name our log)
* @param name 日志名 (The log name what you want)
* @return
*/
public LogBuilder logByName(String name){
if (TextUtils.isEmpty(name)){
this.logName = DEFAULTLOGNAME;
throw new IllegalArgumentException("log name cannot be empty!");
}else {
this.logName = name;
}
return this;
}
/**
* 日志名为当天日期 (The log name is today's date)
* @return
*/
public LogBuilder logByDay(){
String originDate = "1992-07-18";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
StringBuilder now = new StringBuilder(sdf.format(System.currentTimeMillis()));
if (!originDate.equals(now.toString())){
this.logName = now.append(".txt").toString();
}
return this;
}
public Loggger build(){
return new Loggger(this);
}
}
private String getCurrentTime(){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
return sdf.format(System.currentTimeMillis());
}
@Override
public void close(){
if (isOkToClose() && writeLogService != null){
writeLogService.shutdownNow();
}
}
public boolean isOkToClose() {
return okToClose;
}
public synchronized void setOkToClose(boolean okToClose) {
this.okToClose = okToClose;
}
}
使用示例:
自定义日志文件名和存放位置
Loggger loggger = new Loggger.LogBuilder().
savePath(Environment.getExternalStorageDirectory() + File.separator + "haha").logByName("hh.txt").build();
loggger.writeLog("log something"); //日志内容
loggger.close();
自动以当天日期作为日志名
Loggger loggger = new Loggger.LogBuilder().
savePath(Environment.getExternalStorageDirectory() + File.separator + "haha").logByDay().build();
loggger.writeLog("log something");
loggger.close();
好了,感兴趣的同学可以去我的github给个star :项目地址