《android开发进阶从小工到专家》读书笔记--网络框架的设计与实现

Posted 嘉禾世兴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《android开发进阶从小工到专家》读书笔记--网络框架的设计与实现相关的知识,希望对你有一定的参考价值。

第一步:

第一层:Request--请求类型,JSON,字符串,文件

第二层:消息队列--维护了提交给网络框架的请求列表,并且根据响应的规则进行排序。默认情况下按照优先级和进入队列的顺序来执行,该队列使用的是线程安全的PriorityBlockingQueue<E>,因为我们的队列会被并发访问,因此需要保证队列访问的原子性

第三层:NetworkExecutor--网络执行者,该Eexcutor继承自Thread,在run方法中循环访问请求队列,从请求队列中获取并执行HTTP请求,请求完成之后将结果投递给UI线程

第四层:Response以及Response投递类,使用ResponseDelivery来封装Response的投递,保证Response执行在UI线程。而Response会根据用户的不同需求将返回结果格式化为特定的类型。

第二步:网络请求类

public static enum HttpMethod{
    GET("GET"),
    POST("POST"),
    PUT("PUT"),
    DELETE("DELETE");
    
    /** http request type */
    private String mHttpMethod = "";
    
    privte HttpMethod(String method){
        mHttpMethod = method;
    }
    
    @Override
    public String toString(){
        return mHttpMethod;
    }
}

//优先级枚举
public static enum Priority{
    LOW,
    NORMAL,
    HIGH,
    IMMEDIATE
}
/**
  * 网络请求类,注意GET和DELETE不能传递请求参数,因为其请求的性质所致,用户可以将参数构建到URL后传递进来并到Request中
  * @param <T> T为请求返回的数据类型
  */
public abstract class Request<T> implements Comparable<Request<T>>{
    //默认的编码方式
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
    //请求序列号
    protected int mSerialNum = 0;
    //优先级默认设置为Normal
    protected Priority mPriority = Priority.NORMAL;
    //是否取消该请求
    protected boolean isCancel = false;
    /** 改请求是否应该缓存*/
    private boolean mShouldCache = true;
    //请求Listener
    protected RequestListener<T> mRequestlistener;
    //请求的URL
    private String mUrl = "";
    //请求的方法
    HttpMethod mHttpMethod = HttpMethod.GET;
    //请求的header
    private Map<String,String> mHeaders = new HashMap<String,String>();
    //请求参数
    private Map<String,String> mBodyParams = new HashMap<String,String>();
    
    /**
     * @param method 请求方式
     * @param url 请求的目标URL
     * @param listener 请求回调,将结果回调给用户
     */
    public Request(HttpMethod method,String url,RequestListener<T> listener){
        mHttpMethod = method;
        mUrl = url;
        mRequestlistener = listener;
    }
    
    //从原生的网络请求中解析结果,子类必须覆写
    public abstract T parseResponse(Response response);
    
    //处理Response,该方法需要运行在UI线程
    public final void deliveryResponse(Response response){
        //解析得到请求结果
        T result = parseResponse(response);
        if(mRequestlistener!=null){
            int stCode = response !=null?response.getStatusCode:-1;
            String msg = response !=null?response.getMessage():"unknown error";
            mRequestlistener.onComplete(stCode,result,msg);
        }
    }
    //代码省略
    
    protected String getParamsEncoding(){
        return DEFAULT_PARAMS_ENCODING;
    }
    
    public String getBodyContentType(){
        return "application/x-www-form-urlencoded;charset="+getParamsEncoding();
    }
    
    //返回POST或者PUT请求时的Body参数字节数组
    public byte[] getBody(){
        Map<String,String> params = getParams();
        if(params!=null && params.size()>0){
            return encodeParameters(params,getParamsEncoding());
        }
        return null;
    }
    
    //将参数转换为URL编码的参数串,格式为key1=value1&key2=value2
    private byte[] encodeParameters(Map<String,String> params,String paramsEncoding){
        StringBuilder encodedParams = new StringBuilder();
        try{
            for(Map.Entry<String,String> entry:params.entrySet()){
                encodedParams.append(URLEncoder.encode(entry.getKey(),paramsEncoding));
                encodedParams.append(\'=\');
                encodedParams.append(URLEncoder.encode(entry.getValue(),paramsEncoding));
                encodedParams.append(\'&\');
            }
            return encodedParams.toString().getBytes(paramsEncoding);
        }catch(UnsupportEncodingException uee){
            throw new RuntimeException("Encoding not supported:"+paramsEncoding,uee);
        }
    }
    
    //用于对请求的排序处理,根据优先级和加入到队列的序号进行排序
    @Override
    public int compareTo(Request<T> another){
        Priority myPriority = this.getPriority();
        Priority anotherPriority = another.getPriority();
        //如果优先级相等,那么按照添加到队列的序列号顺序来执行
        return myPriority.equals(another)?this.getSerialNumber()-another.getSerialNumber():myPriority.ordinal()-anotherPriority.ordinal();
    }
    
    /**
      *网络请求Listener,会被执行在UI线程
      *@param <T> 请求的response类型
      */
    public static interface RequestListener<T>{
        //请求完成的回调
        public void onComplete(int stCode,T response,String errMsg);
    }
}

HTTP实际上是基于TCP协议,而TCP协议又是基于Socket,Socket实际上操作的也就是输入、输出流,输出流是向服务器写数据,输入流是从服务器读取数据。

第三步:响应类

//请求结果类,继承自BasicHttpResponse,将结果存储在rawData中
public class Response extends BasicHttpResponse{
    //原始的Response主体数据
    public byte[] rawData = new byte[0];
    
    public Response(StatusLine statusLine){
        super(statusLine);
    }
    
    public Response(ProtocolVersion ver,int code,String reason){
        super(ver,code,reason);
    }
    
    @Override
    public void setEntity(HttpEntity entity){
        super.setEntity(entity);
        rawData = entityToBytes(getEntity());
    }
    
    public byte[] getRawData(){
        return rawData;
    }
    
    //代码省略
    /**Reads the contents of HttpEntity into a byte[].*/
    private byte[] entityToBytes(HttpEntity entity){
        try{
            return EntityUtils.toByteArray(entity);
        }catch(IOException e){
            e.printStackTrace();
        }
        return new byte[0];
    }
}

第四步:请求队列

网络请求队列就是在内部封装了一个优先级队列,在构建队列时会启动指定个数的NetworkExecutor(继承自Thread)来从请求队列中获取、执行请求,请求队列会根据请求的优先级、序列号对所有Request进行排序。

//请求队列,使用优先队列,使得请求可以按照优先级进行处理
public final class RequsetQueue{
    //线程安全的请求队列
    private BlockingQueue<Request<?>> mRequestQueue = new PriorityBlockingQueue<Request<?>>();
    //请求的序列化生成器
    private AtomicInteger mSerialNumGenerator = new AtomicInteger(0);
    //默认的核心数 为CPU格式加1
    private int mDispatcherNums = DEFAULT_CORE_NUMS;
    //NetworkExecutor,执行网络请求的线程
    private NetworkExecutor[] mDispatchers = null;
    //Http请求的真正执行者
    private HttpStack mHttpStack;
    
    protected RequestQueue(int coreNums,HttpStack httpStack){
        mDispatcherNums = coreNums;
        
        mHttpStack = httpStack !=null?httpStack:HttpStackFactory.createHttpStack();
    }
    
    //启动NetworkExecutor
    private final void startNetworkExecutors(){
        mDispatchers = new NetworkExecutor[mDispatchNums];
        for(int i=0;i<mDispatchNums;i++){
            mDispatchers[i] = new NetworkExecutor(mRequestQueue,mHttpStack);
            mDispatchers[i].start();
        }
    }
    
    public void start(){
        stop();
        startNetworkExecutors();
    }
    
    /**
      * 停止NetworkExecutor
      */
    public void stop(){
        if(mDispatchers!=null && mDispatchers.length>0){
            for(int i=0;i<mDispatchers.length;i++){
                mDispatchers[i].quit();
            }
        }
    }
    
    //添加请求到队列中
    public void addRequest(Request<?> request){
        if(!mRequestQueue.contains(request)){
            //为请求设置序列号
            request.setSerialNumber(this.generateSerialNumber());
            mRequestQueue.add(request);
        }else{
            Log.d("","### 请求队列中已经含有");
        }
    }
    
    //代码省略
    //为每个请求生成一个系列号
    private int generateSerialNumber(){
        return mSerialNumGenerator.incrementAndGet();
    }
}

第五步:NetworkExecutor网络执行器

多个NetworkExecutor共享一个消息队列,在各个NetworkExecutor的run函数中循环地取请求队列中的请求,拿到一个请求之后通过HttpStack对象来真正地执行请求,最终将请求结果通过ResponseDelivery分发给UI线程。

NetworkExecutor实质上是一个Thread,在run方法中我们会执行一个循环,不断地从请求队列中取得请求,然后交给HttpStack。

//网络请求Exector,继承自Thread,从网络请求队列中循环读取请求并且执行
final class NetworkExecutor extends Thread{
    //网络请求队列
    private BlockingQueue<Request<?>> mRequestQueue;
    //网络请求栈
    private HttpStack mHttpStack;
    //结果分发器,将结果投递到主线程
    private static ResponseDelivery mResponseDelivery = new ResponseDelivery();
    //请求缓存
    private static Cache<String,Response> mReqCache = new LruMemCache();
    //是否停止
    private boolean isStop = false;
    
    public NetworkExecutor(BlockingQueue<Request<?>> queue,HttpStack httpStack){
        mRequestQueue = queue;
        mHttpStack = httpStack;
    }
    
    @Override
    public void run(){
        try{
            while(!isStop){
                final Request<?> request = mRequestQueue.take();
                if(request.isCanceled()){
                    Log.d("###","### 取消执行了");
                    continue;
                }
                Response response = null;
                if(isUseCache(request)){
                    //从缓存中取
                    response = mReqCache.get(request.getUrl());
                }else{
                    //从网络中获取数据
                    response = mHttpCache.get(request.getUrl());
                }else{
                    //从网络上获取数据
                    response = mHttpStack.performRequest(request);
                    //如果该请求需要缓存,那么请求成功则缓存到mResponseCache中
                    if(request.shouldCache() && isSuccess(response)){
                        mReqCache.put(request.getUrl(),response);
                    }
                }
                //分发请求结果
                mResponseDelivery.deliveryResponse(request,response);
            }
        }catch(InterruptedExecption e){
            Log.i("","### 请求分发器退出");
        }
    }
    
    private boolean isSuccess(Response response){
        return reponse!=null && response.getStatusCode()==200;
    }
    
    private boolean isUseCache(Request<?> request){
        return request.shouldCache() && mReqCache.get(request.getUrl())!=null;
    }
    
    public void quit(){
        isStop = true;
        interrupt();
    }
}

第六步:执行网络请求的接口--HttpStack

//执行网络请求的接口
public interface HttpStack{
    /**
      * 执行HTTP请求
      * @param request 待执行的请求
      * @retrun 返回Response
      */
    public Response performRequest(Request<?> request);
}
//使用HttpURLConnection执行网络请求的HttpStack
public class HttpUrlConnStack implements HttpStack{
    @Override
    public Response performRequest(Request<?> request){
        HttpURLConnection urlConnection = null;
        try{
            //构建HttpURLConnection
            urlConnection = createUrlConnection(request.getUrl());
            //设置headers
            setRequestHeaders(urlConnection,request);
            //设置Body参数
            setRequestParams(urlConnection,request);
            return fetchResponse(urlConnection);
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if(urlConnection!=null){
                urlConnection.disconnect();
            }
        }
        return null;
    }
    
    private HttpURLConnection createUrlConnection(String url) throws IOException{
        URL newURL = new URL(url);
        URLConnection urlConnection = newURL.openConnection();
        urlConnection.setConnectTimeout(mConfig.connTimeOut);
        urlConnection.setReadTimeout(mConfig.soTimeOut);
        urlConnection.setDoInput(true);
        urlConnection.setUseCaches(false);
        return (HttpURLConnection)urlConnection;
    }
    
    private void setRequestHeaders(HttpURLConnection connection,Request<?> request){
        Set<String> headersKeys = request.getHeaders().keySet();
        for(String headerName : headersKeys){
            connection.addRequestProperty(headerName,request.getHeaders().get(headerName));
        }
    }
    
    protected void setRequestParams(HttpURLConnection connection,Request<?> request) throws ProtocolException,IOException{
        HttpMethod method = request.getHttpMethod();
        connection.setRequestMethod(method.toString());
        //add params
        byte[] body = request.getBody();
        if(body!=null){
            //enable output
            connection.setDoOutput(true);
            //set content type
            connection.addRequestProperty(Request.HEADER_CONTENT_TYPE,request.getBodyContentType());
            //write params data to connection
            DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
            dataOutputStream.write(body);
            dataOutputStream.close();
        }
    }
    
    private Response fetchResponse(HttpURLConnection connection) throws IOException{
        //Initialize HttpResponse with data from the HttpURLConnection
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP",1,1);
        int responseCode = connection.getResponseCode();
        if(responseCode == -1){
            throw new IOException("Could not retrieve response code from HttpURLConnection.");
        }
        //状态行数据
        StatusLine responseStatus = new BasicStatusLine(ptocolVersion,connection.getResponseCode(),connection.getResponseMessage());
        //构建response
        Response response = new Response(responseStatus);
        //设置response数据
        response.setEntity(entityFromURLConnection(connection));
        addHeadersToResponse(response,connection);
        return response;
    }
    
    /**
    * 执行HTTP请求之后获取到其数据流,即返回请求结果的流
    * @param connection
    * @return
    */
    private HttpEntity entityFromURLConnection(HttpURLConnection connection){
        BasicHttpEntity entity = new BasicHttpEntity();
        InputStream inputStream = null;
        try{
            inputStream = connection.getInputStream();
        }catch(IOException e){
            e.printStackTrace();
            inputStream = connection.getErrorStream();
        }
        //TODO:GZIP
        entity.setContent(inputStream);
        entity.setContentlength(connection.getContentLength());
        entity.setContentEncoding(connection.getContentEncoding());
        entity.setContentType(connection.getContentType());
        return entity;
    }
    
    private void addHeadersToResponse(BasicHttpResponse response,HttpURLConnection connection){
        for(Entry<String,List<String>> header : connection.getHeaderFields().entrySet()){
            if(header.getKey() != null){
                Header h = new BasicHeader(header.getKey(),header.getValue().get(0));
                response.addHeader(h);
            }
        }
    }
}

简单来说:构建HttpURLConnection,并通过HttpURLConnection对象设置请求Header、参数,然后发起请求,请求完成只有解析结果,并返回Response。

第七步:将请求的回调执行到UI线程--ResponseDelivery

//请求结果投递类,将请求结果投递给UI线程
class ResponseDelivery implements Executor{
    //关联主线程消息队列的hander
    Hander mResponseHandler = new Handler(Looper.getMainLooper());
    /**
      * 处理请求结果,将其执行在UI线程
      * @param request
      * @param response
      */
    public void deliveryResponse(final Request<?> request,final Response response){
        Runnable respRunnable = new Runnable(){
            @Override
            public void run(){
                request.deliveryResponse(response);
            }
        };
        
        execute(respRunnable);
    }
    
    @Override
    public void execute(Runnable command){
        mResponseHandler.post(command);
    }
}

ResponseDelivery其实就是封装了关联了UI线程消息队列的Handler。

使用:

//1.构建并启动请求队列
RequestQueue mQueue = SimpleNet.newRequestQueue();
/**
  * 发送GET请求,返回的是String类型的数据,同理还有{@see JsonRequest}、{@see MultipartRequest}
  */
private void sendStringRequest(){
    //2.构建请求
    StringRequest request = new StringRequest(HttpMethod.GET,"http://www.baidu.com",
        new RequestListener<String>(){
            @Override
            public void onComplete(int stCode,String response,String errMsg){
                //处理结果
            }
        });
    //3.将请求添加到请求队列中
    mQueue.addRequest(request);
} 

 

以上是关于《android开发进阶从小工到专家》读书笔记--网络框架的设计与实现的主要内容,如果未能解决你的问题,请参考以下文章

Android工程师进阶之路 :《Android开发进阶:从小工到专家》上市啦!

《程序员修炼之道:从小工到专家》读书笔记

Androidproject师进阶之路 :《Android开发进阶:从小工到专家》上市啦!

第四周读书笔记——《程序员修炼之道——从小工到专家》

《程序员修炼之道--从小工到专家》读书小计

程序员修炼之道_从小工到专家_读书分享