尊重原创 http://write.blog.csdn.net/postedit/25921795

在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列

  1. /** The cache triage queue. */
  2. private final PriorityBlockingQueue<Request<?>> mCacheQueue =
  3. new PriorityBlockingQueue<Request<?
  4.  
  5. >>();
  6.  
  7. /** The queue of requests that are actually going out to the network. */
  8. private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
  9. new PriorityBlockingQueue<Request<?
  10.  
  11. >>();

与之相应的分别有本地线程和网络线程。通过对RequestQueue源代码的分析,本地线程有一条。而网络线程默认有四条,我们能够对网络线程的个数进行设置,我们首先来学习一下本地线程:

(1) CacheDispatcher.java

  1. public class CacheDispatcher extends Thread {
  2.  
  3. private static final boolean DEBUG = VolleyLog.DEBUG;
  4.  
  5. //本地队列,从RequestQueue中传递进来的
  6. private final BlockingQueue<Request<?>> mCacheQueue;
  7.  
  8. //网络请求队列。也是从RequestQueue中传递进来,当本地缓存没有命中时。须要把请求从本地队列增加网络队列
  9. private final BlockingQueue<Request<?>> mNetworkQueue;
  10.  
  11. //磁盘缓存对象
  12. private final Cache mCache;
  13.  
  14. //就是用于从子线程向Ui线程发送数据
  15. private final ResponseDelivery mDelivery;
  16.  
  17. /** Used for telling us to die. */
  18. private volatile boolean mQuit = false;
  19.  
  20. /**
  21. * Creates a new cache triage dispatcher thread. You must call {@link #start()}
  22. * in order to begin processing.
  23. *
  24. * @param cacheQueue Queue of incoming requests for triage
  25. * @param networkQueue Queue to post requests that require network to
  26. * @param cache Cache interface to use for resolution
  27. * @param delivery Delivery interface to use for posting responses
  28. */
  29. public CacheDispatcher(
  30. BlockingQueue<Request<?
  31.  
  32. >> cacheQueue, BlockingQueue<Request<?
  33.  
  34. >> networkQueue,
  35. Cache cache, ResponseDelivery delivery) {
  36. mCacheQueue = cacheQueue;
  37. mNetworkQueue = networkQueue;
  38. mCache = cache;
  39. mDelivery = delivery;
  40. }
  41.  
  42. /**
  43. * Forces this dispatcher to quit immediately. If any requests are still in
  44. * the queue, they are not guaranteed to be processed.
  45. */
  46. public void quit() {
  47. mQuit = true;
  48. interrupt();
  49. }
  50.  
  51. @Override
  52. public void run() {
  53. if (DEBUG) VolleyLog.v("start new dispatcher");
  54. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  55.  
  56. // 缓存初始化,将磁盘中的数据读入内存
  57. mCache.initialize();
  58.  
  59. while (true) {
  60. try {
  61.  
  62. // 堵塞式从队列中取出请求
  63. final Request<?> request = mCacheQueue.take();
  64. request.addMarker("cache-queue-take");
  65.  
  66. // 推断request是否被取消了(调用cancel方法)。假设取消了就不运行,再次到队列中取请求
  67. if (request.isCanceled()) {
  68. request.finish("cache-discard-canceled");
  69. continue;
  70. }
  71.  
  72. // 从缓存中读取数据
  73. Cache.Entry entry = mCache.get(request.getCacheKey());
  74. if (entry == null) {
  75. //没有命中
  76. request.addMarker("cache-miss");
  77. // 没有命中时,就将请求放入网络队列
  78. mNetworkQueue.put(request);
  79. continue;
  80. }
  81.  
  82. // 数据已经过期,将请求放入网络队列
  83. if (entry.isExpired()) {
  84. request.addMarker("cache-hit-expired");
  85. request.setCacheEntry(entry);
  86. mNetworkQueue.put(request);
  87. continue;
  88. }
  89.  
  90. // 本地命中
  91. request.addMarker("cache-hit");
  92. Response<?> response = request.parseNetworkResponse(
  93. new NetworkResponse(entry.data, entry.responseHeaders));
  94. request.addMarker("cache-hit-parsed");
  95.  
  96. if (!entry.refreshNeeded()) {
  97. // Completely unexpired cache hit. Just deliver the response.
  98. //命中,而且不须要刷新
  99. mDelivery.postResponse(request, response);
  100. } else {
  101. //命中,须要刷新,将请求放入网络队列,这里面的代码事实上能够依据需求自己重写
  102. // Soft-expired cache hit. We can deliver the cached response,
  103. // but we need to also send the request to the network for
  104. // refreshing.
  105. request.addMarker("cache-hit-refresh-needed");
  106. request.setCacheEntry(entry);
  107.  
  108. // Mark the response as intermediate.
  109. response.intermediate = true;
  110.  
  111. // Post the intermediate response back to the user and have
  112. // the delivery then forward the request along to the network.
  113. mDelivery.postResponse(request, response, new Runnable() {
  114. @Override
  115. public void run() {
  116. try {
  117. mNetworkQueue.put(request);
  118. } catch (InterruptedException e) {
  119. // Not much we can do about this.
  120. }
  121. }
  122. });
  123. }
  124.  
  125. } catch (InterruptedException e) {
  126. // We may have been interrupted because it was time to quit.
  127. if (mQuit) {
  128. return;
  129. }
  130. continue;
  131. }
  132. }
  133. }
  134. }

(2) NetworkDispatcher.java

  1. public class NetworkDispatcher extends Thread {
  2. /** 网络队列 */
  3. private final BlockingQueue<Request<?
  4.  
  5. >> mQueue;
  6. /** 用于Http请求,依据前面的学习,他事实上使用的是HttpURLConnection或者HttpClient. */
  7. private final Network mNetwork;
  8. /** 本地缓存,网络请求成功后。放入缓存. */
  9. private final Cache mCache;
  10. /** For posting responses and errors. */
  11. private final ResponseDelivery mDelivery;
  12. /** Used for telling us to die. */
  13. private volatile boolean mQuit = false;
  14.  
  15. /**
  16. * Creates a new network dispatcher thread. You must call {@link #start()}
  17. * in order to begin processing.
  18. *
  19. * @param queue Queue of incoming requests for triage
  20. * @param network Network interface to use for performing requests
  21. * @param cache Cache interface to use for writing responses to cache
  22. * @param delivery Delivery interface to use for posting responses
  23. */
  24. public NetworkDispatcher(BlockingQueue<Request<?
  25.  
  26. >> queue,
  27. Network network, Cache cache,
  28. ResponseDelivery delivery) {
  29. mQueue = queue;
  30. mNetwork = network;
  31. mCache = cache;
  32. mDelivery = delivery;
  33. }
  34.  
  35. /**
  36. * Forces this dispatcher to quit immediately. If any requests are still in
  37. * the queue, they are not guaranteed to be processed.
  38. */
  39. public void quit() {
  40. mQuit = true;
  41. interrupt();
  42. }
  43.  
  44. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  45. private void addTrafficStatsTag(Request<?> request) {
  46. // Tag the request (if API >= 14)
  47. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  48. TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
  49. }
  50. }
  51.  
  52. @Override
  53. public void run() {
  54. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  55. Request<?
  56.  
  57. > request;
  58. while (true) {
  59. try {
  60. // 从队列中堵塞式取出一个请求.
  61. request = mQueue.take();
  62. } catch (InterruptedException e) {
  63. // We may have been interrupted because it was time to quit.
  64. if (mQuit) {
  65. return;
  66. }
  67. continue;
  68. }
  69.  
  70. try {
  71. request.addMarker("network-queue-take");
  72.  
  73. // 同理须要推断是否取消,假设取消运行下一个请求
  74. if (request.isCanceled()) {
  75. request.finish("network-discard-cancelled");
  76. continue;
  77. }
  78.  
  79. addTrafficStatsTag(request);
  80.  
  81. // 通过NetWork的perfromRequest方法放回一个NetworkResponse对象
  82. NetworkResponse networkResponse = mNetwork.performRequest(request);
  83. request.addMarker("network-http-complete");
  84.  
  85. // 假设这个返回结果已经发送到了ui线程。就将它finish
  86. if (networkResponse.notModified && request.hasHadResponseDelivered()) {
  87. request.finish("not-modified");
  88. continue;
  89. }
  90.  
  91. // 将NetworkResponse 解析成Response.
  92. Response<?> response = request.parseNetworkResponse(networkResponse);
  93. request.addMarker("network-parse-complete");
  94.  
  95. // 假设须要缓存。那么将结果存入缓存
  96. if (request.shouldCache() && response.cacheEntry != null) {
  97. mCache.put(request.getCacheKey(), response.cacheEntry);
  98. request.addMarker("network-cache-written");
  99. }
  100.  
  101. // 标记为已经发送
  102. request.markDelivered();
  103. //将数据发送到Ui线程
  104. mDelivery.postResponse(request, response);
  105. } catch (VolleyError volleyError) {
  106. parseAndDeliverNetworkError(request, volleyError);
  107. } catch (Exception e) {
  108. VolleyLog.e(e, "Unhandled exception %s", e.toString());
  109. mDelivery.postError(request, new VolleyError(e));
  110. }
  111. }
  112. }
  113.  
  114. private void parseAndDeliverNetworkError(Request<?
  115.  
  116. > request, VolleyError error) {
  117. error = request.parseNetworkError(error);
  118. mDelivery.postError(request, error);
  119. }
  120. }

通过上面的代码,我们来总结一下一个请求的运行过程吧:

1、一个请求就是一个Request对象,首先将Request对象增加到RequestQueue中.

2、推断Request能否够缓存,假设能够。则增加到本地缓存队列,否则增加网络队列

3、本地线程不断监听本地队列是否有请求。假设有请求取出来

4、推断Request是否取消,假设取消,处理下一个请求

5、推断缓存是否命中,假设没有命中。将该请求增加网络队列

6、假设命中,可是过期,相同将该请求增加网络队列

7、假设命中。而且不用刷新,那么直接放回结果。不用增加网络队列

8、假设命中,而且须要刷新。那么放回结果,而且增加网络队列

9、相同4条网络线程也在不断监听网络队列是否有请求,一旦发现有请求,取出请求,推断是否取消,假设取消。那么取出下一个请求

10、假设没有取消,那么通过NetWork进行http请求,将请求结果封装成NetworkResponse,然后转换为Response

11、假设能够缓存,那么将数据写入缓存

12、通过Delivery将Response返回到ui线程

通过以上12步,完毕了一个完整的请求



研究了这么久。我们还没有研究Request和Response是什么呢,假设熟悉http请求的同学相信非常好理解。

Request就是一个http请求。Response就是http返回的内容,先看看Request这个类吧

Request是一个抽象类。我仅仅介绍比較重要的几个方法:

  1. public abstract class Request<T> implements Comparable<Request<T>> {
  2. //Http 请求方法 POST,GET
  3. private final int mMethod;
  4.  
  5. /** 请求URL*/
  6. private final String mUrl;
  7. //用于出错时的回调接口
  8. private final Response.ErrorListener mErrorListener;
  9.  
  10. /** 这个请求在队列中的顺序 */
  11. private Integer mSequence;
  12.  
  13. ...
  14.  
  15. /** 是否可以缓存 */
  16. private boolean mShouldCache = true;
  17.  
  18. /** 是否已经取消了,网络线程和本地线程都会对此推断,假设取消了就不请求了 */
  19. private boolean mCanceled = false;
  20.  
  21. /** 请求策略,比方设置最大重试次数之类的*/
  22. private RetryPolicy mRetryPolicy;
  23.  
  24. /**
  25. * Creates a new request with the given method (one of the values from {@link Method}),
  26. * URL, and error listener. Note that the normal response listener is not provided here as
  27. * delivery of responses is provided by subclasses, who have a better idea of how to deliver
  28. * an already-parsed response.
  29. */
  30. public Request(int method, String url, Response.ErrorListener listener) {
  31. mMethod = method;
  32. mUrl = url;
  33. mErrorListener = listener;
  34. setRetryPolicy(new DefaultRetryPolicy());
  35.  
  36. mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
  37. }
  38.  
  39. /**
  40. * Sets the retry policy for this request.
  41. *
  42. * @return This Request object to allow for chaining.
  43. */
  44. public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
  45. mRetryPolicy = retryPolicy;
  46. return this;
  47. }
  48.  
  49. ...
  50.  
  51. /**
  52. * 通过此方法取消一个请求
  53. */
  54. public void cancel() {
  55. mCanceled = true;
  56. }
  57.  
  58. /**
  59. * 推断是否已经取消.
  60. */
  61. public boolean isCanceled() {
  62. return mCanceled;
  63. }
  64.  
  65. /**
  66. * 获取请求头
  67. * @throws AuthFailureError In the event of auth failure
  68. */
  69. public Map<String, String> getHeaders() throws AuthFailureError {
  70. return Collections.emptyMap();
  71. }
  72.  
  73. /**
  74. * Returns a Map of POST parameters to be used for this request, or null if
  75. * a simple GET should be used. Can throw {@link AuthFailureError} as
  76. * authentication may be required to provide these values.
  77. *
  78. * <p>Note that only one of getPostParams() and getPostBody() can return a non-null
  79. * value.</p>
  80. * @throws AuthFailureError In the event of auth failure
  81. *
  82. * @deprecated Use {@link #getParams()} instead.
  83. */
  84. @Deprecated
  85. protected Map<String, String> getPostParams() throws AuthFailureError {
  86. return getParams();
  87. }
  88.  
  89. /**
  90. * Returns a Map of parameters to be used for a POST or PUT request. Can throw
  91. * {@link AuthFailureError} as authentication may be required to provide these values.
  92. *
  93. * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
  94. *
  95. * @throws AuthFailureError in the event of auth failure
  96. */
  97. protected Map<String, String> getParams() throws AuthFailureError {
  98. return null;
  99. }
  100.  
  101. public String getBodyContentType() {
  102. return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
  103. }
  104.  
  105. /**
  106. * 设置是否能缓存
  107. *
  108. * @return This Request object to allow for chaining.
  109. */
  110. public final Request<?
  111.  
  112. > setShouldCache(boolean shouldCache) {
  113. mShouldCache = shouldCache;
  114. return this;
  115. }
  116.  
  117. /**
  118. * 推断是否可以缓存
  119. */
  120. public final boolean shouldCache() {
  121. return mShouldCache;
  122. }
  123.  
  124. /**
  125. * 这是个抽象方法,我们必须实现,用于将NetworkResponse 转化为Response
  126. * @param response Response from the network
  127. * @return The parsed response, or null in the case of an error
  128. */
  129. abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
  130.  
  131. /**
  132. * 这个我们也必须实现,用于将Response发送到ui线程
  133. * @param response The parsed response returned by
  134. * {@link #parseNetworkResponse(NetworkResponse)}
  135. */
  136. abstract protected void deliverResponse(T response);
  137.  
  138. }
  139.  
  140. 以下继续看看Response这个类:
  141. public class Response<T> {
  142.  
  143. /** 成功的时候回调. */
  144. public interface Listener<T> {
  145. /** Called when a response is received. */
  146. public void onResponse(T response);
  147. }
  148.  
  149. /** 失败的时候回调 */
  150. public interface ErrorListener {
  151. /**
  152. * Callback method that an error has been occurred with the
  153. * provided error code and optional user-readable message.
  154. */
  155. public void onErrorResponse(VolleyError error);
  156. }
  157.  
  158. /** 成功的时候创建一个Response. */
  159. public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
  160. return new Response<T>(result, cacheEntry);
  161. }
  162.  
  163. /**
  164. * 失败的时候创建一个Response
  165. */
  166. public static <T> Response<T> error(VolleyError error) {
  167. return new Response<T>(error);
  168. }
  169.  
  170. /** Parsed response, or null in the case of error. */
  171. public final T result;
  172.  
  173. /**
  174. * Returns whether this response is considered successful.
  175. */
  176. public boolean isSuccess() {
  177. return error == null;
  178. }
  179.  
  180. //私有的,我们无法调用
  181. private Response(T result, Cache.Entry cacheEntry) {
  182. this.result = result;
  183. this.cacheEntry = cacheEntry;
  184. this.error = null;
  185. }
  186.  
  187. private Response(VolleyError error) {
  188. this.result = null;
  189. this.cacheEntry = null;
  190. this.error = error;
  191. }
  192. }

学习了上面两个类后,我们须要知道例如以下知识:

Volley中的不论什么请求都是继承Request的。如Volley提供的StringRequest,JsonArrayRequest,JsonObjectRequest

ImageRequest等等。而且要实现当中的两个方法

abstract protected Response<T> parseNetworkResponse(NetworkResponse response);



abstract protected void deliverResponse(T response);



T是泛型,StringRequest中T表示String,后期我将会简介这几种Request的使用,敬请大家期待。。





最后在介绍一个接口,就是ResponseDelivery.java

它的一个实现类是ExecutorDelivery.java

  1. public class ExecutorDelivery implements ResponseDelivery {
  2. /** 执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每一个任务将怎样执行的机制(包含线程使用的细节、调度等)分离开来的方法。在线程池中经经常使用到 */
  3. private final Executor mResponsePoster;
  4.  
  5. /**
  6. * 传入一个Handler,事实上就是执行在主线的Handler。我想你应该明确为什么他可以从子线程
  7. 将数据传入ui线程了
  8. * @param handler {@link Handler} to post responses on
  9. */
  10. public ExecutorDelivery(final Handler handler) {
  11. // Make an Executor that just wraps the handler.
  12. mResponsePoster = new Executor() {
  13. @Override
  14. public void execute(Runnable command) {
  15. //这里调用了handler的post方法
  16. handler.post(command);
  17. }
  18. };
  19. }
  20.  
  21. /**
  22. * Creates a new response delivery interface, mockable version
  23. * for testing.
  24. * @param executor For running delivery tasks
  25. */
  26. public ExecutorDelivery(Executor executor) {
  27. mResponsePoster = executor;
  28. }
  29.  
  30. @Override
  31. public void postResponse(Request<?> request, Response<?> response) {
  32. postResponse(request, response, null);
  33. }
  34.  
  35. @Override
  36. public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
  37. request.markDelivered();
  38. request.addMarker("post-response");
  39. mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
  40. }
  41.  
  42. /**
  43. * A Runnable used for delivering network responses to a listener on the
  44. * main thread.
  45. */
  46. @SuppressWarnings("rawtypes")
  47. private class ResponseDeliveryRunnable implements Runnable {
  48. private final Request mRequest;
  49. private final Response mResponse;
  50. private final Runnable mRunnable;
  51.  
  52. public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
  53. mRequest = request;
  54. mResponse = response;
  55. mRunnable = runnable;
  56. }
  57.  
  58. @SuppressWarnings("unchecked")
  59. @Override
  60. public void run() {
  61. // If this request has canceled, finish it and don't deliver.
  62. if (mRequest.isCanceled()) {
  63. mRequest.finish("canceled-at-delivery");
  64. return;
  65. }
  66.  
  67. // Deliver a normal response or error, depending.
  68. if (mResponse.isSuccess()) {
  69. //在这里调用了deliverResponse
  70. mRequest.deliverResponse(mResponse.result);
  71. } else {
  72. mRequest.deliverError(mResponse.error);
  73. }
  74.  
  75. // If this is an intermediate response, add a marker, otherwise we're done
  76. // and the request can be finished.
  77. if (mResponse.intermediate) {
  78. mRequest.addMarker("intermediate-response");
  79. } else {
  80. mRequest.finish("done");
  81. }
  82.  
  83. // If we have been provided a post-delivery runnable, run it.
  84. if (mRunnable != null) {
  85. mRunnable.run();
  86. }
  87. }
  88. }
  89. }

好了,今天就写到这里吧,大家有什么不明确的欢迎留言讨论....

Android网络通信Volley框架源代码浅析(二)的更多相关文章

  1. Android网络通信Volley框架源代码浅析(一)

    尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天開始,我打算为大家呈现关于Volley框架的源代码分析的文章,Volley ...

  2. Android网络通信Volley框架源代码浅析(三)

    尊重原创 http://write.blog.csdn.net/postedit/26002961 通过前面浅析(一)和浅析(二)的分析.相信大家对于Volley有了初步的认识,可是假设想更深入的理解 ...

  3. Volley框架源代码分析

    Volley框架分析Github链接 Volley框架分析 Volley源代码解析 为了学习Volley的网络框架,我在AS中将Volley代码又一次撸了一遍,感觉这样的照抄代码也是一种挺好的学习方式 ...

  4. android图片缓存框架Android-Universal-Image-Loader(二)

    http://blog.csdn.net/king_is_everyone/article/details/35595515 这篇打算直接告诉大家怎么用吧,其实这个也不是很难的框架,大致使用过程如下: ...

  5. Android 网络通信框架Volley简介(Google IO 2013)

    1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient( ...

  6. Android 网络通信框架Volley(一)

    转自:http://blog.csdn.net/t12x3456/article/details/9221611 1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫 ...

  7. [转]Android 网络通信框架Volley简介(Google IO 2013)

    Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...

  8. 【转】Android 网络通信框架Volley简介(Google IO 2013)

    Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...

  9. Android 网络通信框架Volley简介

    1.1. Volley引入的背景在以前,我们可能面临如下很多麻烦的问题. 比如以前从网上下载图片的步骤可能是这样的流程: 在ListAdapter#getView()里开始图像的读取. 通过Async ...

随机推荐

  1. 在Eclipse中导入新浪微博SDK

    在Eclipse中导入新浪微博SDK 今天在看<Android开发应用实战>,全书都在讲一个android版的新浪微博客户端怎么做,于是按照书上步骤做.网上有人说这本书没有细节,我想对于小 ...

  2. Struts 2 Overview

    Struts2 is popular and mature web application framework based on the MVC design pattern. Struts2 is ...

  3. 使用mybatis-generator-core自动生成代码

    SSM框架可以使用mybatis-generator-core-1.3.2.jar来自动生成代码,以下是配置和使用的代码. generatorConfig.xml <?xml version=& ...

  4. html-注册邮箱

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. 使用scss + react + webpack + es6实现幻灯片

    写在前面: 刚学习完慕课网里的一个幻灯片案例,自己加了刚学的react,两者结合.首先让大家看看效果 点击此处 你可以先用纯js实现上面的效果:我的github上的 JS代码 或者 观看慕课提供的课程 ...

  6. Jquery~跨域异步上传文件

    先说明白 这个跨域异步上传功能我们借助了Jquery.form插件,它在异步表单方面很有成效,而跨域我们会在HTTP响应头上添加access-control-allow-method,当然这个头标记只 ...

  7. 【LOJ】#2056. 「TJOI / HEOI2016」序列

    题解 这个我们处理出来每一位能变化到的最大值和最小值,包括自身 然后我们发现 \(f[i] = max(f[i],f[j] + 1) (mx[j] <= a[i] && a[j] ...

  8. MVC设计模式一

    一:基础知识 1.mvc model view control 2.模型 是应用程序的主体部分,模型表示业务数据与业务逻辑. 一个模型可以为多个视图提供数据 提高了代码的可重用性 3.视图 用户看到的 ...

  9. mysql (主从复制)(proxy , Amoeba)

    原址如下: http://heylinux.com/archives/1004.html Mysql作为目前世界上使用最广泛的免费数据库,相信所有从事系统运维的工程师都一定接触过.但在实际的生产环境中 ...

  10. C#使用Pechkin与CPechkin生成PDF

    http://blog.sina.com.cn/s/blog_5a52cec70102wpcf.html 1. Pechkin     从NuGet程序管理器中获得Pechkin,代码示例如下:   ...