文档章节

Android 常用开源框架源码解析 系列 (四)Glide

o
 osc_odyg6b92
发布于 2018/07/13 10:17
字数 8383
阅读 10
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

一、定义 
 
    Glide 一个被google所推荐的图片加载库,作者是bumptech。对Android SDk 最低要求是 API 10 
    与之功能类似的是Square公司的picasso 
 
二、基本概念
 
    Model :数据来源 :Uri、本地文件、资源ID
    Data :加工数据
    Resource :对原始数据进行解码,解码之后的资源 resource
    Resource decode :资源解码器
    TransformedResource:转换资源
    TranscodedResource:转码,将静态、Gif动态图进行格式转换以便能加载
    Target :目标图片
    
三、整体流程
 
A:Model图片数据源 ———ModelLoader加载—>原始数据Data——Decoder解码——>
    Resource——Transform裁剪——>TransformResource——Transcode转码——>TranscodeResource——封装——>Target
 
四、源码
    引入 :compile 'com.github.bumptech.glide:glide:3.7.0'
    
    4.1、使用流程三步曲: Glide
                        .with(“上下文context”)
                        .load(“url”)
                        .into(“显示的控件资源");
 
    4.2、常用加载图片的配置参数:
public void LoadImage(View view) {
            //with 创建一个加载图片的Glide实例,流式接口
    Glide.with(getApplicationContext()) //指定的Context,传入的参数会决定整个Glide的生命周期
        ps:图片的加载会和传入的Acitivty或是Fragment保持一致,所以建议使用Activity 或是Fragment作为参数而不是单纯的使用this 或是 context
    
            .load("url")//指定的图片的URL
 
            加载占位图:-int or Drawable 
           .placeholder(R.mipmap.ic_launcher) //指定图片未成功加载前现实的图片占位符
            // ,直到加载的网络图片显示就会被替换,仅支持本地图片资源
             错误占位图:-int or Drawable 
           .error(R.mipmap.ic_launcher)  //指定图片加载失败显示的图片占位图
            
            加载本地缩略图:
           .thumbnail( float ) //0.2f Glide会显示原始图片的20%大小,注意ImageView的ScaleType值的设置
            加载网络缩略图:
           DrawableRequestBuilder<String> thumbnailRequest = Glide.with(context).load(url);            
            Glide.with( context) .load(url) 
                .thumbnail (thumbnailRequest ).into (imageView);
 
            加载图片动画效果:
                .crossFade() or crossfade(int duration)  //强制开启GLide默认的图片淡出淡入效果,默认持续时间300ms,设置dontAnimate()设置无任何淡出淡入效果
                
            显示Gif 和Video 功能:
        String gifUrl = “…xxxxoo.git”;
            .load (gifUrl) 
            .asGif() 
            .error(xxxx)
            // 如果图片类型不是Gif的话 就会当作load 失败来处理
            
            显示静态的Gif图片 //仅仅显示Gif的第一桢图像
        String gifUrl = “…xxxxoo.git”;
            .load (gifUrl) 
            .asBitmap() 
            .error(xxxx)
        
           显示手机本地视频文件:
            String filePath = “/storage/emulated/0/xxxx.mp4"
            .load(Uri.fromFile ( new File (filePath) ))
            
           .override(300, 300)  //不自动适配图片尺寸的时候进行手动进行图片尺寸设置 ,glide可以自动限制图片的尺寸来优化图片的加载
            //ps:Glide不会完整的加载原始图片资源到内存当中,会自动判断imageView大小再进行最优尺寸选择加载
 
           .fitCenter()  //指定图片缩放类型1,显示的图像宽和高都小于等于ImageView的边界范围
            //ps:缺陷 有可能不被填满所需要的ImageView, FIT_CENTER
 
           .centerCrop()  //指定图片缩放类型2。显示的图像填充整个ImageView,然后裁剪额外超出的部分
            //ps:缺陷,有可能完整填充ImageView但是图片不能完整显示,CENTER_CROP
 
           .skipMemoryCache(true)  //不将该图片放在内存缓存中,但是仍然会利用该部分磁盘缓存,依然会在磁盘缓存中建立区域,Glide默认将图片放入内存缓存中
            //ps: Glide默认会将图片缓存到内存缓存中所以无需传入false值,若同一Url地址在首次没有调用该方法则第二次直接从内存中缓存缓存图片,若想调整行为需要保证每次调用行为一致
 
            //硬盘缓存策略-枚举值:默认开启
           .diskCacheStrategy(DiskCacheStrategy.NONE) //禁用硬盘的缓存 让其处于null状态,跳过磁盘缓存
            .diskCacheStrategy(DiskCacheStrategy.SOURCE) //仅仅只缓存原来的全分辨率尺寸图像
            .diskCacheStrategy(DiskCacheStrategy.RESULT) //仅仅只缓存资源最终的加载图像(降低压缩分辨率后的图片)
            .diskCacheStrategy(DiskCacheStrategy.ALL)   //缓存所有版本的图片
 
           .priority(Priority.HIGH)//优先级  先处理优先级高的图片信息 ,但不保证所有图片按照优先级顺序进行加载
 
            .into(imageview); //显示到指定的ImageView
}
 
   4.3、with()解析: 基础目的:获取RequestManager对象(管理Request请求)
 
    根据当前的上下文和组建选择不同的with构造方法,不同的参数对于图片加载的生命周期产生不同的影响,将图片加载的生命周期和组件相挂钩。
以context 为例:
public static RequestManager with(Context context) {
    //用于产生requestManager 图片请求管理 
   RequestManagerRetriever 生产requestManager
    RequestManagerRetrieverretriever = RequestManagerRetriever.get();
    return retriever.get(context);
}
//通过RequestManagerRetriever 这个生产Request类来处理,同时Glide绑定了组件的生命周期
 
RequestManager的创建流程:
public RequestManager get(Context context) {
    if (context == null) {
        throw new IllegalArgumentException("You cannot start a load on a null Context");
    } 
    //当前Context 是否是在主线程,context是否是application的实例
else if (Util.isOnMainThread() && !(context instanceof Application)) {
        if (context instanceof FragmentActivity) {
            return get((FragmentActivity) context);
        } else if (context instanceof Activity) {
            return get((Activity) context);
        } else if (context instanceof ContextWrapper) {
            return get(((ContextWrapper) context).getBaseContext());
        }
    }
    //返回一个单例模式的ApplicationManager
    return getApplicationManager(context);
}
    //双重锁检查机制的单例模型
获取唯一个RequestManager,进行图片请求的处理
private RequestManager getApplicationManager(Context context) {
    if (applicationManager == null) {
        synchronized (this) {
            if (applicationManager == null) {
                applicationManager = new RequestManager(context.getApplicationContext(),
                        new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
            }
        }
    }
    return applicationManager;
}
   RequestManagerRetriever的 get()解析:
a、传入application 类型的Context
当Glide传入的是整个程序的生命周期的时候,就不需要进行过多处理
 
b、传入非application 类型的Context (activity、fragment)
public RequestManager get(FragmentActivity activity) {
   //是否是在后台线程,只有在非UI线程才进else
    if (Util.isOnBackgroundThread()) {
        return get(activity.getApplicationContext());
    } else {
        assertNotDestroyed(activity);
        FragmentManager fm = activity.getSupportFragmentManager();
        return supportFragmentGet(activity, fm);
    }
}
supportFragmentGet():
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
   //Fragment 添加到Activity有两种方式,A有Ui的Fragment B没有Ui界面的Fragment
    //这里使用第二种方式,通过RequestManagerFragment来监听Activity的生命周期来完成绑定生命周期,图片加载选择的过程
        ps:Glide无法直接监听Activity的生命周期
 SupportRequestManagerFragment current 
                                            =     getSupportRequestManagerFragment(fm);
    //创建RequestManager实例,完成Glide对象的构造,通过RequestManager就可以控制整个界面的生命周期的监听,通过监听进行图片的相应操作
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
        requestManager = new RequestManager(context, 
                    current.getLifecycle(), current.getRequestManagerTreeNode());
        current.setRequestManager(requestManager);
    }
    return requestManager;
}
 
setRequestManager():将RequestManagerFragment这个空界面的Fragment和RequestManager进行绑定;
    目的:监听Activity生命周期,管理图片加载的整个流程 绑定到Activity一起操作
public void setRequestManager(RequestManager requestManager) {
    this.requestManager = requestManager;
}
ps:一个RequestManager 对应 一个RequestManagerFragment,一一对应关系
 
    思考:前文提到的 SupportRequestManagerFragment 空的Fragment 是如何和RequestManager建立生命周期关系的呢?
 
    答:在RequestManagerFragment中含有ActivityFragmentLifecycle函数,用于管理组建生命周期的。RequestManager实际上在RequestManagerFragment中注册了一些回调接口,通过接口监听Activity 或是Fragment的生命周期
例如:
@Override
public void onStart() {
    super.onStart();
    lifecycle.onStart();
//说明RequestManagerFragment 这个无Ui的Fragment是通过LifeCycle进行生命周期的管理
}
根据不同的生命周期进行相应的处理
 
  4.4、load()解析: 初始化操作 获取DrawableTypeRequest对象
 
    思考:为什么Glide会有一大堆重载方法?
    答:因为Glide支持多种格式的图片来源,依托于此Glide加载也就需要不同的类型
 
public DrawableTypeRequest<String> load(String string) {
    return (DrawableTypeRequest<String>)
                    fromString().load(string);
}
DrawableTypeRequest() :表示Glide中所有加载图片的Request请求
 
 
class DrawableTypeRequest<ModelType> extends     DrawableRequestBuilder<ModelType> implements DownloadOptions
   能想象到DrawableTypeRequest 应该就是通过Builder()内部类的构建者模式进行构建初始化的。
DrawableRequestBuilder:对Glide参数初始化的配置工作
而DrawableRequestBuilder又extends继承自 GenericRequestBuilder
 
GenericRequestBuilder:Glide所有配置参数基本的最终类:
public class DrawableRequestBuilder<ModelType>
        extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>
        implements BitmapOptions, DrawableOptions 
ps:DrawableRequestBuilder可以通过链式调用配置参数
 
在GenericRequestBuilder中有一个很重要的成员变量:
protected final RequestTracker requestTracker; //负责跟踪整个图片请求的周期
 
fromString():传入String的Class对象作为参数
public DrawableTypeRequest<String> fromString() {
    return loadGeneric(String.class);
}
loadGeneric():
private <T> DrawableTypeRequest<T> 
                        loadGeneric(Class<T> modelClass) {
   //创建两个ModelLoader对象,通过数据来源,ML将来源加载成原始数据
    ModelLoader<T, InputStream> streamModelLoader 
            = Glide.buildStreamModelLoader(modelClass, context);
    ModelLoader<T, ParcelFileDescriptor>fileDescriptorModelLoader
            = Glide.buildFileDescriptorModelLoader(modelClass, context);
    if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
        throw new IllegalArgumentException(...);
    }
    //创建DrawableTypeRequest 对象
    return optionsApplier.apply(
            new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                    glide, requestTracker, lifecycle, optionsApplier));
}
 
DrawableTypeRequest():相对常用的方法,通过返回不同的TypeRequest来选择需要的方法
//强制指定加载静态图片
public BitmapTypeRequest<ModelType> asBitmap() {
    return optionsApplier.apply(new BitmapTypeRequest<ModelType>
                            (this, streamModelLoader,
                    fileDescriptorModelLoader, optionsApplier));
}
//强制指定加载动态图片
public GifTypeRequest<ModelType> asGif() {
    return optionsApplier.apply(new GifTypeRequest<ModelType>        (this, streamModelLoader, optionsApplier));
}
 
   4.5、into()解析:——在主线程中调用的更新UI操作
public Target<TranscodeType> into(ImageView view) {
   //1、是否在主线程中操作,非主线程抛出异常
    Util.assertMainThread();
    if (view == null) {
        throw new IllegalArgumentException("You must pass in a non null View");
    }
    //2、判断类型是否进行哪类图片处理
    if (!isTransformationSet && view.getScaleType() != null) {
        switch (view.getScaleType()) {
            case CENTER_CROP:
                applyCenterCrop();
                break;
            case FIT_CENTER:
            case FIT_START:
            case FIT_END:
                applyFitCenter();
                break;
            //$CASES-OMITTED$
            default:
                // Do nothing.
        }
    }
    //3、创建一个Target对象,图片所要显示的控件
    return into(glide.buildImageViewTarget(view, transcodeClass));
}
 
applyCenterCrop():
————>centerCrop()————>centerCrop():
public DrawableRequestBuilder<ModelType> centerCrop() {
    return transform(glide.getDrawableCenterCrop());
}
transform()://图片转换,对transformation进行赋值操作
public GenericRequestBuilder<ModelType, DataType,
                    ResourceType, TranscodeType> transform(
        Transformation<ResourceType>... transformations) {
    isTransformationSet = true;
    if (transformations.length == 1) {
        transformation = transformations[0];
    } else {
        transformation = new MultiTransformation<ResourceType>(transformations);
    }
    return this;
}
glide.buildImageViewTarget() 中Target接口 :
 
//Glide能绑定生命周期的原因:
interface Target<R> extends LifecycleListener 
    接口内含有onStart()、onStop()、onDestory()函数
 
在进行.with()操作的时候,会传入context or Activity or Fragment进行相应生命周期的绑定操作 ,这时候就是通过LifecycleListener进行组件的监听
 
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
    //通过imageViewTargetFactory工厂构建Target
    return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
//通过传入class对象判断Glide需要加载哪类图片,buildTarget()就创建哪类Target
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
    if (GlideDrawable.class.isAssignableFrom(clazz)) {
            //未调用asBitmap()方法就会默认采取GlideDrawableImageViewTarget()方法
        return (Target<Z>) new GlideDrawableImageViewTarget(view);
    } else if (Bitmap.class.equals(clazz)) {
            //调用了asBitmap()
        return (Target<Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
            //使用较少
        return (Target<Z>) new DrawableImageViewTarget(view);
    } else {
        throw new IllegalArgumentException("Unhandled class: " + clazz
                + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
}
 
into():
 
3、新建一个Request绑定到Target上
4、发送Request 交给RequestTracker进行相应的处理
public <Y extends Target<TranscodeType>> Y into(Y target) {
    //是否在主线程 (更新UI必须在主线程)
    Util.assertMainThread();
                …
        1、对旧的绑定的Target对象进行清除
    //获取当然Target对象所绑定的旧的Request对象
    Request previous = target.getRequest();
    if (previous != null) {
        //清理操作
        previous.clear();
        //取消当前Request请求避免图片错位
        requestTracker.removeRequest(previous);
        //在该实现类中将成员变量赋值为null 
            ps:并调用REQUEST_POOL.offer(this);当前一个Request不用的时候会被放入请求池以备复用
        previous.recycle();
    }
    2、创建一个加载图片的Request
    ps:list图片错位的问题?
    解决:给View绑定setTag(),将view和图片进行绑定
 
    Request request = buildRequest(target);
    //将图片Request请求和ImageView绑定
    target.setRequest(request);
    lifecycle.addListener(target);
    //3、将Request发送给RequestTracker执行request请求
    requestTracker.runRequest(request);
    return target;
}
 
如何buildRequest?
buildRequest():————>buildRequestRecursive()
    ...
obtainRequest()————> GenericRequest.obtain():实际创建Request()
 
GenericRequest.obtain():
   //从线程池中获取一个请求,如果有可以复用的就复用一个请求,如果没有就创建一个新的
    ...
GenericRequest<A, T, Z, R> request =
     (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
    if (request == null) {
        request = new GenericRequest<A, T, Z, R>();
    }
        request.init();//初始化操作
        return request;
 
requestTracker.runRequest:
public void runRequest(Request request) {
    //将当前request加入到set<Request> 集合当中
    requests.add(request);
    //对当前状态进行判断,当前是有Request请求进入else ,将当前Request加入pending悬挂中的集合中
    if (!isPaused) {
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}
 
request.begin():{
    …
    //判断前面是否调用了overrideWidth 限定宽高的方法,直接执行onSizeReady()
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
    onSizeReady(overrideWidth, overrideHeight);
} else { 
   //没有限定款高通过getSize()计算宽高
    target.getSize(this);
}
    //当前状态是否已经完成,图片是否不是失败状态进行判断
    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
    //设定图片(获取占位图)
    target.onLoadStarted(getPlaceholderDrawable());
    ...
}
 
target.getSize——>
public void getSize(SizeReadyCallback cb) {
    sizeDeterminer.getSize(cb);
}
getSize():内部根据ImageView宽高进行再次计算后再执行onSizeReady()
public void getSize(SizeReadyCallback cb) {
    //1、获取宽高
    int currentWidth = getViewWidthOrParam();
    int currentHeight = getViewHeightOrParam();
    //3、当View绘制完时候就会进入onSizeReady()方法
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
    cb.onSizeReady(currentWidth, currentHeight);
    …
    //2、当前View没有被测量完,会被添加到viewTreeObserver观察者中
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);    
    
 
    1、通过LoadProvider()方法获取ModelLoader和Transcoder
    2、根据ModelLoader获取DataFetcher
    3、engine.load 进行实际图片加载
 
ModelLoader:从数据源中获取原始数据,一般是输入流inputStream,在Glide中被封装成Data
DataFetcher:将Data原始数据转换成能直接用的不同形式的图片数据类型
ResourceTranscoder:将原始数据解码,将io输入流解码成bitmap的解码工具对象
Resource:解码后的资源
 
onSizeReady (int width, int height) {
    …
ModelLoader<A, T> modelLoader 
                =     loadProvider.getModelLoader();
//通过loadProvider接口获取到dataFetcher、loadProvider、transcoder
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
            
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
        priority, isMemoryCacheable, diskCacheStrategy, this);
}
LoadProvider():GenericRequest的成员变量
public interface LoadProvider<A, T, Z, R> extends DataLoadProvider<T, Z> {
    ModelLoader<A, T> getModelLoader();
    ResourceTranscoder<Z, R> getTranscoder();
 
LoadProvider初始化:在GenericRequest的实现类
DrawableTypeRequest(...) {
    super(context, modelClass,
            buildProvider(glide, streamModelLoader, fileDescriptorModelLoader,)
    ...
}
 
buildProvider的返回值就是LoadProvider,FixedLoadProvider是LoadProvider的实现类:获取原始图片,进行编解码、转码等功能
private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
        ModelLoader<A, InputStream> streamModelLoader,
        ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader...) {
   //判断来自不同数据的Loader
    if (streamModelLoader == null && fileDescriptorModelLoader == null) {
        return null;
    }
    //ResourceTranscoder是否为空,为null则通过Glide创建新的Transcoder
ps:对原始数据进行解码
    if (transcoder == null) {
        transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
    }
    //DataLoadProvider对图片进行编解码的接口,实现类中LoadProvider
    DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider 
                = glide.buildDataProvider(ImageVideoWrapper.class,resourceClass);
    //封装了两个ModelLoader
    ImageVideoModelLoader<A> modelLoader = new 
        ImageVideoModelLoader<A>(streamModelLoader,fileDescriptorModelLoader);
    
        return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>
                    (modelLoader, transcoder, dataLoadProvider);
}
 
DataLoadProvider:负责编解码
Data:从数据源获取的数据
Resource:解码后的资源
解码:Data————>Resource 资源
编码:将Data、Resource———>持久化到本地的过程 用于实现Glide的磁盘缓存功能
 
engine.load():
engine:负责图片加载,管理正在使用和处于缓存的图片类
 
LoadStatus load(){
    …
//加载图片的唯一标识id,比如加载网络图片的话就是一个网络图片的url地址
final String id = fetcher.getId();
//传入唯一标识id ,其他很多参数依然可以决定EngineKey的值,构建Glide缓存中的一个key值
EngineKey key = keyFactory.buildKey(id,…);    
    ...
 //从缓存中获取图片
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
    cb.onResourceReady(cached);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    //若缓存图片是空的则调用loadFromActiveResources
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    …
    //若两者都没有获取到就自己开启一个runnable 从磁盘或是网络进行图片的获取
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable);
}
 
GLide中的内存缓存策略
 
GLide 构建内存缓存的组成部分:cache、active
 
A、内存缓存读取操作原理
   1、 LruCache算法:近期最少使用算法,将最近使用的对象的强引用存储在LinkHashMap上;并且把最近最少使用的对象,在缓存池达到预设值之前从内存中移除
   2、弱引用缓存机制
    
Glide中的内存缓存对象:Engine类中的 Load()函数
    MemoryCache——loadFromCache(EngineKey,boolean isMemoryCacheable)
        cached :最近使用过的而当前不在使用的EngineResource,LoadFromCache内部使用linkHashMap当内存达到阀值会通过LruCache算法进行清除操作
 
loadFromCache():1、从内存缓存MemoryCache中获取图片
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    //配置Glide 的时候使用的isMemoryCacheable,跳过内存缓存操作skipMemoryCache(true)
    默认情况下为true 的时候开启缓存模式
    if (!isMemoryCacheable) {
        return null;
    }
    //获取实际缓存对象
    EngineResource<?> cached = getEngineResourceFromCache(key);
       //ps:2、在该方法中会使用缓存的key值 从cache中获取值并调用remove函数把这个图片从MemoryCahce缓存中移除掉
            //Resource cached = this.cache.remove(key);
    if (cached != null) {
        cached.acquire();
        //写入一个弱引用的 缓存
        activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
    }
    return cached;
}
 
ps:使用弱引用的好处,使得该部分缓存不会被Lru算法给回收掉资源
 
loadFromActiveResources():3、调用该方法从正在使用的部分中继续获取图片
    ActiveResource——loadFromActiveResources(EngineKey,boolean isMemoryCacheable)——HashMap<key,WeakReference<EngineResource>>
       active:保存当前正在使用的EngineResource对象
      ps:  会以EngineKey为键,以EngineResource的弱引用为值 
 
HashMap的Get()、 Put()函数对应着 缓存的读和取 的操作
 
//4、若前两步都没有获取到图片资源,就创建一个Engine runnbale对象加载,从磁盘或是网络获取图片
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
        transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnablerunnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
 
B、内存缓存写入操作原理
   
if (cached != null) {
    cb.onResourceReady(cached);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    return null;
}
cb:callback回调
onResourceReady :ResourceCallback的具体实现
在onResourceReady()中:定义一个handler发送message,将执行逻辑切回到主线程中操作
public void onResourceReady(final Resource<?> resource) {
    this.resource = resource;
    MAIN_THREAD_HANDLER.obtainMessage(
                                                MSG_COMPLETE, this).sendToTarget();
}
其中MAIN_THREAD_HANDLER是MainThreadCallback回调定义:
private static final Handler MAIN_THREAD_HANDLER = 
                    new Handler(Looper.getMainLooper(),
                                    new MainThreadCallback());
MainThreadCallback():接受Message信息并处理的callback回调,通过名字可以知道该消息是在主线程进行结果回调
private static class MainThreadCallbackimplements Handler.Callback {
 
    @Override
    public boolean handleMessage(Message message) {
        if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
            EngineJob job = (EngineJob) message.obj;
            if (MSG_COMPLETE == message.what) {
                job.handleResultOnMainThread();
            } else {
                job.handleExceptionOnMainThread();
            }
              ...
}
job.handleResultOnMainThread():
private void handleResultOnMainThread() {
    //1、如果任务取消则回收资源 recycle
    if (isCancelled) {
        resource.recycle();
        return;
    //2、若callback集合为null 空 则抛出异常
    } else if (cbs.isEmpty()) {
        throw new IllegalStateException("Received a resource without any callbacks to notify");
    }
    //3、通过工厂累创建包含图片资源的engineResource对象
    engineResource = engineResourceFactory.build(resource, isCacheable);
     ...
   //3、将对象回调到onEngineJobComplete()当中
engineResource.acquire();
    //4、acquire():记录图片被引用的次数,同时在该函数结尾通过 ++this.acquire 来进行图片次数的+1 操作;当acquired >0 表示有图片正在使用中,也就是应该把图片放入到activeResource 这个弱引用缓存中
    listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
    if (!isInIgnoredCallbacks(cb)) {
        engineResource.acquire();
        cb.onResourceReady(engineResource);
    }
}
    //5、release() 当图片等于0的时候表示图片没有在使用了就调用onResourceReleased释放资源操作,该实现在Engine()函数中
    engineResource.release();
}
 
onEngineJobComplete():
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
    Util.assertMainThread();
    if (resource != null) {
        resource.setResourceListener(key, this);
        if (resource.isCacheable()) {
           //最终调用activeResources.put()这个函数进行了缓存的写入操作
    ps:这里写入的是弱引用的缓存
            activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
        }
    }
    // TODO: should this check that the engine job is still current?
    jobs.remove(key);
}
 
onResourceReleased():
public void onResourceReleased(Key cacheKey, EngineResource resource) {
    Util.assertMainThread();
    //1、把整个缓存图片从activeResources从缓存图片中删除
    activeResources.remove(cacheKey);
    if (resource.isCacheable()) {
    //2、通过put()加入到LruCache算法中,
    ps:实现正在使用的图片通过弱引用进行缓存,而不在使用的利用LruCache进行缓存
       cache.put(cacheKey, resource);
    } else {
        resourceRecycler.recycle(resource);
    }
}
 
磁盘缓存读写 待补充
 
五、Glide常用
     
  5.1 、Target函数的使用- Glide获取 Bitmap资源本身
        Target其实就是整个图片的加载的生命周期,通过它在图片加载完成之后获取Bitmap
    以 SimpleTarget 为例:
       private SimpleTarget<Bitmap> mSimpleTarget= new SimleTarget<Bitmap> (可添加宽*高 尺寸限定){
            @Override
            public void onResourceReady( Bitmap resource, 
                                                        GlideAnimation< ? super Bitmap>animation) {
                ImageView.setImageBitmap(resource);
            }
    };    
    private void loadImageSimpleTarget(){
        Glide .with (1 ). load(url ) . asBitmap() . into ( mSimpleTarget );
    }
 
在这里如果SimpletTarget 使用匿名内部类的方式创建 SimpleTarget 的对象,这样会增大该对象在 Glide 完成图片请求之前就被回收的可能性。
(1):这里如果传入的是Target ,可能独立于with的生命周期之外,所以最好给with里传入参数context .getApplicationContext(),让Glide持有整个app的生命周期。
 
    5.2、ViewTarget函数的使用
        在自定义View的时候,Glide有时候并不支持加载图片到自定义View中的时候,这时候就需要ViewTarget
public void loadImageTarget(Context context){ 
        CustomView mCustomView = (CustomView) findViewById(R.id.custom_view);             ViewTarget viewTarget = 
        new ViewTarget<CustomView,GlideDrawable>( mCustomView) { 
                @Override 
                public void onResourceReady(GlideDrawable resource,
                                         GlideAnimation<? super GlideDrawable> glideAnimation) {                             this.view.setImage(resource); 
    } 
}; 
    Glide.with(context) .load(mUrl) .into(viewTarget); 
}
onResourceReady回调方法中使用了自定义view自己的方法设置图片,可以看到在创建ViewTarget的时候传入了CustomView 对象
 
    5.3、Transformations函数的使用
         如果需要对图片进行图片切圆角、灰阶处理 等功能 就需要 通过 Transformations来操作bitmap 。通过修改尺寸、范围、颜色、像素、位置等达到需求。
    ps:若是对图片进行常规的bitmap转换的话,可以使用抽象类 BitmapTransformation 进行
   对图片切圆角操作 :getId()是图片的唯一标识符必须唯一
    public class RoundTransformation extends BitmapTransformation {
         private float radius = 0f; 
        
    public RoundTransformation(Context context) { 
            this(context, 4); 
        } 
    
    public RoundTransformation(Context context, int px) {
         super(context); 
        this.radius = px; 
    } 
 
       @Override 
      protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
                                                                                 int outWidth, int outHeight) { 
            return roundCrop(pool, toTransform); 
    } 
    
    private Bitmap roundCrop(BitmapPool pool, Bitmap source) { 
        if (source == null) 
            return null; 
            Bitmap result = pool.get(source.getWidth(),
                                 source.getHeight(), Bitmap.Config.ARGB_8888); 
 
        if (result == null) { 
            result = Bitmap.createBitmap(source.getWidth(), 
                                  source.getHeight(), Bitmap.Config.ARGB_8888); 
        } 
 
            Canvas canvas = new Canvas(result);    
            Paint paint = new Paint(); 
            paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP,
                                                            BitmapShader.TileMode.CLAMP));                
             paint.setAntiAlias(true); 
             RectF rectF = new RectF(0f, 0f, source.getWidth(), 
                                                                            source.getHeight());                                                     canvas.drawRoundRect(rectF, radius,radius, paint); 
            return result;
     } 
        @Override 
        public String getId() { 
            return getClass().getName() + Math.round(radius); 
        } 
    }
下面是transform的调用:
    Glide.with(context) 
            .load(mUrl) 
                .transform(new RoundTransformation(context , 20)) 
                    //.bitmapTransform( new RoundTransformation(context , 20) )         .into(mImageView);
 
ps:若需要同时执行多个transformation的话 ,不能再像这样通过链式调用的形式多次调用.transform() 或是.bitmapTransform() 方法。上面的transform会被最后一个覆盖掉。
这时候可以这样做:
    Glide.with(context) 
        .load(mUrl) 
            .transform(new RoundTransformation(context , 20) 
                , new RotateTransformation(context , 90f)) 
        .into(mImageView);
 
  对图片旋转操作 
public class RotateTransformationextends BitmapTransformation { 
        private float rotateRotationAngle = 0f; 
        public RotateTransformation(Context context, float rotateRotationAngle) {
             super( context );
         this.rotateRotationAngle = rotateRotationAngle;
     } 
        @Override 
        protected Bitmap transform(BitmapPool pool, 
                Bitmap toTransform, int outWidth, int outHeight) {
             Matrix matrix = new Matrix(); 
             matrix.postRotate(rotateRotationAngle); 
                return Bitmap.createBitmap(toTransform, 0, 0, 
                            toTransform.getWidth(), toTransform.getHeight(), matrix, true); 
    } 
        @Override 
       public String getId() {
             return getClass().getName() + Math.round(rotateRotationAngle); 
        }
 }
ps:这里需要注意一点 .centerCrop() 和 .fitCenter() 也都是 Transformation 所以也是遵循同时使用多个 Transformation 的规则的,即:当你使用了自定义转换后你就不能使用 .centerCrop() 或 .fitCenter() 了。
这里有一个 GLide Transformations 的库,它提供了很多 Transformation 的实现,非常值得去看,不必重复造轮子对吧!
 
5.4、Animate动画函数的使用
     图片间切换的平滑过渡 是非常重要的!!所以Glide又一个标准动画去柔化Ui中的改变,但是如果是设置自己的动画的话:
一个小例子:
<set 
    xmlns:android=" http://schemas.android.com/apk/res/android"         android:fillAfter="true”> 
    <scale 
        android:duration="@android:integer/
                                                config_longAnimTime”     
        android:fromXScale="0.1” 
        android:fromYScale="0.1” 
        android:pivotX="50%”
        android:pivotY="50%”
        android:toXScale="1” 
        android:toYScale="1"/> 
    </set>
    XML 动画缩放动画,图片刚开始小的,然后逐渐增大到原尺寸。我们现在要应用到 Glide 加载图片中去,调用 .animate() 方法传入 XML 动画的 id 即可。
    Glide.with(context)     
        .load(mUrl)
         .transform(new RoundTransformation(this , 20))
         .animate( R.anim.zoom_in )
         .into(mImageView);
 
animate在Target中的使用:
    ViewPropertyAnimation.Animatoranimator 
            = new ViewPropertyAnimation.Animator() { 
        @Override 
        public void animate(View view) { 
            view.setAlpha( 0f ); 
            ObjectAnimator fadeAnim 
                    = ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
                 fadeAnim.setDuration( 2500 ); 
                fadeAnim.start();
         } 
    };
Glide.with(context)
    .load(mUrl)
    .animate( animator )
    .into(viewTarget);
 
  5.5、Modules函数的使用
    Glide 的Module 是一个可以全局改变Glide 的行为的东西。为了实现需求需要去实现 interface GlideModule 来实现功能
   public class ExampleModule implements GlideModule{
         @Override 
        public void applyOptions(Context context, GlideBuilder builder) { 
        // todo
     } 
        @Override 
        public void registerComponents(Context context, Glide glide) { 
        // todo
     } }
主要使用的是 applyOptions(Context context, GlideBuilder builder) , 我们自己的需要重新定义的代码写在该方法里就可以了。然后我们还需要去 AndroidManifest.xml 中使用 meta 声明我们上面实现的 Module:
        <application>
             <meta-data android:name
                                ="com.mrtrying.demoglide.module.ExampleModule"                         android:value="GlideModule" /> 
                ... </application>
到这里我们就完成了 ExampleModule 的声明,Glide 将会在工作是使用我们所定义的 Module
 
TIPS
  • 我们需要将 android:name 属性改成 包名+类名 的形式,这样的引用才是正确的。如果你想删掉 Glide Module,只需要删除在 AndroidManifest.xml 中的声明就可以了。Java 类可以保存,说不定以后会用呢。如果它没有在 AndroidManifest.xml 中被引用,那它不会被加载或被使用。
  • 定制 module 的话 Glide 会有这样一个优点:你可以同时声明多个 Glide module。Glide 将会(没有特定顺序)得到所有的声明 module。因为你当前不能定义顺序,请确保定制不会引起冲突!
applyOptions(Context context, GlideBuilder builder) 中有两个参数, 我们通过使用 GlideBuilder 来实现我们的需求。先看看 GlideBuilder 中可用的方法
  • .setMemoryCache(MemoryCache memoryCache)
  • .setBitmapPool(BitmapPool bitmapPool)
  • .setDiskCache(DiskCache.Factory diskCacheFactory)
  • .setDiskCacheService(ExecutorService service)
  • .setResizeService(ExecutorService service)
  • .setDecodeFormat(DecodeFormat decodeFormat)
可以看到,这个 GlideBuilder 对象给你访问了 Glide 重要的核心组件。接下来我们就要试着去使用这些方法
增加 Glide 的图片质量
在 Android 中有两个主要的方法对图片进行解码:ARGB_8888 和 RGB_565 。前者为每个像素使用4个字节,后者每个像素仅使用2个字节。ARGB_8888 的有时就是图像质量更高以及能储存一个 alpha 通道。 Picasso 使用的就是 ARGB_8888 , Glide 默认使用低质量的 RGB_565 ,但是现在你就可以使用 Glide module 来改变图片解码规则。就象这样
public class QualityModule implements GlideModule{
    @Override
    public void applyOptions(Context context , GlideBuilder builder){
        builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
    }
 
    @Override
    public void registerComponents(Context context , Glide glide){
        // nothing to do here
    }
}
这样我们就简单的增加了 Glide 的图片质量。
往往我们还会遇到一些情况,希望 Glide 可以使用我们自己的网络框架,我们就需要做一些事情来实现这个需求了。Glide 的开发者不强制设置网络库给你,所以Glide可以说和 HTTPS 无关。理论上,它可以与任何的网络库实现,只要覆盖了基本的网络能力就行。同样是需要实现 Glide 的 ModuleLoader 的接口,为了让我们更加易用,Glide 为 OkHttp 和 Volley 两个网络库提供了实现。
假设我要集成 OkHttp 作为 Glide 的网络库,我可以手动实现一个 GlideModule 也可以在 build.gradle 中添加依赖:
dependencies{
    //...
    
    // Glide
    compile 'com.github.bumptech.glide:glide:3.7.0'
 
    // Glide's OkHttp Integration
    compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
    compile 'com.squareup.okhttp:okhttp:3.2.0'
}
Gradle 会自动合并必要的 GlideModule 到你的 AndroidManifest.xml , Glide 会认可在 manifest 中存在,然后使用 OkHttp 做到的所有网络连接!!
 
作者:MrTrying
來源:简书
以上一小节选取了 该文章的内容多谢分享!
 
六、Gilde相关知识补充-bitmap &oom & 优化bitmap
 
    4.6.1 bitmap 导致OOM
a、在使用list类View
    一、加载大量View组件又没有合理的处理缓存的情况下,大量加载bitmap很容易造成OOM。
    解决方法:
    (1)三级缓存
    (2)设置listView的监听事件,当滑动的时候不进行图片的加载,当停止滑动的时候再加载
 
   二、图片分辨率越来越高,消耗的内存越来越大 
    注意:图片Bitmap所占用的内存 = 图片长度 * 图片宽度 * 一个像素点占用的字节数
 
    三、VM值上限dalvik.vm.heapgrowthlimit 
    限定了每个app 可用的最大内存,如果超过这个值就会出现OOM现象
 
    4.6.2 bitmap的4种优化策略
        一、对图片质量进行压缩
private Bitmap compressImage(Bitmap image) {
    //1、创建字节输出流
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //2、Bitmap.CompressFormat进行压缩(类型,值越小代表压缩比例越大,100表示不压缩,压缩好的图片放在字节输出流)
    image.compress(Bitmap.CompressFormat.JPEG
            , 100, baos);
    int options = 100;
    //3、循环判断,压缩好的图片大小是否大于100kb,大于就进一步压缩
    while (baos.toByteArray().length / 1024 > 100) {
            //3.1、清空输出流
        baos.reset();
            //3.2、再次压缩
        image.compress(Bitmap.CompressFormat.JPEG, options, baos);
            //3.3、每次减少10options 数值
        options -= 10;
    }
        //4、将压缩好的数据放入到字节输入流中
    ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        //5、通过BitmapFactory.decodeStream完成bitmap的重建
    Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
    return bitmap;
}
 
        二、对图片按比例进行压缩
/**
* 图片比例压缩
*/
private Bitmap comBitmapInSize(Bitmap image) {
    //1、创建字节数组输入输出流
           ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ByteArrayInputStream isBm;
    //2、创建BitmapFactory的Options内部类对象,通过该对象可以对bimtap进行宽高、内存等的控制
            BitmapFactory.Options newOpts = new BitmapFactory.Options();
    //3、解码bitmap只返回它的宽高数值,而不必为他申请内存,通过设为true可以节省内存空间
            newOpts.inJustDecodeBounds = true;
            Bitmap bitmap;
            int w = newOpts.outWidth;
            int h = newOpts.outHeight;
            float hh = 1290f; //设置高度为1280f
            float ww = 720f; //设置宽度为720f
    //4、创建be缩放比例
            int be = 1;
            if (w > h && w > ww) {
        //4.1、如果宽大于高的话,就根据宽的大小进行缩放
                be = (int) (newOpts.outWidth / ww);
             } else if (w < h && h > hh) {
        //4.2、如果宽小于高的话,就根据高的大小进行缩放
                be = (int) (newOpts.outHeight / hh);
            }
                if (be <= 0)
                    be = 1;
    //5、将计算好的比例数字be 赋值给Options中的缩放比例变量inSamplesize
                newOpts.inSampleSize = be;
   //6、将Options的inJustDecodeBounds设置为false表示需要将图片加载到内存中
                newOpts.inJustDecodeBounds = false;
                isBm = new ByteArrayInputStream(baos.toByteArray());
    //7、重建bitmap
                bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
                return bitmap;
}
        三、关于bitmap的recycle方法(含有争议话题)
/**
* Free the native object associated with this bitmap, and clear the
* reference to the pixel data. This will not free the pixel data synchronously;
* it simply allows it to be garbage collected if there are no other references.
* The bitmap is marked as "dead", meaning it will throw an exception if
* getPixels() or setPixels() is called, and will draw nothing. This operation
* cannot be reversed, so it should only be called if you are sure there are no
* further uses for the bitmap. This is an advanced call, and normally need
* not be called, since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/
    /**
        * 当释放这个bitmap相关联的native对象的时候,它会清除像素数据。ps:不会同步的释放像素数据,只是根据在没有其他引用的情况下收集 该GC垃圾,同时将状态标记为dead状态。这时候意味着调用 setPixels() 或是getPixels()时候都会抛出异常。而不回去绘制任务东西。通常情况下不需要手动的调用,当其没有引用这个bitmap的时候,正常的垃圾回收进程会释放掉该部分内存。
        *
        */
    public void recycle() {
        ...
    }
注解: 手动的调用recycle()本身可能并没有被释放资源。所以硬要调用recycle的时候要将bitmap设置为null,好让GC和垃圾回收器回收这部分的对象。
 
        四、捕获异常
针对OOM 的区域进行 异常捕获 
public void handleBitmapCrash() {
    Bitmap bitmap = null;
    String path = "xx/xx/xx";
    try {
        //实例化bitmap
        bitmap = BitmapFactory.decodeFile(path);
    } catch (OutOfMemoryError e) {
        e.printStackTrace();
    }
}
 
七、Gilde相关知识补充-三级缓存 /LruChache算法
   Android的缓存策略-三级缓存
        内存 - 本地 - 网络  
意义:
       第一次从网络获取 ,之后将获取的图片保存到本地和内存各一份,当程序再次用到该图片的时候首先会从内存中判断是否有缓存,有的话直接从内存中获取,没有的话再从本地SD卡中进行获取。如果内存和本地均没有的话,再次从网络上获取。
 
思考:内存缓存是如何实现的呢?
    缓存主要是通过添加 、获取、删除 进行操作的。
    不论内存缓存还是硬盘缓存,缓存的大小都是固定的,有个max限制。当该部分缓存用满之后其他缓存就无法再添加,所以需要进行删除无用缓存的操作。这时候就需要一个删除旧缓存的算法,也就说常说的LRU(Least Recently Used)近期最少使用缓存算法
 
LruChache算法
LRU核心
    当缓存已满的时候会优先淘汰掉最近使用最少的缓存对象
   整个Lru算法的核心是LinkedHashMap,其继承自HashMap,其构造函数中会有一个boolean类型的值,尤其控制插入的顺序,a是Lru顺序,b是正常顺序
 
public class LruCache<T, Y> {
    private final LinkedHashMap<T, Y> cache= new LinkedHashMap<T, Y>(100, 0.75f(负载因子), true(当为true时候说明整个顺序是通过Lru算法顺序删除元素的));
     
        int size : 当前缓存的大小
        int maxSize : 当前缓存的最大值
        int putCount : put方法调用的次数
        int createCount : create方法调用的次数
        int evictionCount : 当需要淘汰一些缓存变量时候的计数器
        int hitCount : 缓存对象使用到hitCount +1 计数
        int missCount : 缓存未命中 计数 +1
        
Get():
     传入key值 获取相应的item,若是无缓存会创建并返回相应的item,相应的item会被移动到队列的尾部。
    public final V  get(K key){
        if(key == null ) {
            throw new NullPointerException(“key==null"):
        }
    V mapValue;
        //利用LinkedHashMap的get方法获取相应的value
        synchronized(this){
        mapValue = map.get(key);
        if(mapValue !=null){
            //获取到缓存 hitCount+1 并返回Value值
            hitCount++;
            return mapValue;
        }
        missCount++ ;
    }
   //未命中,调用create()创建一个对象
    V createdValue = create(key);
     if(createdValue == null) {
        return null;    
    }
    synchronized (this ){
            createCount++;   
            //覆盖原有Value值并添加到mapValue中
            mapValue = map.put(key , createdValue);
            if(mapValue !=null){
               //如果该值不为空,将重新创建好的cratedValue又覆盖成原来的mapValue,逆推上一步操作
                map.put(key,mapValue);  //插入的新的对象会存储到列表的尾端
            }else{
                //加入新创建的对象需要重新创建缓存size大小
            size += safeSizeOf(key ,createValue);
    }
        if(mapValue != null){
            entryRemoved(false, key,createdValue,mapValue);
            return mapValue;
        }else {
            //新加入对象都会调用trimToSize(),来查看对象是否需要被回收。根据传入的缓存的最大容量调整缓存的大小。传入 -1 表示清空所有缓存对象
            trimToSize(maxSize);
            return createdValue;
        }
}
Put():
    public final V put (K key , V value){
        if(key == null || value == null) {
                throw new NullPointerException (“key ==null ||value ==null ")
        }
        V previos ;
        synchronized (this ){
            //调用put方法每次计数+1
            putCount ++;
            // 每次put都会造成整个缓存大小增大,所以需要自增当前缓存
            size += safeSizeOf (key ,value );
            previous= map .put(key , value );
           //如果之前存在key为键值的对象,这时候当前的缓存size 需要减掉这个对象的大小
                if( previous !=null){
                    size -= safeSizeOf (key ,previous);
                    }
            }
            if(previous !=null ){
                entryRemoved (false ,key ,previous ,value);
            }
           //调整内存缓存大小,插入的新对象会存储在列表的尾端
            trimToSize (maxSize);
            return previous;
    }
  • LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存
  • 每次调用get则将该对象移到链表的尾端
  • 调用put插入新的对象也是存储在链表尾端
  • 当内存缓存达到设定的最大值时候,将链表头部的对象(近期最少使用到的)移除
o
粉丝 1
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Netty那点事(三)Channel与Pipeline

Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的...

黄亿华
2013/11/24
2W
22
访问安全控制解决方案

本文是《轻量级 Java Web 框架架构设计》的系列博文。 今天想和大家简单的分享一下,在 Smart 中是如何做到访问安全控制的。也就是说,当没有登录或 Session 过期时所做的操作,会自动退回到...

黄勇
2013/11/03
3.5K
8
浅入浅出Android(003):使用TextView类构造文本控件

基础: TextView是无法供编辑的。 当我们新建一个项目MyTextView时候,默认的布局(/res/layout/activity_main.xml)中已经有了一个TextView: <TextView 运行效果如下: 修改其文本内容...

樂天
2014/03/22
679
1
beego API开发以及自动化文档

beego API开发以及自动化文档 beego1.3版本已经在上个星期发布了,但是还是有很多人不了解如何来进行开发,也是在一步一步的测试中开发,期间QQ群里面很多人都问我如何开发,我的业余时间实在...

astaxie
2014/06/25
2.7W
22
Nutch学习笔记4-Nutch 1.7 的 索引篇 ElasticSearch

上一篇讲解了爬取和分析的流程,很重要的收获就是: 解析过程中,会根据页面的ContentType获得一系列的注册解析器, 依次调用每个解析器,当其中一个解析成功后就返回,否则继续执行下一个解...

强子哥哥
2014/06/26
712
0

没有更多内容

加载失败,请刷新页面

加载更多

SQL 语句大全

点击上方“掌上编程”,选择“置顶或者星标” 优质文章第一时间送达! 一、基础 「1、说明:创建数据库」 CREATE DATABASE database-name    「2、说明:删除数据库」 drop database ...

GeneralMa
昨天
0
0
山东创睦网络科技有限公司:使用Python爬取全球新冠肺炎疫情数据

使用Python爬取全球新冠肺炎疫情数据 导入所需库包 获取实时数据的url 正式编写程序 查看输出结果 导入所需库包 在获取数据之前,我们需要先安装好所需的包requests和pandas: 1.如果是使用p...

osc_qv1fwke0
40分钟前
14
0
如何1年获得别人3年的工作经验(深度好文)

最近有同学问我,为什么你的工作年限不长,技术却这么厉害,我笑了笑,啥也没说。 我不是不想回答,是不知道怎么回答。在他们的定位可能就是,每方面都懂一点,遇到问题能够快速解决,就是比...

zhang_rick
今天
1
0
新基建带动行业

什么是“新基建”? 什么是“新基建”? 根据央视发布的信息来看,其涵盖了5G基站建设、新能源汽车充电桩、大数据中心、人工智能、工业互联网,特高压,城际以及城轨交通,涉及了七大领域和相...

osc_anefoz50
40分钟前
16
0
怕入错行?这群技术人写了本“择业指南”

计算机专业好找工作吗?哪些方向是当前的主流和热门方向呢? 计算机专业的你是不是还在为职业发展纠结犹豫呢? 刚经历完高考选专业的你是不是还在迷茫徘徊呢? 那么福利来啦! 《软件技术职业...

阿里云云栖号
40分钟前
21
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部