这里我们就简单分析一下demo中网络课和图片库的架构模型
网络库基本模型
架构设计过程分析:
对于一个框架的设计, 使用面向接口编程是必不可少的技能,
http/https是基于请求-响应模型, 我们需要抽象出一个请求和响应接口类,
抽象一个请求接口, 其实现可以是基于HttpURLConnection、Socket等, 抽象接口是为了约束使用者构造请求时提供请求所需要的参数, 如下接口满足一个网络请求的条件: url、请求参数、请求类型、执行请求 。
public interface IHttpRequest {
void setUrl(String url);
void setMethod(@HttpMethodType String method);
void setRequestData(byte[] requestData);
void execute();
// 设置两个接口之间的关系, 由execute()方法可知,实现类会执行真正的网络请求, 请求的响应接口只能由自己维护
void setHttpCallback(IHttpCallback httpCallback);
}
抽象响应接口,考虑到框架的扩展性, 返回到应用层的数据可能是Json、String或者范型对象, 这里传递了网络请求最原始的流数据,
public interface IHttpCallback {
// 接受上一个接口的结果
void onSuccess(InputStream inputStream);
void onFailure(String error);
}
请求管理类的实现, 具体代码看注释
public class RequestManager {
private LinkedBlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
private ThreadPoolExecutor threadPoolExecutor;
// 3.将请求任务添加到请求队列
public void execute(Runnable runnable) {
// TODO queue.put(runnable);
}
// 1.构建消费者任务
private Runnable runnable = new Runnable() {
@Override
public void run() {
while(true) {
Runnable runnable = null;
// TODO 从阻塞队列中取出请求任务
// runnable = queue.take();
// 2.请求任务放入到线程池执行
// threadPoolExecutor.execute(runnable);
}
}
};
private ThreadPoolManage() {
threadPoolExecutor = new ThreadPoolExecutor(4,
20, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), rejectedExecutionHandler);
// 让消费者线程放入到线程池执行
threadPoolExecutor.execute(runnable);
}
......
}
现在该生产者出场了, 我们这里仿照Volley的实现, 通过中间层Volley类来进行面向接口的Json数据格式的请求,
public class Volley {
public static <T, M>void sendJSONRequest(@HttpMethodType String method, T requestInfo, String url, Class<M> responseType, IDataCallback<M> dataCallback) {
IHttpRequest httpRequest = new JsonRequest();
IHttpCallback httpCallback = new JsonHttpCallback(responseType, dataCallback);
HttpTask<T> httpTask = new HttpTask<T>(method, requestInfo, url, httpRequest, httpCallback);
RequestManager.getInstance().execute(httpTask);
}
}
对Volley类的分析:
1. 这里设计IDataCallback<M>的原因是, IHttpRequest的实现类完成网络请求后将原始流数据回调给IHttpCallback的实现类JsonHttpCallback, 在JsonHttpCallback类中将流数据转变成范型的对象后, 网络请求结果数据传递中断, 故需要调用处提供IDataCallback实现来处理后续的网络请求数据, 当时思考如果网络请求回调使用一个callback那么出现在调用处的将是数据流对象, 对调用者不够友好且增加了解析的工作量;
2. 这里构造了HttpTask对象, 并将其添加到RequestManager的请求队列中, 那么很明显HttpTask需要继承Runnable接口, 当HttpTask任务被线程池中的线程执行时就会调用其run()方法, 我们就在方法体内执行httpRequest.execute()来进行真正的网络请求执行, 那么HttpTask类中就需要持有httpRequest的引用。
public interface IDataCallback<M> {
void onSuccess(M m);
void onFailure(String error);
}
public class JsonHttpCallback<M> implements IHttpCallback {
Class<M> responseClass;
IDataCallback<M> dataCallback;
// 用于切换线程
Handler handler = new Handler(Looper.getMainLooper());
@Override
public void onSuccess(InputStream inputStream) {
String content = getContent(inputStream);
final M response = new Gson().fromJson(content, responseClass);
handler.post(new Runnable() {
@Override
public void run() {
if (dataCallback != null) {
dataCallback.onSuccess(response);
}
}
});
}
private String getContent(InputStream inputStream) {
String content = null;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line;
try {
while((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// TODO 关闭流
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return content;
}
}
public class JsonHttpRequest implements IHttpRequest{
String url;
private byte[] requestData;
IHttpCallback httpCallback;
String method;
@Override
public void setRequestData(byte[] requestData) {
this.requestData = requestData;
}
@Override
public void execute() {
if (TextUtils.equals(method, HttpMethodType.GET)) {
connectionGet();
} else if (TextUtils.equals(method, HttpMethodType.POST)){
connectionPost();
}
}
private void connectionPost() {
HttpURLConnection connection = null;
URL url;
try {
url = new URL(this.url);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(6000);
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(true); // 仅作用于当前函数
connection.setReadTimeout(3000);
connection.setDoInput(true); // 设置连接可写入数据
connection.setDoOutput(true); // 设置连接可以输出数据
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
connection.connect();
// ----------使用字节流发送数据------------
OutputStream out = connection.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(out);
if (requestData != null) {
bos.write(requestData);
}
// 把字节数组的数据写入缓冲区
bos.flush(); // 刷新缓冲区,发送数据
out.close();
bos.close();
// --------字节流写入数据---------
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream in = connection.getInputStream();
httpCallback.onSuccess(in);
} else {
httpCallback.onFailure(String.valueOf(connection.getResponseCode()) + ":" + connection.getResponseMessage());
}
} catch (Exception e) {
e.printStackTrace();
httpCallback.onFailure(e.getMessage());
} finally {
connection.disconnect(); // 使用完关闭TCP连接,释放资源
}
}
private void connectionGet() {
// TODO
}
}
网络请求库的架构设计
这里的架构设计和网络请求库的架构设计大同小异, 也是一种典型的生产者-消费者模式, 类比较简单就直接贴代码了, 具体分析会在代码中
因为要构造链式调用, 所以请求类不在使用接口编程,
public class ImageRequest {
private String url;
private Context context;
private SoftReference<ImageView> imageViewRef;
private int placeHoldId;
private ImageCallback requestListener;
private String urlMd5;
public static ImageRequest with(Context context) {
ImageRequest request = new ImageRequest();
request.context = context;
return request;
}
public ImageRequest load(String url) {
this.url = url;
// TODO md5加密
this.urlMd5 = url;
return this;
}
public void into(ImageView imageView) {
imageView.setTag(urlMd5);
this.imageViewRef = new SoftReference<>(imageView);
ImageManager.getInstance().addBitmapRequest(this);
}
}
在每次调用into的时候就会将构造的请求添加到ImageManager类中,
class ImageManager {
// TODO 疑问: 1. 添加的请求存在队列里(队列里的请求一般是Runnable),如何调度, 管理者内部维护着一个阻塞式的死循环轮询队列;
// TODO 疑问: 2. 如何关联ImageDispatcher;
// TODO 疑问: 3. ImageDispatcher的复用. 如果用线程池, 那么线程池怎么调度ImageDispatcher
// TODO 思考: 1. 线程池是复用内部的线程来execute从请求队列中取出的Runnable任务
// TODO 思考: 2. 目前把图片请求的实现放在了Thread的子类执行,不满足threadPoolExecutor.execute()的入参
private static volatile ImageManager instance;
// 同步问题
private LinkedBlockingDeque<ImageRequest> mDeque = new LinkedBlockingDeque<>();
private ImageDispatcher[] bitmapDispatchers;
public static ImageManager getInstance() {
// TODO
}
private void start() {
stop();
startAllDispatchers();
}
private void stop() {
if (bitmapDispatchers != null && bitmapDispatchers.length > 0) {
for (ImageDispatcher bitmapDispatcher : bitmapDispatchers) {
if (!bitmapDispatcher.isInterrupted()) {
bitmapDispatcher.interrupt();
}
}
}
}
private void startAllDispatchers() {
int threadCount = Runtime.getRuntime().availableProcessors();
bitmapDispatchers = new ImageDispatcher[threadCount];
for (int i = 0; i < threadCount; i++) {
ImageDispatcher bitmapDispatcher = new ImageDispatcher(mDeque);
bitmapDispatcher.start();
bitmapDispatchers[i] = bitmapDispatcher;
}
}
public void addBitmapRequest(ImageRequest bitmapRequest) {
if (bitmapRequest == null) {
return;
}
if (!mDeque.contains(bitmapRequest)) {
mDeque.add(bitmapRequest);
}
}
}
最后是图片请求加载
public class ImageDispatcher extends Thread {
// 由银行创建
private LinkedBlockingDeque<ImageRequest> mDeque;
private Handler mHandler = new Handler(Looper.getMainLooper());
// 比如银行办理业务的人和窗口
// TODO 为啥是外部传递 线程安全、 先进先出
public ImageDispatcher(LinkedBlockingDeque deque) {
mDeque = deque;
}
@Override
public void run() {
super.run();
// 办业务
// 从队列里获取请求
// 设置占位图
// 从服务器加载具体图片
// 把图片显示到ImageView
while (!isInterrupted()) {
ImageRequest request = null;
try {
request = mDeque.take();
showLoadingImg(request);
// 内存找, 找不到,去硬盘找
// LruCache<key, Bitmap>
Bitmap bitmap = findBitmap(request);
// 硬盘找不到, 去网络找
// DiskLruCache
showImageView(request, bitmap);
if (request.requestListener() != null) {
request.requestListener().onSuccess(bitmap);
}
} catch (Exception e) {
e.printStackTrace();
if (request != null && request.requestListener() != null) {
request.requestListener().onFailure();
}
}
}
}
private Bitmap findBitmap(ImageRequest request) {
return downloadImage(request.url());
}
private Bitmap downloadImage(@Nullable String url) {
InputStream is = null;
Bitmap bitmap = null;
try {
URL uri = new URL(url);
HttpURLConnection conn = (HttpURLConnection) uri.openConnection();
is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
} catch (Exception e) {
e.printStackTrace();
} finally {
// TODO
}
return bitmap;
}
private void showImageView(final ImageRequest request, final Bitmap bitmap) {
if (TextUtils.equals(request.url(), request.urlMd5()) && request.imageView() != null) {
// TODO
}
}