当前位置: 首页 > news >正文

网站建设与网页设计从入门到精通 素材下载上海seo网站设计

网站建设与网页设计从入门到精通 素材下载,上海seo网站设计,临海企业网站设计,wordpress图片云盘本文采用研究生论文格式编写#xff0c;方便大家阅读。作者#xff1a;谭东 摘要 Picasso是美国SQUARE移动支付公司开源的图片缓存加载库。可以实现图片下载和缓存功能#xff0c;效率和性能都很不错。 Square公司官方博客#xff1a;http://square.github.io/ Square公司… 本文采用研究生论文格式编写方便大家阅读。作者谭东 摘要 Picasso是美国SQUARE移动支付公司开源的图片缓存加载库。可以实现图片下载和缓存功能效率和性能都很不错。 Square公司官方博客http://square.github.io/ Square公司Github地址https://github.com/square Square公司Picasso的Wiki地址http://square.github.io/picasso/ Square公司Picasso的Github地址https://github.com/square/picasso 1 绪论 Picasso的用法是链式调用和Glide用法很像。当然也支持个性化设置是目前比较流行的图片缓存库之一。 1.1 Picasso特点 Picasso是全尺寸下载图片也就是把要加载的图片原图下载缓存下来。Picasso默认的缓存格式为ARGB_888相对于RGB_565内存占用高了一半左右当然图片质量显示要高一些。下面看下Picasso的主要特点 ①链式调用使用简单 ②具有一般图片框架的基础功能 ③方便的图片转换 ④加载过程监听和错误处理 ⑤自动添加磁盘和内存二级缓存 ⑥支持多种数据源加载。 Picasso默认不支持Gif图片加载。Picasso库很小类也很少库仅118KB大小。最新版目前是2.5.2。 2 Picasso基本用法 2.1 引入Picasso Jar包下载https://search.maven.org/remote_content?gcom.squareup.picassoapicassovLATEST Gradle引用 [java]  view plain copy compile com.squareup.picasso:picasso:2.5.2   Maven引用 [java]  view plain copy dependency   groupIdcom.squareup.picasso/groupId   artifactIdpicasso/artifactId   version2.5.2/version   /dependency   2.2 基本加载图片 图2.1 Picasso加载图片 链式调用逻辑上比较清晰。 [java]  view plain copy Picasso.with(context).load(http://i.imgur.com/DvpvklR.png).into(imageView);   改变图片尺寸和填充方式 [java]  view plain copy Picasso.with(context)     .load(url)     .resize(50, 50)     .centerCrop()     .into(imageView)   图片个性化转换例如把图片显示加载成正方形那么你就要自己写具体逻辑。继承Transformation很简单只需要重新按照你的要求去绘制和创建个Bitmap即可 [java]  view plain copy public class CropSquareTransformation implements Transformation {     Override public Bitmap transform(Bitmap source) {       int size  Math.min(source.getWidth(), source.getHeight());       int x  (source.getWidth() - size) / 2;       int y  (source.getHeight() - size) / 2;       Bitmap result  Bitmap.createBitmap(source, x, y, size, size);       if (result ! source) {         source.recycle();       }       return result;     }        Override public String key() { return square(); }   }   加入加载中图片和加载出错图片 [java]  view plain copy Picasso.with(context)       .load(url)       .placeholder(R.drawable.user_placeholder)       .error(R.drawable.user_placeholder_error)       .into(imageView);   针对不同来源的图片加载如文件、网络图片、Assets目录、Resource、Content Provider等 [java]  view plain copy Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);   Picasso.with(context).load(file:///android_asset/DvpvklR.png).into(imageView2);   Picasso.with(context).load(new File(...)).into(imageView3);   图片的Log日志模式和显示图片的缓存来源的标识设置 [java]  view plain copy Picasso picasso  Picasso.with(this);          picasso.setIndicatorsEnabled(true);//蓝色从本地缓存读取的图片红色从网络加载的图片绿色从内存缓存加载的图片          picasso.setLoggingEnabled(true);//日志调试模式   图2.2 Picasso加载图片来源标识 图片缓存颜色格式配置 [java]  view plain copy Picasso.with(context).load(http://square.github.io/picasso/static/sample.png).config(Bitmap.Config.RGB_565)                   .into(imageView);   ARGB_8888要比RGB_565占用内存大一半左右但图像质量差距不大一般。 3 Picasso高级用法 下面的代码是实现 1.自定义Picasso全局线程池  2.自定义Picasso默认缓存目录  3.自定义监听Picasso加载图片进度,结合OKHTTP [java]  view plain copy package com.tandong.picassodemo;      import android.app.Application;      import com.tandong.picassodemo.utils.OkHttp3Downloader;   import com.tandong.picassodemo.utils.ProgressListener;   import com.squareup.picasso.Picasso;      import java.io.IOException;   import java.util.concurrent.PriorityBlockingQueue;   import java.util.concurrent.ThreadPoolExecutor;   import java.util.concurrent.TimeUnit;      import okhttp3.Cache;   import okhttp3.Interceptor;   import okhttp3.OkHttpClient;   import okhttp3.Response;      /**   * Created by Administrator on 2017/4/12.   */      /**   * 1.自定义Picasso全局线程池   * 2.自定义Picasso默认缓存目录   * 3.自定义监听Picasso加载图片进度,结合OKHTTP   */   public class BaseApplication extends Application {       private ThreadPoolExecutor threadPoolExecutor;       private int cpu  0;       private Picasso picasso;       private OkHttpClient okHttpClient;       public static ProgressListener progress;          Override       public void onCreate() {           super.onCreate();           initPicasso();       }          private void initPicasso() {           okHttpClient  new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor() {               Override               public Response intercept(Chain chain) throws IOException {                   Response response  chain.proceed(chain.request());                   return response.newBuilder().body(new ProgressResponseBody(progress, response.body())).build();               }           }).cache(new Cache(getExternalCacheDir(), 10 * 1024 * 1024)).build();           cpu  Runtime.getRuntime().availableProcessors();           threadPoolExecutor  new ThreadPoolExecutor(cpu  1, cpu * 2  1, 1, TimeUnit.MINUTES, new PriorityBlockingQueueRunnable());           picasso  new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//默认3个线程           Picasso.setSingletonInstance(picasso);       }          public static void setProgressListener(ProgressListener progressListener) {           progress  progressListener;       }   }   build.gradle引用 [java]  view plain copy compile com.squareup.picasso:picasso:2.5.2   compile com.squareup.okhttp3:okhttp:3.6.0   Okhttp3下载器Okhttp3Downloader [java]  view plain copy package com.tandong.picassodemo.utils;      import android.content.Context;   import android.net.Uri;   import android.os.StatFs;      import com.squareup.picasso.Downloader;   import com.squareup.picasso.NetworkPolicy;      import java.io.File;   import java.io.IOException;      import okhttp3.Cache;   import okhttp3.CacheControl;   import okhttp3.Call;   import okhttp3.OkHttpClient;   import okhttp3.Request;   import okhttp3.ResponseBody;      /**   * Created by office on 2017/4/10.   */   public final class OkHttp3Downloader implements Downloader {       private static final String PICASSO_CACHE  picasso-cache;       private static final int MIN_DISK_CACHE_SIZE  5 * 1024 * 1024; // 5MB       private static final int MAX_DISK_CACHE_SIZE  50 * 1024 * 1024; // 50MB          private static File defaultCacheDir(Context context) {           File cache  new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);           if (!cache.exists()) {               //noinspection ResultOfMethodCallIgnored               cache.mkdirs();           }           return cache;       }          private static long calculateDiskCacheSize(File dir) {           long size  MIN_DISK_CACHE_SIZE;              try {               StatFs statFs  new StatFs(dir.getAbsolutePath());               long available  ((long) statFs.getBlockCount()) * statFs.getBlockSize();               // Target 2% of the total space.               size  available / 50;           } catch (IllegalArgumentException ignored) {           }              // Bound inside min/max size for disk cache.           return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);       }          /**       * Creates a {link Cache} that would have otherwise been created by calling       * {link #OkHttp3Downloader(Context)}. This allows you to build your own {link OkHttpClient}       * while still getting the default disk cache.       */       public static Cache createDefaultCache(Context context) {           File dir  defaultCacheDir(context);           return new Cache(dir, calculateDiskCacheSize(dir));       }          private static OkHttpClient createOkHttpClient(File cacheDir, long maxSize) {           return new OkHttpClient.Builder()                   .cache(new Cache(cacheDir, maxSize))                   .build();       }          private final Call.Factory client;       private final Cache cache;          /**       * Create new downloader that uses OkHttp. This will install an image cache into your application       * cache directory.       */       public OkHttp3Downloader(Context context) {           this(defaultCacheDir(context));       }          /**       * Create new downloader that uses OkHttp. This will install an image cache into the specified       * directory.       *       * param cacheDir The directory in which the cache should be stored       */       public OkHttp3Downloader(File cacheDir) {           this(cacheDir, calculateDiskCacheSize(cacheDir));       }          /**       * Create new downloader that uses OkHttp. This will install an image cache into your application       * cache directory.       *       * param maxSize The size limit for the cache.       */       public OkHttp3Downloader(final Context context, final long maxSize) {           this(defaultCacheDir(context), maxSize);       }          /**       * Create new downloader that uses OkHttp. This will install an image cache into the specified       * directory.       *       * param cacheDir The directory in which the cache should be stored       * param maxSize  The size limit for the cache.       */       public OkHttp3Downloader(File cacheDir, long maxSize) {           this(createOkHttpClient(cacheDir, maxSize));       }          public OkHttp3Downloader(OkHttpClient client) {           this.client  client;           this.cache  client.cache();       }          public OkHttp3Downloader(Call.Factory client) {           this.client  client;           this.cache  null;       }          Override       public Response load(Uri uri, int networkPolicy) throws IOException {           CacheControl cacheControl  null;           if (networkPolicy ! 0) {               if (NetworkPolicy.isOfflineOnly(networkPolicy)) {                   cacheControl  CacheControl.FORCE_CACHE;               } else {                   CacheControl.Builder builder  new CacheControl.Builder();                   if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {                       builder.noCache();                   }                   if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {                       builder.noStore();                   }                   cacheControl  builder.build();               }           }              Request.Builder builder  new Request.Builder().url(uri.toString());           if (cacheControl ! null) {               builder.cacheControl(cacheControl);           }              okhttp3.Response response  client.newCall(builder.build()).execute();           int responseCode  response.code();           if (responseCode  300) {               response.body().close();               throw new ResponseException(responseCode     response.message(), networkPolicy,                       responseCode);           }              boolean fromCache  response.cacheResponse() ! null;              ResponseBody responseBody  response.body();           return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());       }          Override       public void shutdown() {           if (cache ! null) {               try {                   cache.close();               } catch (IOException ignored) {               }           }       }   }   官方地址 https://github.com/JakeWharton/picasso2-okhttp3-downloader/blob/master/src/main/java/com/jakewharton/picasso/OkHttp3Downloader.java 自定义ResponseBody实现对数据下载进度的监听处理 [java]  view plain copy package com.tandong.picassodemo;      import android.util.Log;      import com.tandong.picassodemo.utils.ProgressListener;      import java.io.IOException;      import okhttp3.MediaType;   import okhttp3.ResponseBody;   import okio.Buffer;   import okio.BufferedSource;   import okio.ForwardingSource;   import okio.Okio;   import okio.Source;      /**   * Created by Administrator on 2017/4/14.   */   public class ProgressResponseBody extends ResponseBody {       private ResponseBody responseBody;       private BufferedSource bufferedSource;       private ProgressListener progressListener;          public ProgressResponseBody(ProgressListener progressListener,ResponseBody responseBody) {           this.responseBody  responseBody;           this.progressListenerprogressListener;       }          Override       public MediaType contentType() {           return responseBody.contentType();       }          Override       public long contentLength() {           return responseBody.contentLength();       }          Override       public BufferedSource source() {           if (bufferedSource  null) {               bufferedSource  Okio.buffer(source(responseBody.source()));           }           return bufferedSource;       }          private Source source(Source source) {           ForwardingSource forwardingSource  new ForwardingSource(source) {               long totalBytesRead  0L;                  Override               public long read(Buffer sink, long byteCount) throws IOException {                   long bytesRead  super.read(sink, byteCount);                   totalBytesRead  bytesRead ! -1 ? bytesRead : 0;                   progressListener.update((int) ((100 * totalBytesRead) / responseBody.contentLength()));                   Log.i(info, 进度  (int) ((100 * totalBytesRead) / responseBody.contentLength()));                   return bytesRead;               }                  Override               public void close() throws IOException {                   super.close();               }           };           return forwardingSource;       }   }   进度接口 [java]  view plain copy package com.tandong.picassodemo.utils;      import android.support.annotation.IntRange;      /**   * Created by Administrator on 2017/4/14.   */   public interface ProgressListener {       public void update(IntRange(from  0, to  100) int progress);   }   自定义带进度的ImageView [java]  view plain copy package com.tandong.picassodemo.view;      import android.content.Context;   import android.graphics.Canvas;   import android.graphics.Color;   import android.graphics.Paint;   import android.graphics.RectF;   import android.support.annotation.IntRange;   import android.support.v4.view.ViewCompat;   import android.util.AttributeSet;   import android.widget.ImageView;      /**   * Created by office on 2017/4/10.   */   public class PicassoImageView extends ImageView {       private final int MAX_PROGRESS  100;       private Paint mArcPaint;       private RectF mBound;       private Paint mCirclePaint;       private int mProgress  0;          public PicassoImageView(Context context) {           this(context, null, 0);       }          public PicassoImageView(Context context, AttributeSet attrs) {           this(context, attrs, 0);       }          public PicassoImageView(Context context, AttributeSet attrs, int defStyleAttr) {           super(context, attrs, defStyleAttr);           init();       }          private void init() {           mArcPaint  new Paint(Paint.ANTI_ALIAS_FLAG);           mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);           mArcPaint.setStrokeWidth(dpToPixel(0.1f, getContext()));           mArcPaint.setColor(Color.argb(120, 0xff, 0xff, 0xff));           mCirclePaint  new Paint(Paint.ANTI_ALIAS_FLAG);           mCirclePaint.setStyle(Paint.Style.STROKE);           mCirclePaint.setStrokeWidth(dpToPixel(2, getContext()));           mCirclePaint.setColor(Color.argb(120, 0xff, 0xff, 0xff));           mBound  new RectF();       }          public void setProgress(IntRange(from  0, to  MAX_PROGRESS) int mProgress) {           this.mProgress  mProgress;           ViewCompat.postInvalidateOnAnimation(this);       }          Override       protected void onSizeChanged(int w, int h, int oldw, int oldh) {           super.onSizeChanged(w, h, oldw, oldh);           int min  Math.min(w, h);           int max  w  h - min;           int r  min / 5;           //set up a square in the imageView           //设置了在图片中间的一个小圆           mBound.set(max / 2 - r, min / 2 - r, max / 2  r, min / 2  r);       }          Override       protected void onDraw(Canvas canvas) {           super.onDraw(canvas);           //画进度条, Paint是画笔详见源码           if (mProgress ! MAX_PROGRESS  mProgress ! 0) {               float mAngle  mProgress * 360f / MAX_PROGRESS;               //画扇型               canvas.drawArc(mBound, 270, mAngle, true, mArcPaint);               //画圆               canvas.drawCircle(mBound.centerX(), mBound.centerY(), mBound.height() / 2, mCirclePaint);           }       }          private static float scale;          public static int dpToPixel(float dp, Context context) {           if (scale  0) {               scale  context.getResources().getDisplayMetrics().density;           }           return (int) (dp * scale);       }   }   Picasso源码原理解析 先看流程图 Picasso的源码很小类也很少相对简单些。 我们主要关注以下标红的几个类。 不是太想写太多的文字那就看源码分析吧。 先看构造单例模式的Piasso实例 [java]  view plain copy /**           * with方式           */           Picasso.with(this).load(http://square.github.io/picasso/static/sample.png).centerCrop().into(imageView);           /**           * new with方式           */           Picasso picasso  Picasso.with(this);           /**           * builder方式           */           picasso  new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//默认3个线程           Picasso.setSingletonInstance(picasso);   这几种方式通过查看都是通过一个方式实现的构造那就是builder方式。 我们按照这句最基本的方式进行源码分析。 [java]  view plain copy Picasso.with(this).load(http://square.github.io/picasso/static/sample.png).centerCrop().into(imageView);   点击with方法进去。 [java]  view plain copy public static Picasso with(Context context) {       if (singleton  null) {         synchronized (Picasso.class) {           if (singleton  null) {             singleton  new Builder(context).build();           }         }       }       return singleton;     }             /** Create the {link Picasso} instance. */       public Picasso build() {         Context context  this.context;         //是否自定义下载器无的话使用默认下载器         if (downloader  null) {           downloader  Utils.createDefaultDownloader(context);         }         //是否自定义缓存方式无的话使用默认LruCache         if (cache  null) {           cache  new LruCache(context);         }         //是否自定义线程池无的话使用默认线程池         if (service  null) {           service  new PicassoExecutorService();         }         //是否自定义转换器无的话使用默认转换器         if (transformer  null) {           transformer  RequestTransformer.IDENTITY;         }         //统计器统计缓存的命中率         Stats stats  new Stats(cache);         //创建Dispather分发调度器         Dispatcher dispatcher  new Dispatcher(context, service, HANDLER, downloader, cache, stats);         //创建Picasso实例         return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,             defaultBitmapConfig, indicatorsEnabled, loggingEnabled);       }     }   可以看出都是通过builder方式实例化的Picasso类。这里实例化了Dispatcher和Picasso。 接下来点击load方法进去 [java]  view plain copy /**    * Start an image request using the specified path. This is a convenience method for calling    * {link #load(Uri)}.    * p    * This path may be a remote URL, file resource (prefixed with {code file:}), content resource    * (prefixed with {code content:}), or android resource (prefixed with {code    * android.resource:}.    * p    * Passing {code null} as a {code path} will not trigger any request but will set a    * placeholder, if one is specified.    *    * see #load(Uri)    * see #load(File)    * see #load(int)    * throws IllegalArgumentException if {code path} is empty or blank string.    */    public RequestCreator load(String path) {      if (path  null) {        return new RequestCreator(this, null, 0);      }      if (path.trim().length()  0) {        throw new IllegalArgumentException(Path must not be empty.);      }      return load(Uri.parse(path));    }      /**    * Start an image request using the specified URI.    * p    * Passing {code null} as a {code uri} will not trigger any request but will set a placeholder,    * if one is specified.    *    * see #load(File)    * see #load(String)    * see #load(int)    */    public RequestCreator load(Uri uri) {      return new RequestCreator(this, uri, 0);    }       RequestCreator(Picasso picasso, Uri uri, int resourceId) {      if (picasso.shutdown) {        throw new IllegalStateException(            Picasso instance already shut down. Cannot submit new requests.);      }      this.picasso  picasso;      this.data  new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);    }   可以看出这里实例化了一个RequestCreator我们看下这个源码类干嘛的。 [java]  view plain copy /** Fluent API for building an image download request. */   SuppressWarnings(UnusedDeclaration) // Public API.   public class RequestCreator {     private static final AtomicInteger nextId  new AtomicInteger();        private final Picasso picasso;     private final Request.Builder data;        private boolean noFade;     private boolean deferred;     private boolean setPlaceholder  true;     private int placeholderResId;     private int errorResId;     private int memoryPolicy;     private int networkPolicy;     private Drawable placeholderDrawable;     private Drawable errorDrawable;     private Object tag;   看下基本的声明就是进行我们个性化配置存储的类。 我们接下来点击centerCrop方法进去 [java]  view plain copy /**   * Crops an image inside of the bounds specified by {link #resize(int, int)} rather than   * distorting the aspect ratio. This cropping technique scales the image so that it fills the   * requested bounds and then crops the extra.   */   public RequestCreator centerCrop() {     data.centerCrop();     return this;   }   这个配置也是操作我们的RequestCreator。 最后点击into方法。 [java]  view plain copy /**   * Asynchronously fulfills the request into the specified {link ImageView}.   * p   * emNote:/em This method keeps a weak reference to the {link ImageView} instance and will   * automatically support object recycling.   */   public void into(ImageView target) {     into(target, null);   }   **    * Asynchronously fulfills the request into the specified {link ImageView} and invokes the    * target {link Callback} if its not {code null}.    * p    * emNote:/em The {link Callback} param is a strong reference and will prevent your    * {link android.app.Activity} or {link android.app.Fragment} from being garbage collected. If    * you use this method, it is bstrongly/b recommended you invoke an adjacent    * {link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.    */   public void into(ImageView target, Callback callback) {     long started  System.nanoTime();     checkMain();        if (target  null) {       throw new IllegalArgumentException(Target must not be null.);     }        if (!data.hasImage()) {//清除取消存在请求       picasso.cancelRequest(target);       if (setPlaceholder) {         setPlaceholder(target, getPlaceholderDrawable());       }       return;     }        if (deferred) {       if (data.hasSize()) {         throw new IllegalStateException(Fit cannot be used with resize.);       }       int width  target.getWidth();       int height  target.getHeight();       if (width  0 || height  0) {         if (setPlaceholder) {           setPlaceholder(target, getPlaceholderDrawable());         }         picasso.defer(target, new DeferredRequestCreator(this, target, callback));         return;       }       data.resize(width, height);     }        Request request  createRequest(started);//时间戳为Key创建Request     String requestKey  createKey(request);        if (shouldReadFromMemoryCache(memoryPolicy)) {//内存缓存检查       Bitmap bitmap  picasso.quickMemoryCacheCheck(requestKey);       if (bitmap ! null) {         picasso.cancelRequest(target);         setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);         if (picasso.loggingEnabled) {           log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), from   MEMORY);         }         if (callback ! null) {           callback.onSuccess();         }         return;       }     }        if (setPlaceholder) {       setPlaceholder(target, getPlaceholderDrawable());     }     //关键创建Action这里用的ImageViewAction     Action action          new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,             errorDrawable, requestKey, tag, callback, noFade);     //把请求封装成Action提交分发     picasso.enqueueAndSubmit(action);   }   [java]  view plain copy void enqueueAndSubmit(Action action) {      Object target  action.getTarget();      if (target ! null  targetToAction.get(target) ! action) {        // This will also check we are on the main thread.        cancelExistingRequest(target);        targetToAction.put(target, action);      }      submit(action);    }   void submit(Action action) {//提交给Dispatcher分发      dispatcher.dispatchSubmit(action);    }   [java]  view plain copy void dispatchSubmit(Action action) {     handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));   }   private static class DispatcherHandler extends Handler {     private final Dispatcher dispatcher;        public DispatcherHandler(Looper looper, Dispatcher dispatcher) {       super(looper);       this.dispatcher  dispatcher;     }        Override public void handleMessage(final Message msg) {       switch (msg.what) {         case REQUEST_SUBMIT: {//提交分发下载请求           Action action  (Action) msg.obj;           dispatcher.performSubmit(action);           break;         }         case REQUEST_CANCEL: {           Action action  (Action) msg.obj;           dispatcher.performCancel(action);           break;         }         case TAG_PAUSE: {           Object tag  msg.obj;           dispatcher.performPauseTag(tag);           break;         }         case TAG_RESUME: {           Object tag  msg.obj;           dispatcher.performResumeTag(tag);           break;         }         case HUNTER_COMPLETE: {           BitmapHunter hunter  (BitmapHunter) msg.obj;           dispatcher.performComplete(hunter);           break;         }         case HUNTER_RETRY: {           BitmapHunter hunter  (BitmapHunter) msg.obj;           dispatcher.performRetry(hunter);           break;         }         case HUNTER_DECODE_FAILED: {           BitmapHunter hunter  (BitmapHunter) msg.obj;           dispatcher.performError(hunter, false);           break;         }         case HUNTER_DELAY_NEXT_BATCH: {           dispatcher.performBatchComplete();           break;         }         case NETWORK_STATE_CHANGE: {           NetworkInfo info  (NetworkInfo) msg.obj;           dispatcher.performNetworkStateChange(info);           break;         }         case AIRPLANE_MODE_CHANGE: {           dispatcher.performAirplaneModeChange(msg.arg1  AIRPLANE_MODE_ON);           break;         }         default:           Picasso.HANDLER.post(new Runnable() {             Override public void run() {               throw new AssertionError(Unknown handler message received:   msg.what);             }           });       }     }   }   [java]  view plain copy   void performSubmit(Action action) {       performSubmit(action, true);     }        void performSubmit(Action action, boolean dismissFailed) {       if (pausedTags.contains(action.getTag())) {         pausedActions.put(action.getTarget(), action);         if (action.getPicasso().loggingEnabled) {           log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),               because tag   action.getTag()   is paused);         }         return;       }      //BitmapHunter核心它是个线程执行图片下载编解码       BitmapHunter hunter  hunterMap.get(action.getKey());       if (hunter ! null) {         hunter.attach(action);         return;       }          if (service.isShutdown()) {         if (action.getPicasso().loggingEnabled) {           log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), because shut down);         }         return;       }          hunter  forRequest(action.getPicasso(), this, cache, stats, action);       hunter.future  service.submit(hunter);//这个service是ExecutorService线程池提交执行下载线程       hunterMap.put(action.getKey(), hunter);       if (dismissFailed) {         failedActions.remove(action.getTarget());       }          if (action.getPicasso().loggingEnabled) {         log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());       }     }   大概看下BitmapHunter源码 [java]  view plain copy /*   * Copyright (C) 2013 Square, Inc.   *   * Licensed under the Apache License, Version 2.0 (the License);   * you may not use this file except in compliance with the License.   * You may obtain a copy of the License at   *   *      http://www.apache.org/licenses/LICENSE-2.0   *   * Unless required by applicable law or agreed to in writing, software   * distributed under the License is distributed on an AS IS BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   * See the License for the specific language governing permissions and   * limitations under the License.   */   package com.squareup.picasso;      import android.graphics.Bitmap;   import android.graphics.BitmapFactory;   import android.graphics.Matrix;   import android.net.NetworkInfo;   import java.io.IOException;   import java.io.InputStream;   import java.io.PrintWriter;   import java.io.StringWriter;   import java.util.ArrayList;   import java.util.List;   import java.util.concurrent.Future;   import java.util.concurrent.atomic.AtomicInteger;      import static com.squareup.picasso.MemoryPolicy.shouldReadFromMemoryCache;   import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;   import static com.squareup.picasso.Picasso.Priority;   import static com.squareup.picasso.Picasso.Priority.LOW;   import static com.squareup.picasso.Utils.OWNER_HUNTER;   import static com.squareup.picasso.Utils.VERB_DECODED;   import static com.squareup.picasso.Utils.VERB_EXECUTING;   import static com.squareup.picasso.Utils.VERB_JOINED;   import static com.squareup.picasso.Utils.VERB_REMOVED;   import static com.squareup.picasso.Utils.VERB_TRANSFORMED;   import static com.squareup.picasso.Utils.getLogIdsForHunter;   import static com.squareup.picasso.Utils.log;      class BitmapHunter implements Runnable {     /**     * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since     * this will only ever happen in background threads we help avoid excessive memory thrashing as     * well as potential OOMs. Shamelessly stolen from Volley.     */     private static final Object DECODE_LOCK  new Object();        private static final ThreadLocalStringBuilder NAME_BUILDER  new ThreadLocalStringBuilder() {       Override protected StringBuilder initialValue() {         return new StringBuilder(Utils.THREAD_PREFIX);       }     };        private static final AtomicInteger SEQUENCE_GENERATOR  new AtomicInteger();        private static final RequestHandler ERRORING_HANDLER  new RequestHandler() {       Override public boolean canHandleRequest(Request data) {         return true;       }          Override public Result load(Request request, int networkPolicy) throws IOException {         throw new IllegalStateException(Unrecognized type of request:   request);       }     };        final int sequence;     final Picasso picasso;     final Dispatcher dispatcher;     final Cache cache;     final Stats stats;     final String key;     final Request data;     final int memoryPolicy;     int networkPolicy;     final RequestHandler requestHandler;        Action action;     ListAction actions;     Bitmap result;     Future? future;     Picasso.LoadedFrom loadedFrom;     Exception exception;     int exifRotation; // Determined during decoding of original resource.     int retryCount;     Priority priority;        BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,         RequestHandler requestHandler) {       this.sequence  SEQUENCE_GENERATOR.incrementAndGet();       this.picasso  picasso;       this.dispatcher  dispatcher;       this.cache  cache;       this.stats  stats;       this.action  action;       this.key  action.getKey();       this.data  action.getRequest();       this.priority  action.getPriority();       this.memoryPolicy  action.getMemoryPolicy();       this.networkPolicy  action.getNetworkPolicy();       this.requestHandler  requestHandler;       this.retryCount  requestHandler.getRetryCount();     }   它是实现了Runable接口的线程再继续看Run方法。 [java]  view plain copy Override public void run() {     try {       updateThreadName(data);          if (picasso.loggingEnabled) {         log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));       }       //这个result是Bitmap调用了hunt方法获取       result  hunt();          if (result  null) {//分发图片加载失败         dispatcher.dispatchFailed(this);       } else {//分发图片加载完成         dispatcher.dispatchComplete(this);       }     } catch (Downloader.ResponseException e) {       if (!e.localCacheOnly || e.responseCode ! 504) {         exception  e;       }       dispatcher.dispatchFailed(this);     } catch (NetworkRequestHandler.ContentLengthException e) {       exception  e;       dispatcher.dispatchRetry(this);     } catch (IOException e) {       exception  e;       dispatcher.dispatchRetry(this);     } catch (OutOfMemoryError e) {       StringWriter writer  new StringWriter();       stats.createSnapshot().dump(new PrintWriter(writer));       exception  new RuntimeException(writer.toString(), e);       dispatcher.dispatchFailed(this);     } catch (Exception e) {       exception  e;       dispatcher.dispatchFailed(this);     } finally {       Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);     }   }   再继续看hunt方法 [java]  view plain copy Bitmap hunt() throws IOException {      Bitmap bitmap  null;      //内存缓存检查检查了很多次内存缓存了      if (shouldReadFromMemoryCache(memoryPolicy)) {        bitmap  cache.get(key);        if (bitmap ! null) {          stats.dispatchCacheHit();          loadedFrom  MEMORY;          if (picasso.loggingEnabled) {            log(OWNER_HUNTER, VERB_DECODED, data.logId(), from cache);          }          return bitmap;        }      }         data.networkPolicy  retryCount  0 ? NetworkPolicy.OFFLINE.index : networkPolicy;      RequestHandler.Result result  requestHandler.load(data, networkPolicy);      if (result ! null) {        loadedFrom  result.getLoadedFrom();        exifRotation  result.getExifOrientation();           bitmap  result.getBitmap();           // If there was no Bitmap then we need to decode it from the stream.        if (bitmap  null) {          InputStream is  result.getStream();          try {//inputstream解码            bitmap  decodeStream(is, data);          } finally {            Utils.closeQuietly(is);          }        }      }         if (bitmap ! null) {        if (picasso.loggingEnabled) {          log(OWNER_HUNTER, VERB_DECODED, data.logId());        }        stats.dispatchBitmapDecoded(bitmap);//解码命中率统计        if (data.needsTransformation() || exifRotation ! 0) {          synchronized (DECODE_LOCK) {            if (data.needsMatrixTransform() || exifRotation ! 0) {              bitmap  transformResult(data, bitmap, exifRotation);              if (picasso.loggingEnabled) {                log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());              }            }            if (data.hasCustomTransformations()) {              bitmap  applyCustomTransformations(data.transformations, bitmap);              if (picasso.loggingEnabled) {                log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), from custom transformations);              }            }          }          if (bitmap ! null) {            stats.dispatchBitmapTransformed(bitmap);          }        }      }         return bitmap;    }   看decodeStream方法里的逻辑 [java]  view plain copy /**   * Decode a byte stream into a Bitmap. This method will take into account additional information   * about the supplied request in order to do the decoding efficiently (such as through leveraging   * {code inSampleSize}).   */   static Bitmap decodeStream(InputStream stream, Request request) throws IOException {     MarkableInputStream markStream  new MarkableInputStream(stream);     stream  markStream;        long mark  markStream.savePosition(65536); // TODO fix this crap.        final BitmapFactory.Options options  RequestHandler.createBitmapOptions(request);     final boolean calculateSize  RequestHandler.requiresInSampleSize(options);        boolean isWebPFile  Utils.isWebPFile(stream);     markStream.reset(mark);     // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.     // Decode byte array instead     if (isWebPFile) {       byte[] bytes  Utils.toByteArray(stream);       if (calculateSize) {         BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);         RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,             request);       }       return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);     } else {       if (calculateSize) {         BitmapFactory.decodeStream(stream, null, options);         RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,             request);            markStream.reset(mark);       }       Bitmap bitmap  BitmapFactory.decodeStream(stream, null, options);       if (bitmap  null) {         // Treat null as an IO exception, we will eventually retry.         throw new IOException(Failed to decode stream.);       }       return bitmap;     }   }   就是对inputSteam转为Bimap操作。 接下来看dispatcher派发加载成功消息的逻辑 [java]  view plain copy void dispatchComplete(BitmapHunter hunter) {     handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));   }   [java]  view plain copy case HUNTER_COMPLETE: {//加载完成            BitmapHunter hunter  (BitmapHunter) msg.obj;            dispatcher.performComplete(hunter);            break;          }   [java]  view plain copy void performComplete(BitmapHunter hunter) {     if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {       cache.set(hunter.getKey(), hunter.getResult());     }     hunterMap.remove(hunter.getKey());     batch(hunter);     if (hunter.getPicasso().loggingEnabled) {       log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), for completion);     }   }   private void batch(BitmapHunter hunter) {     if (hunter.isCancelled()) {       return;     }     batch.add(hunter);     if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {       handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);     }   }   [java]  view plain copy case HUNTER_DELAY_NEXT_BATCH: {//继续派发next_batch消息            dispatcher.performBatchComplete();            break;          }   [java]  view plain copy void performBatchComplete() {      ListBitmapHunter copy  new ArrayListBitmapHunter(batch);      batch.clear();      mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));//注意这里的mainThreadHandler是Picasso的Handler      logBatch(copy);    }   注意这里的mainThreadHander是Picasso里的Handler我们可以看下Dispatcher的实例化。 [java]  view plain copy Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,        Downloader downloader, Cache cache, Stats stats) {      this.dispatcherThread  new DispatcherThread();      this.dispatcherThread.start();      Utils.flushStackLocalLeaks(dispatcherThread.getLooper());      this.context  context;      this.service  service;      this.hunterMap  new LinkedHashMapString, BitmapHunter();      this.failedActions  new WeakHashMapObject, Action();      this.pausedActions  new WeakHashMapObject, Action();      this.pausedTags  new HashSetObject();      this.handler  new DispatcherHandler(dispatcherThread.getLooper(), this);      this.downloader  downloader;      this.mainThreadHandler  mainThreadHandler;//这个就是Picasso传进来的mainThreadHandler      this.cache  cache;      this.stats  stats;      this.batch  new ArrayListBitmapHunter(4);      this.airplaneMode  Utils.isAirplaneModeOn(this.context);      this.scansNetworkChanges  hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);      this.receiver  new NetworkBroadcastReceiver(this);      receiver.register();    }   那我们看下Picasso里的HUNTER_BATCH_COMPLETE消息处理 [java]  view plain copy static final String TAG  Picasso;    static final Handler HANDLER  new Handler(Looper.getMainLooper()) {      Override public void handleMessage(Message msg) {        switch (msg.what) {          case HUNTER_BATCH_COMPLETE: {            SuppressWarnings(unchecked) ListBitmapHunter batch  (ListBitmapHunter) msg.obj;            //noinspection ForLoopReplaceableByForEach            for (int i  0, n  batch.size(); i  n; i) {              BitmapHunter hunter  batch.get(i);              hunter.picasso.complete(hunter);//回调了complete方法            }            break;          }          case REQUEST_GCED: {            Action action  (Action) msg.obj;            if (action.getPicasso().loggingEnabled) {              log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), target got garbage collected);            }            action.picasso.cancelExistingRequest(action.getTarget());            break;          }          case REQUEST_BATCH_RESUME:            SuppressWarnings(unchecked) ListAction batch  (ListAction) msg.obj;            //noinspection ForLoopReplaceableByForEach            for (int i  0, n  batch.size(); i  n; i) {              Action action  batch.get(i);              action.picasso.resumeAction(action);            }            break;          default:            throw new AssertionError(Unknown handler message received:   msg.what);        }      }    };   我们看下Picasso的complete方法 [java]  view plain copy void complete(BitmapHunter hunter) {      Action single  hunter.getAction();//拿到了派发过来的Action      ListAction joined  hunter.getActions();         boolean hasMultiple  joined ! null  !joined.isEmpty();      boolean shouldDeliver  single ! null || hasMultiple;         if (!shouldDeliver) {        return;      }         Uri uri  hunter.getData().uri;      Exception exception  hunter.getException();      Bitmap result  hunter.getResult();//拿到了Bitmap      LoadedFrom from  hunter.getLoadedFrom();         if (single ! null) {        deliverAction(result, from, single);//执行deliverAction方法      }         if (hasMultiple) {        //noinspection ForLoopReplaceableByForEach        for (int i  0, n  joined.size(); i  n; i) {          Action join  joined.get(i);          deliverAction(result, from, join);        }      }         if (listener ! null  exception ! null) {        listener.onImageLoadFailed(this, uri, exception);      }    }   我们看下deliverAction方法 [java]  view plain copy private void deliverAction(Bitmap result, LoadedFrom from, Action action) {      if (action.isCancelled()) {        return;      }      if (!action.willReplay()) {        targetToAction.remove(action.getTarget());      }      if (result ! null) {        if (from  null) {          throw new AssertionError(LoadedFrom cannot be null.);        }        action.complete(result, from);//回调了action的complete方法        if (loggingEnabled) {          log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), from   from);        }      } else {        action.error();        if (loggingEnabled) {          log(OWNER_MAIN, VERB_ERRORED, action.request.logId());        }      }    }   这里注意下我们之前用的是Action里的ImageViewAction类我们看下ImageViewAction源码和它的complete类里的处理逻辑 [java]  view plain copy /*   * Copyright (C) 2013 Square, Inc.   *   * Licensed under the Apache License, Version 2.0 (the License);   * you may not use this file except in compliance with the License.   * You may obtain a copy of the License at   *   *      http://www.apache.org/licenses/LICENSE-2.0   *   * Unless required by applicable law or agreed to in writing, software   * distributed under the License is distributed on an AS IS BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   * See the License for the specific language governing permissions and   * limitations under the License.   */   package com.squareup.picasso;      import android.content.Context;   import android.graphics.Bitmap;   import android.graphics.drawable.Drawable;   import android.widget.ImageView;      class ImageViewAction extends ActionImageView {        Callback callback;        ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,         int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,         Callback callback, boolean noFade) {       super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,           tag, noFade);       this.callback  callback;     }     //图片加载完成回调     Override public void complete(Bitmap result, Picasso.LoadedFrom from) {       if (result  null) {         throw new AssertionError(             String.format(Attempted to complete action with no result!\n%s, this));       }          ImageView target  this.target.get();       if (target  null) {         return;       }          Context context  picasso.context;       boolean indicatorsEnabled  picasso.indicatorsEnabled;       PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);//设置bitmap给PicassoDrawable          if (callback ! null) {         callback.onSuccess();       }     }        Override public void error() {       ImageView target  this.target.get();       if (target  null) {         return;       }       if (errorResId ! 0) {         target.setImageResource(errorResId);       } else if (errorDrawable ! null) {         target.setImageDrawable(errorDrawable);       }          if (callback ! null) {         callback.onError();       }     }        Override void cancel() {       super.cancel();       if (callback ! null) {         callback  null;       }     }   }   我们简单看下PicassoDrawable源码和它的setBitmap方法 [java]  view plain copy /*   * Copyright (C) 2013 Square, Inc.   *   * Licensed under the Apache License, Version 2.0 (the License);   * you may not use this file except in compliance with the License.   * You may obtain a copy of the License at   *   *      http://www.apache.org/licenses/LICENSE-2.0   *   * Unless required by applicable law or agreed to in writing, software   * distributed under the License is distributed on an AS IS BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   * See the License for the specific language governing permissions and   * limitations under the License.   */   package com.squareup.picasso;      import android.content.Context;   import android.graphics.Bitmap;   import android.graphics.Canvas;   import android.graphics.ColorFilter;   import android.graphics.Paint;   import android.graphics.Path;   import android.graphics.Point;   import android.graphics.Rect;   import android.graphics.drawable.AnimationDrawable;   import android.graphics.drawable.BitmapDrawable;   import android.graphics.drawable.Drawable;   import android.os.Build;   import android.os.SystemClock;   import android.widget.ImageView;      import static android.graphics.Color.WHITE;   import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;      final class PicassoDrawable extends BitmapDrawable {//继承自BitmapDrawable     // Only accessed from main thread.     private static final Paint DEBUG_PAINT  new Paint();     private static final float FADE_DURATION  200f; //ms        /**     * Create or update the drawable on the target {link ImageView} to display the supplied bitmap     * image.     */     static void setBitmap(ImageView target, Context context, Bitmap bitmap,         Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {       Drawable placeholder  target.getDrawable();       if (placeholder instanceof AnimationDrawable) {         ((AnimationDrawable) placeholder).stop();       }       PicassoDrawable drawable            new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);       target.setImageDrawable(drawable);//调用ImageView的setImageDrawable方法将转换后的drawable加载进去显示     }        /**     * Create or update the drawable on the target {link ImageView} to display the supplied     * placeholder image.     */     static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {       target.setImageDrawable(placeholderDrawable);       if (target.getDrawable() instanceof AnimationDrawable) {         ((AnimationDrawable) target.getDrawable()).start();       }     }        private final boolean debugging;     private final float density;     private final Picasso.LoadedFrom loadedFrom;        Drawable placeholder;        long startTimeMillis;     boolean animating;     int alpha  0xFF;        PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder,         Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {       super(context.getResources(), bitmap);          this.debugging  debugging;       this.density  context.getResources().getDisplayMetrics().density;          this.loadedFrom  loadedFrom;          boolean fade  loadedFrom ! MEMORY  !noFade;       if (fade) {         this.placeholder  placeholder;         animating  true;         startTimeMillis  SystemClock.uptimeMillis();       }     }        Override public void draw(Canvas canvas) {       if (!animating) {         super.draw(canvas);       } else {         float normalized  (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;         if (normalized  1f) {           animating  false;           placeholder  null;           super.draw(canvas);         } else {           if (placeholder ! null) {             placeholder.draw(canvas);           }              int partialAlpha  (int) (alpha * normalized);           super.setAlpha(partialAlpha);           super.draw(canvas);           super.setAlpha(alpha);           if (Build.VERSION.SDK_INT  Build.VERSION_CODES.GINGERBREAD_MR1) {             invalidateSelf();           }         }       }          if (debugging) {         drawDebugIndicator(canvas);       }     }        Override public void setAlpha(int alpha) {       this.alpha  alpha;       if (placeholder ! null) {         placeholder.setAlpha(alpha);       }       super.setAlpha(alpha);     }        Override public void setColorFilter(ColorFilter cf) {       if (placeholder ! null) {         placeholder.setColorFilter(cf);       }       super.setColorFilter(cf);     }        Override protected void onBoundsChange(Rect bounds) {       if (placeholder ! null) {         placeholder.setBounds(bounds);       }       super.onBoundsChange(bounds);     }        private void drawDebugIndicator(Canvas canvas) {       DEBUG_PAINT.setColor(WHITE);       Path path  getTrianglePath(new Point(0, 0), (int) (16 * density));       canvas.drawPath(path, DEBUG_PAINT);          DEBUG_PAINT.setColor(loadedFrom.debugColor);       path  getTrianglePath(new Point(0, 0), (int) (15 * density));       canvas.drawPath(path, DEBUG_PAINT);     }        private static Path getTrianglePath(Point p1, int width) {       Point p2  new Point(p1.x  width, p1.y);       Point p3  new Point(p1.x, p1.y  width);          Path path  new Path();       path.moveTo(p1.x, p1.y);       path.lineTo(p2.x, p2.y);       path.lineTo(p3.x, p3.y);          return path;     }   }   这样整个Picasso就实现了我们的图片请求加载显示。 如果你觉得本文太长可以单独查看Picasso源码原理分析一文 http://blog.csdn.net/jay100500/article/details/71055229 参考文献 [1]Picasso官方简易Wiki.Picasso[OL].2013.http://square.github.io/picasso/ 独创性声明 本人声明所呈交的学术研究文章是本人自己和在他人指导下进行的研究工作及取得的研究成果。除了文中特别加以标注和致谢的地方外文章中不包含他人已经发表或撰写过的研究成果也不包含为获得其他教育机构的学位或证书而使用过的材料。与我一同工作的同志对本研究所做的任何贡献均已在论文中作了明确的说明。 学术文章作者签名谭东              签字日期 2017年 4月 25日 学术文章版权使用授权书 本学术文章作者完全了解有关保留、使用学术文章的规定有权保留并向国家有关部门或机构送交文章的复印件和磁盘允许文章被查阅和借阅。本人授权后可以将学术文章的全部或部分内容编入有关数据库进行检索可以采用影印、缩印或扫描等复制手段保存、汇编学术文章。 遵循BY-SA 署名-相同方式共享 4.0协议
http://www.hkea.cn/news/14501582/

相关文章:

  • 怎样建立自己网站视频四川城乡建设网网站
  • 常州的网站建设哪个网站可以做魔方图片
  • 教育培训 营销型网站系统太原建站模板
  • 南充公司网站建设网上哪里可以免费打广告
  • 黄页推广网站下载app营销策略模板
  • 杭州大型网站建设计算机网络实验 做网站的
  • 微信公众号商城网站开发网站建设和考核工作通知
  • 新乡哪里有做网站的建立网站时间
  • 做儿童网站事业单位网站建设算固定资产吗
  • 有道网站提交入口西安网站设计
  • 泉州网站建设定制网络销售怎么聊客户
  • iis网站伪静态手机上的免费销售网站建设
  • 微商如何引流与推广seo站内优化
  • 深圳建站公司招聘企业网站建设申请怎么写
  • 购物商城类网站备案WordPress页面模板功能不见了
  • 东胜区教育网站入口网站怎么做vga头
  • 邯郸网站seo平台推广方案模板
  • 关注建设银行网站wordpress加密修改密码
  • zedu小语种网站建设wordpress 开发指南
  • mysql做wp网站wordpress改变访问目录结构
  • 小网站搜什么关键词好wordpress 招聘网站
  • 濮阳门户网站开发搜索推广策略制定
  • 织梦做仿站时 为何会发生本地地址跳转网站地址网站建设专业性的评价
  • 建一个网站需要多少时间表产品定制网站开发
  • 做网站放广告赚钱手机在线做ppt模板下载网站有哪些
  • 什么网站用php做的flash网站源码带后台
  • 网站制作案例招远网站建设公司报价
  • 手游网站开发企业官网营销推广
  • 简单大气网站欣赏黄骅贴吧最新消息金鼎18号
  • 阜阳学网站建设室内设计素材网站哪个最好