下面小编为大家整理了Android Glide源码解析(共含8篇),欢迎阅读与借鉴!同时,但愿您也能像本文投稿人“橘子芬达”一样,积极向本站投稿分享好文章。
本文细述上文引出的RAECost和SoftmaxCost两个类,
SoftmaxCost
我们已经知道,SoftmaxCost类在给定features和label的情况下(超参数给定),衡量给定权重(hidden×catSize)的误差值cost,并指出当前的权重梯度。看代码。
@Override
public double valueAt(double[] x)
{
if( !requiresEvaluation(x) )
return value;
int numDataItems = Features.columns;
int[] requiredRows = ArraysHelper.makeArray(0, CatSize-2);
ClassifierTheta Theta = new ClassifierTheta(x,FeatureLength,CatSize);
DoubleMatrix Prediction = getPredictions (Theta, Features);
double MeanTerm = 1.0 / (double) numDataItems;
double Cost = getLoss (Prediction, Labels).sum * MeanTerm;
double RegularisationTerm = 0.5 * Lambda * DoubleMatrixFunctions.SquaredNorm(Theta.W);
DoubleMatrix Diff = Prediction.sub(Labels).muli(MeanTerm);
DoubleMatrix Delta = Features.mmul(Diff.transpose());
DoubleMatrix gradW = Delta.getColumns(requiredRows);
DoubleMatrix gradb = ((Diff.rowSums()).getRows(requiredRows));
//Regularizing. Bias does not have one.
gradW = gradW.addi(Theta.W.mul(Lambda));
Gradient = new ClassifierTheta(gradW,gradb);
value = Cost + RegularisationTerm;
gradient = Gradient.Theta;
return value;
}
public DoubleMatrix getPredictions (ClassifierTheta Theta, DoubleMatrix Features)
{
int numDataItems = Features.columns;
DoubleMatrix Input = ((Theta.W.transpose()).mmul(Features)).addColumnVector(Theta.b);
Input = DoubleMatrix.concatVertically(Input, DoubleMatrix.zeros(1,numDataItems));
return Activation.valueAt(Input);
}
是个典型的2层神经网络,没有隐层,首先根据features预测labels,预测结果用softmax归一化,然后根据误差反向传播算出权重梯度。
此处增加200字。
这个典型的2层神经网络,label为一列向量,目标label置1,其余为0;转换函数为softmax函数,输出为每个label的概率。
计算cost的函数为getLoss,假设目标label的预测输出为p∗,则每个样本的cost也即误差函数为:
cost=E(p∗)=−log(p∗)
根据前述的神经网络后向传播算法,我们得到(j为目标label时,否则为0):
∂E∂wij=∂E∂pj∂hj∂netjxi=−1pjpj(1−pj)xi=−(1−pj)xi=−(labelj−pj)featurei
因此我们便理解了下面代码的含义:
1
DoubleMatrix Delta = Features.mmul(Diff.transpose());
RAECost
先看实现代码:
@Override
public double valueAt(double[] x)
{
if(!requiresEvaluation(x))
return value;
Theta Theta1 = new Theta(x,hiddenSize,visibleSize,dictionaryLength);
FineTunableTheta Theta2 = new FineTunableTheta(x,hiddenSize,visibleSize,catSize,dictionaryLength);
Theta2.setWe( Theta2.We.add(WeOrig) );
final RAEClassificationCost classificationCost = new RAEClassificationCost(
catSize, AlphaCat, Beta, dictionaryLength, hiddenSize, Lambda, f, Theta2);
final RAEFeatureCost featureCost = new RAEFeatureCost(
AlphaCat, Beta, dictionaryLength, hiddenSize, Lambda, f, WeOrig, Theta1);
Parallel.For(DataCell,
new Parallel.Operation
public void perform(int index, LabeledDatum
{
try {
LabeledRAETree Tree = featureCost.Compute(Data);
classificationCost.Compute(Data, Tree);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
});
double costRAE = featureCost.getCost();
double[] gradRAE = featureCost.getGradient().clone();
double costSUP = classificationCost.getCost();
gradient = classificationCost.getGradient();
value = costRAE + costSUP;
for(int i=0; i gradient[i] += gradRAE[i]; System.gc(); System.gc(); System.gc(); System.gc(); System.gc(); System.gc(); System.gc(); System.gc(); return value; } cost由两部分组成,featureCost和classificationCost, 程序遍历每个样本,用featureCost.Compute(Data)生成一个递归树,同时累加cost和gradient,然后用classificationCost.Compute(Data, Tree)根据生成的树计算并累加cost和gradient。因此关键类为RAEFeatureCost和RAEClassificationCost。 RAEFeatureCost类在Compute函数中调用RAEPropagation的ForwardPropagate函数生成一棵树,然后调用BackPropagate计算梯度并累加。具体的算法过程,下一章分解。
使用文章介绍以及和Picasso的对比分析请参考Introduction to Glide, Image Loader Library for Android, recommended by Google
由于这篇文章使用glide的老版本,因此有些使用方法可能不太一致了,
本文基于github上Glide最新代码4.0.0版本做解析。
最基本的使用方式如下:
Glide.with(this) .asDrawable() .load(i6.topit.me/6/5d/45/1131907198420455d6o.jpg) .apply(fitCenterTransform(this)) .apply(placeholderOf(R.drawable.skyblue_logo_wechatfavorite_checked)) .into(imageView);
Glide使用了现在非常流行的流氏编码方式,方便了开发者的使用,简明、扼要。
接下来主要对上面这一段流氏操作做拆分。
Glide 主入口
这个类有点像门脸模式的统一代理入口,不过实际作用在4.0.0中很多功能都被放到后面的其他类中,此类关注的点就很少了。虽然整个libray的所有需要的组建都在这个类中,但是实际也只是一个统一初始化的地方。
RequestManager(Glide.with(…))
这个类主要用于管理和启动Glide的所有请求,可以使用activity,fragment或者连接生命周期的事件去智能的停止,启动,和重启请求。也可以检索或者通过实例化一个新的对象,或者使用静态的Glide去利用构建在Activity和Fragment生命周期处理中。它的方法跟你的Fragment和Activity的是同步的。
RequestBuilder
通用类,可以处理设置选项,并启动负载的通用资源类型。
在这个类中,主要是应用请求的很多选项(如下的选项从字面都能看出具体的用处,在ImageView控件中经常也能看到,另外之前版本可不是这么使用的):
public final class RequestOptions extends BaseRequestOptions
RequestBuilder transition(TransitionOptions transitionOptions){} 这个方法主要是用于加载对象从占位符(placeholder)或者缩略图(thumbnail)到真正对象加载完成的转场动画。
RequestBuilder load(…){}多太方法中,这里可以加载很多类型的数据对象,可以是String,Uri,File,resourceId,byte[]这些。当然这些后面对应的编码方式也是不一样的。
Target into(…){}这个方法是触发Request真正启动的地方,在上边的示例中最后就是调用这个方法发起请求。
不得不说的registry域,这个域挂载了很多元件,该注册器中囊括了模块加载器(ModelLoader)、编码器(Encoder)、资源解码器(ResourceDecoder)、资源编码器(ResourceEncoder)、数据回转器(DataRewinder)、转码器(Transcoder)。这些都是Glide在对资源编解码中既是基础又是核心功能。
这里主要列举一下一些重要的组件以及他们的结构关系:
ModelLoader
DataFetcher
Target
Resource
ResourceTransformation
Pool
Cache
Decoder
Encoder
把这些组件代码结构列举出来主要是为了让读者和使用者一目了然的看到自己需要的一些功能。
1、根据不同版本的Fragment创建RequestManagerFragment或者SupportRequestManagerFragment,并加入到对应的FragmentManager中。这两种Fragment是不带有任何界面的,主要是用于同步生命周期。具体实现如下:
public static RequestManager with(Context context) { RequestManagerRetriever retriever = RequestManagerRetriever.get; return retriever.get(context); }// RequestManagerRetriever.get(...) @TargetApi(Build.VERSION_CODES.HONEYCOMB) public RequestManager get(Activity activity) { if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {return get(activity.getApplicationContext()); } else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();return fragmentGet(activity, fm, null); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) RequestManager fragmentGet(Context context, android.app.FragmentManager fm,android.app.Fragment parentHint) { RequestManagerFragment current = getRequestManagerFragment(fm, parentHint); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) {requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());current.setRequestManager(requestManager); } return requestManager; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm, android.app.Fragment parentHint) { RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) {current = pendingRequestManagerFragments.get(fm);if (current == null) { current = new RequestManagerFragment(); current.setParentFragmentHint(parentHint); pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();} } return current; }
2、创建一个RequestBuilder,并添加一个DrawableTransitionOptions类型的转场动画
public RequestBuilder
3、加载对象(model域)
public RequestBuilder
4、装载对象(包含请求的发起点)。
public
一般而言,大部分使用者都是用来装载图片的,因此都会调用如下这个方法:
public Target
这里针对ImageView的填充方式做了筛选并对应设置到requestOptions上。最终的是通过ImageView和转码类型(transcodeClass)创建不通过的Target(例如Bitmap对应的BitmapImageViewTarget和Drawable对应的DrawableImageViewTarget)
4.1 Request的创建buildRequest(target)。
在Request的创建中会针对是否有缩略图来创建不同尺寸的请求,缩略图方法可以使用RequestBuilder.thumbnail(…)方法来添加上。
Glide中的Request都是使用了SingleRequest类,当然缩略图采用的是ThumbnailRequestCoordinator类:
private Request obtainRequest(Target
比较值得推崇的是SingleRequest.obtain写法,个人认为比new关键字更简洁明了吧。
target.setRequest(request)也是一个比较值得注意的地方,如果target是ViewTarget,那么request会被设置到View的tag上。这样其实是有一个好处,每一个View有一个自己的Request,如果有重复请求,那么都会先去拿到上一个已经绑定的Request,并且从RequestManager中清理回收掉。这应该是去重的功能。
4.2 requestManager.track(target, request)
这个方法非常的复杂,主要用于触发请求、编解码、装载和缓存这些功能。下面就一步一步来看吧:
4.2.1 缓存target,并启动Request
void track(Targettarget, Request request) { targetTracker.track(target); requestTracker.runRequest(request); } /** * Starts tracking the given request. */ public void runRequest(Request request) { requests.add(request); //添加内存缓存 if (!isPaused) {request.begin(); // 开始 } else {pendingRequests.add(request); // 挂起请求 } }
继续看一下SingleRequest中的begin方法:
@Override public void begin() { stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); // 如果model空的,那么是不能执行的。 这里的model就是前面提到的RequestBuilder中的model if (model == null) {if (Util.isValidDimensions(overrideWidth, overrideHeight)) { width = overrideWidth; height = overrideHeight;}// Only log at more verbose log levels if the user has set a fallback drawable, because// fallback Drawables indicate the user expects null models occasionally.int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;onLoadFailed(new GlideException(Received null model), logLevel);return; } status = Status.WAITING_FOR_SIZE; // 如果当前的View尺寸已经加载获取到了,那么就会进入真正的加载流程。 if (Util.isValidDimensions(overrideWidth, overrideHeight)) {onSizeReady(overrideWidth, overrideHeight); } else { // 反之,当前View还没有画出来,那么是没有尺寸的。 // 这里会调用到ViewTreeObserver.addOnPreDrawListener。 // 等待View的尺寸都ok,才会继续target.getSize(this); } // 如果等待和正在执行状态,那么当前会加载占位符Drawable if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged()) {target.onLoadStarted(getPlaceholderDrawable()); } if (Log.isLoggable(TAG, Log.VERBOSE)) {logV(finished run method in + LogTime.getElapsedMillis(startTime)); } }
接下来是target.getSize(this)方法。这里主要说一下尺寸未加载出来的情况(ViewTarget.java):
void getSize(SizeReadyCallback cb) {int currentWidth = getViewWidthOrParam();int currentHeight = getViewHeightOrParam();if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) { cb.onSizeReady(currentWidth, currentHeight);} else { // We want to notify callbacks in the order they were added and we only expect one or two // callbacks to // be added a time, so a List is a reasonable choice. if (!cbs.contains(cb)) { cbs.add(cb); } if (layoutListener == null) { final ViewTreeObserver bserver = view.getViewTreeObserver(); layoutListener = new SizeDeterminerLayoutListener(this); // 绘画之前加入尺寸的监听。这一点我想大部分Android开发同学应该都知道。 // 接下来在看看系统触发该Listener时target又干了些什么。 observer.addOnPreDrawListener(layoutListener); }} }private static class SizeDeterminerLayoutListener implements ViewTreeObserver .OnPreDrawListener { // 注意这里是弱引用private final WeakReference
ok,继续回到SingleRequest.onSizeReady方法,主要就是Engine发起load操作
public void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); if (Log.isLoggable(TAG, Log.VERBOSE)) {logV(Got onSizeReady in + LogTime.getElapsedMillis(startTime)); } if (status != Status.WAITING_FOR_SIZE) {return; } status = Status.RUNNING; float sizeMultiplier = requestOptions.getSizeMultiplier(); this.width = Math.round(sizeMultiplier * width); this.height = Math.round(sizeMultiplier * height); if (Log.isLoggable(TAG, Log.VERBOSE)) {logV(finished setup for calling load in + LogTime.getElapsedMillis(startTime)); } loadStatus = engine.load( glideContext, model, requestOptions.getSignature(), this.width, this.height, requestOptions.getResourceClass(), transcodeClass, priority, requestOptions.getDiskCacheStrategy(), requestOptions.getTransformations(), requestOptions.isTransformationRequired(), requestOptions.getOptions(), requestOptions.isMemoryCacheable(), this); if (Log.isLoggable(TAG, Log.VERBOSE)) {logV(finished onSizeReady in + LogTime.getElapsedMillis(startTime)); } }
特别的,所有的操作都是来之唯一一个Engine,它的创建是来至于Glide的初始化。如果有需要修改缓存配置的同学可以继续看一下diskCacheFactory的创建:
if (engine == null) {engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor); }
继续看一下Engine.load的详细过程:
public
上面有一些值得注意的地方:
内存缓存:在Glide中默认是LruResourceCache。当然你也可以自定义; 为何要两级内存缓存(loadFromActiveResources)。个人理解是一级缓存采用LRU算法进行缓存,并不能保证全部能命中,添加二级缓存提高命中率之用; EngineJob和DecodeJob各自职责:EngineJob充当了管理和调度者,主要负责加载和各类回调通知;DecodeJob是真正干活的劳动者,这个类实现了Runnable接口。下面来看看DecodeJob是如何执行的:
private void runWrapped() { switch (runReason) {case INITIALIZE: // 初始化 获取下一个阶段状态 stage = getNextStage(Stage.INITIALIZE); currentGenerator = getNextGenerator(); // 运行 runGenerators(); break;case SWITCH_TO_SOURCE_SERVICE: runGenerators(); break;case DECODE_DATA: decodeFromRetrievedData(); break;default: throw new IllegalStateException(Unrecognized run reason: + runReason); } }// 这里的阶段策略首先是从resource中寻找,然后再是data,,再是sourceprivate Stage getNextStage(Stage current) { switch (current) {case INITIALIZE: // 根据定义的缓存策略来回去下一个状态// 缓存策略来之于RequestBuilder的requestOptions域// 如果你有自定义的策略,可以调用RequestBuilder.apply方法即可// 详细的可用缓存策略请参看DiskCacheStrategy.java return diskCacheStrategy.decodeCachedResource()? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);case RESOURCE_CACHE: return diskCacheStrategy.decodeCachedData()? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);case DATA_CACHE: return Stage.SOURCE;case SOURCE:case FINISHED: return Stage.FINISHED;default: throw new IllegalArgumentException(Unrecognized stage: + current); }// 根据Stage找到数据抓取生成器。private DataFetcherGenerator getNextGenerator() { switch (stage) {case RESOURCE_CACHE: // 产生含有降低采样/转换资源数据缓存文件的DataFetcher。 return new ResourceCacheGenerator(decodeHelper, this);case DATA_CACHE: // 产生包含原始未修改的源数据缓存文件的DataFetcher。 return new DataCacheGenerator(decodeHelper, this);case SOURCE:// 生成使用注册的ModelLoader和加载时提供的Model获取源数据规定的DataFetcher。// 根据不同的磁盘缓存策略,源数据可首先被写入到磁盘,然后从缓存文件中加载,而不是直接返回。 return new SourceGenerator(decodeHelper, this);case FINISHED: return null;default: throw new IllegalStateException(Unrecognized stage: + stage); } }
经过很多流程,最后来到了发起实际请求的地方SourceGenerator.startNext()方法:
public boolean startNext() { if (dataToCache != null) {Object data = dataToCache;dataToCache = null;cacheData(data); } if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; // 查找ModelLoader while (!started && hasNextModelLoader()) {loadData = helper.getLoadData().get(loadDataListIndex++);if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; 根据model的fetcher加载数据 loadData.fetcher.loadData(helper.getPriority(), this);} } return started; }
这里的Model必须是实现了GlideModule接口的,fetcher是实现了DataFetcher接口。有兴趣同学可以继续看一下integration中的okhttp和volley工程。Glide主要采用了这两种网络libray来下载图片。
4.2.2 数据下载完成后的缓存处理SourceGenerator.onDataReady
public void onDataReady(Object data) { DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {dataToCache = data;// We might be being called back on someone else's thread. Before doing anything, we should// reschedule to get back onto Glide's thread.cb.reschedule(); } else {cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); } }
有些小伙伴可能看不太明白为什么就一个dataToCache = data就完了…其实cb.reschedule()很重要,这里的cb就是DecodeJob.reschedule():
public void reschedule() { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this); }
这里又有一个Callback,继续追踪,这里的Callback接口是定义在DecodeJob内的,而实现是在外部的Engine中(这里会用线程池重新启动当前job,那为什么要这样做呢?源码中的解释是为了不同线程的切换,因为下载都是借用第三方网络库,而实际的编解码是在Glide自定义的线程池中进行的):
public void reschedule(DecodeJobjob) { if (isCancelled) {MAIN_THREAD_HANDLER.obtainMessage(MSG_CANCELLED, this).sendToTarget(); } else {sourceExecutor.execute(job); } }
接下来继续DecodeJob.runWrapped()方法。这个时候的runReason是SWITCH_TO_SOURCE_SERVICE,因此直接执行runGenerators(),这里继续执行SourceGenerator.startNext()方法,值得注意的dataToCache域,因为上一次执行的时候是下载,因此再次执行的时候内存缓存已经存在,因此直接缓存数据cacheData(data):
private void cacheData(Object dataToCache) { long startTime = LogTime.getLogTime(); try { // 根据不同的数据获取注册的不同EncoderEncoder
继续回到SourceGenerator.startNext()方法,这个时候已经有了sourceCacheGenerator,那么直接执行DataCacheGenerator.startNext()方法:
public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader()) {sourceIdIndex++;if (sourceIdIndex >= cacheKeys.size()) { return false;}Key sourceId = cacheKeys.get(sourceIdIndex);Key riginalKey = new DataCacheKey(sourceId, helper.getSignature());cacheFile = helper.getDiskCache().get(originalKey);if (cacheFile != null) { this.sourceKey = sourceId; modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0;} } loadData = null; boolean started = false; // 这里会通过model寻找注册过的ModelLoader while (!started && hasNextModelLoader()) {ModelLoader
这里的ModelLoader跟之前提到过的Register的模块加载器(ModelLoader)对应是modelLoaderRegistry域,具体执行的操作是Registry.getModelLoaders(…)方法如下:
public
继续回到DataCacheGenerator.startNext()方法,找到了ModelLoader,这里笔者跟踪到的是FileLoader类(FileFetcher.loadData(…)方法):
public void loadData(Priority priority, DataCallbackcallback) { // 读取文件数据try { data = opener.open(file);} catch (FileNotFoundException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, Failed to open file, e); } //失败 callback.onLoadFailed(e); return;}// 成功callback.onDataReady(data); }
4.2.3 装载流程
回调通知这里就不打算多讲了,主要线路如下:
-->DataCacheGenerator.onDataReady -->SourceGenerator.onDataFetcherReady -->DecodeJob.onDataFetcherReady -->DecodeJob.decodeFromRetrievedData -->DecodeJob.notifyEncodeAndRelease -->DecodeJob.notifyComplete-->EngineJob.onResourceReady
Debug流程图:
需要说明的就是在EngineJob中有一个Handler叫MAIN_THREAD_HANDLER。为了实现在主UI中装载资源的作用,ok继续上边的流程:
-->EngineJob.handleResultOnMainThread -->SingleRequest.onResourceReady -->ImageViewTarget.onResourceReady -->ImageViewTarget.setResource-->ImageView.setImageDrawable/ImageView.setImageBitmap
Debug流程图2:
数据的装载过程中有一个很重要的步骤就是decode,这个操作发生在DecodeJob.decodeFromRetrievedData的时候,继续看代码:
private void decodeFromRetrievedData() { if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey(Retrieved data, startFetchTime, data: + currentData + , cache key: + currentSourceKey + , fetcher: + currentFetcher); } Resource
这中间发生了很多转换主要流程:
-->DecodeJob.decodeFromData-->DecodeJob.decodeFromFetcher-->DecodeJob.runLoadPath -->LoadPath.load -->LoadPath.loadWithExceptionList -->LoadPath.decode -->LoadPath.decodeResource -->LoadPath.decodeResourceWithList -->ResourceDecoder.handles -->ResourceDecoder.decode
这里讲到了decode,那么encode发生在什么时候呢?直接通过Encoder接口调用发现,在数据缓存的时候才会触发编码。具体调用在DiskLruCacheWrapper和DataCacheWriter中。一些值得参考的写法例如BitmapEncoder对Bitmap的压缩处理。
最近看开源库Glide关注度一直比较高,因此打算一探究竟。 由于时间比较紧,因此一些应该有的时序图没有画,这里也只能简单用箭头代替。不过个人认为整体执行流程已经表达清楚了。
总体来说代码写的挺漂亮的,单从使用者角度来说入手是比较容易的。 源码使用了大量的工厂方法来创建对象,就像String.valueof(…)方法一样,这也体现编码的优雅。 不过想要对这个库进行改造,可能并非易事,笔者在跟踪代码的过程中发现很多地方有Callback这样的接口,来来回回查找几次很容易就晕头转向了。。。 另外一个感觉难受的地方就是构造方法带入参数太多,就拿SingleRequest来说就是12个构造参数。 单例的使用感觉还是有些模糊,就比如GlideContext,有些时候通过Glide.get(context).getGlideContext()获取,而有些类中采用构造传入。个人觉得既然让Glide作为单例,那么还这样传入参数是不是有点多余?代码的编写都是可以折中考虑,不过如果整个项目拟定好了一个规则的话,我想最好还是遵循它。另外再吐槽一下单例,很多开发人员喜欢用单例,如果你是有代码洁癖的开发者,那么你肯定很讨厌这样,单例很容易造成代码的散落和结构不清晰。有很多网友来信都问关于开发浏览器的问题,能够理解,现在大多数基于CE的产品都具有上网浏览的功能。CE也为此提供了两种IE浏览器的源码。一种IESAMPLE、另一种IESIMPLE。他们的存放路径在%_WINCEROOT%PublicIEOak下。区别在于IESAMPLE就是CE下IE的标准版本的源码,有工具栏、状态栏、地址栏,还有Internet选项、收藏夹等等。和PC Windows的IE几乎一样。而IESIMPLE是mini版本,只有基本的IWebBrowser控件,用户界面上只有全屏显示的网页,默认用快捷键来操作。我早先对IESIMPLE的源码进行了分析,因为我对OLE知之甚少,所以在分析过程中并不能100%的掌握源码,好在这并不影响我对IESIMPLE整体的分析。相信网友看过了这两篇文章(还有一篇关于配置)后如果要基于IESIMPLE的源码开发自己的浏览器就容易多了。之所以选择IESIMPLE,是因为它的源码相对要精简的多,只有实现主要功能的代码,没有收藏夹一类的代码,在此基础上加入我们希望的功能和界面要容易些。再有IESIMPLE和IESAMPLE的主要代码基本相同。下面的图展示了IESIMPLE的基本流程,关于IE的配置将在下一篇文章中讲解。
IESIMPLE只有两个.h文件和一个.cpp文件。mainwnd.h中声明了类CMainWnd,在类CMainWnd的众多成员变量中,_hWnd保存CMainWnd窗口句柄,_pBrowser保存接口IWebBrowser2的指针,_hWndBrowser保存Browser窗口句柄,_rcWnd保存CMainWnd的窗口尺寸。这几个变量总在代码中出现,故在此提出。下面讲解上面的图中提到的每个函数的功能:
WinMain函数先到注册表的特定位置中找到创建CMainWnd窗口的线程的栈的大小。如果这个值适当,那么在创建线程的时候就采用这个栈的值,
接着注册CMainWnd窗口类,之后调用PeekMessage创建消息队列,接着调用HandleNewWindows2函数,在这个函数执行结束后进入while循环,循环体的内容是等待函数,因为每个线程在结束前都使事件对象ghExitEvent处于有信号状态,所以当所有创建的线程都结束时,主线程也就退出while循环后结束。对于HandleNewWindows2函数,在整个IESIMPLE运行过程中,它至少被执行一次。每当在新窗口中打开网页时,它就被执行一次。HandleNewWindow2函数先创建一个CMainWnd对象,然后递增记录线程数的全局变量,之后创建一个新的线程,线程函数为NewWindow。对于NewWindow函数,它先初始化COM库,然后调用CMainWnd的Create成员函数。之后调用GetMessage函数收集消息并处理。当窗口退出时作结束处理工作。对于Create成员函数,它调用CreateWindowEx函数创建窗口和进度条窗口,之后调用CreateBrowser函数创建浏览器窗口,最后调用HandleCommand( ID_FULLSCREEN, 0 )全屏显示网页内容。对于CreateBrowser函数,它首先调用GetProxyOption函数得到Internet连接代理选项,之后调用CoCreateInstance函数创建WebBrowser对象,之后调用SetClientSit设置WebBrowser的客户端,接着激活IWebBrowser控件,之后保存接口IWebBrowser2的指针和保存参与WebBrowser本地激活的窗口句柄。
其它的函数HandleCommand处理用户命令,这些命令如向前、向后、主页、刷新、停止等。对命令的执行实际上是调用IWebBrowser2的接口函数。MainWndProc函数是CMainWnd的消息处理函数。OpenURLDlgProc函数是“打开”对话框的消息处理函数。还有两个函数在下一篇文章中讲解。
整个配置解析主要是函数ngx_init_cycle(&init_cycle)进行处理,
ngx_init_cycle(&init_cycle)
ngx_time_update//时间更新,也是在main函数里面讲过
/* * 通过加锁和解锁,来更新如下时间 ngx_cached_time = tp; ngx_cached_http_time.data = p0; ngx_cached_err_log_time.data = p1; ngx_cached_http_log_time.data = p2; ngx_cached_http_log_iso8601.data = p3; */
log = old_cycle->log;//错误日志对象
pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE,log);//分配一块16k的内存池,第一个节点大约是16k,如果新增小块内存节点,那么该节点最大为4095
cycle->pool = pool;cycle->log = log;cycle->new_log.log_level = NGX_LOG_ERR;cycle->conf_prefix;//设置配置文件dircycle->prefix; //设置运行环境dircycle->conf_file;//设置配置文件绝对路径cycle->conf_param;//保存参数cycle->pathes; //路径数组cycle->open_files;//分配打开的文件描述符,其中这个结构是一个链表,每个链表节点都有一个n个size的cycle->shared_memory; //分配n个共享内存节点,这个是一个list,每个节点都存在n个ngx_shm_zone_t结构,通过next指向链表后面的节点。cycle->listening;//listening数组cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module*sizeof(void*));
为cycle分配ngx_max_module个上下文指针
首先创建核心模块的上下文结构,保存在cycle->conf_ctx相应的结构里面
核心模块如下:
NGX_CORE_MODULEindexNgx_core_module 0Ngx_errlog_module1Ngx_reg_module 6Ngx_events_module3Ngx_http_module 7核心代码
for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_CORE_MODULE) { continue; } module = ngx_modules[i]->ctx; if (module->create_conf) { rv = module->create_conf(cycle); if (rv == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->conf_ctx[ngx_modules[i]->index] = rv; } }
nginx首先创建NGX_CORE_MODULE是为了创建后面的模块上下文做铺垫,也就是
NGX_CORE_MODULE是其他模块的基础,如下:
其中Ngx_errlog_module、ngx_events_module、ngx_http_module模块都没有create_conf
方法,是根据用户的conf实际动态的进行创建,
conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));if (conf.args == NULL) { ngx_destroy_pool(pool); return NULL;} conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);if (conf.temp_pool == NULL) { ngx_destroy_pool(pool); return NULL;} conf.ctx = cycle->conf_ctx;conf.cycle = cycle;conf.pool = pool;conf.log = log;conf.module_type = NGX_CORE_MODULE;conf.cmd_type = NGX_MAIN_CONF;
为conf创建参数,用于保存配置文件的用户配置key和value,同时创建temp_pool内
存池,conf->ctx指向cycle->conf_ctx,保存cycle、pool、log,设置模块类型和命令类型,
开始解析主配置文件变量。
解析配置文件的过程主要是ngx_conf_parse进行间接递归调用,主要分为main作用域 、event作用域、http作用域、server作用域、location作用域,然后接下来就进行分析。
在解析过程中,对配置文件(nginx.conf)分为三类:
enum{ parse_file = 0, //解析文件,比如nginx.conf 还有include文件 parse_block, //解析块,比如http{}块 parse_param //解析参数 比如deamon off}
进入ngx_conf_parse函数,根据是否是解析文件、解析块、解析参数,来设置不同的值,然后进入ngx_conf_read_token,这个函数,主要是读取文件,然后设置一些旗变量,保存用户配置到conf->args里面,其中在解析配置文件(nginx.conf)的时候使用一个buffer内存,主要读取文件,然后解析buffer,解析过程是流式,如果buffer后面还有未解析完,但是没有遇到分号or大括号之类的,就会把未解析完移动到buffer的头部,然后在继续读文件,进行解析,每调用玩一次ngx_conf_read_token就会解析完一个命令,如果配置文件配置有问题,就会报错。解析完一个用户的设置后,保存在conf->args数组中。如果发现cf->handler有值,就调用cf->handler进行回调处理(一般处理mine.types文件),如果cf->handler为空,那么就调用ngx_conf_handler,遍历每一个模块的cmd,来case,找到该命令,并且调用命令的set回调方法,如果cmd是一个key value的变量设置,给相应模块变量设置完后,就调用ngx_conf_read_token,如果是一个块的key,比如http、event、location,他们的回调函数(cmd->set)首先是创建上下文结构变量,然后间接递归ngx_conf_parse,当然就会设置不同的上下文。
漏洞介绍:IIS是微软推出的一款webserver,使用较为广泛,在支持asp/asp.net的同时还可以较好的支持PHP等其他语言的运行,但是80sec发现在IIS的较高版本中存在一个比较严重的安全问题,在按照网络上提供的默认配置情况下可能导致服务器泄露服务器端脚本源码,也可能错误的将任何类型的文件以PHP的方式进行解析,使得恶意的攻击者可能攻陷支持PHP的IIS服务器,特别是虚拟主机用户可能受的影响较大。
漏洞分析:
IIS支持以CGI的方式运行PHP,但是此种模式下,IIS处理请求的时候可能导致一些同80sec提到的nginx安全漏洞一样的问题,任何用户可以远程将任何类型的文件以PHP的方式去解析,你可以通过查看Phpinfo中对php的支持方式,其中如果为CGI/FAST-CGI就可能存在这个问题,
黑盒访问
www.80sec.com/robots.txt/1.php
查看文件是否存在和返回的HTTP头就可以知道是否存在此漏洞。
同时,如果服务器支持了PHP,但应用中使用的是asp就可以通过如下方式来直接查看服务端asp源码
asp/1.php">www.80sec.com/some.asp/1.php
漏洞厂商:www.microsoft.com
解决方案:
我们已经尝试联系官方,但是此前你可以通过以下的方式来减少损失
关闭cgi.fix_pathinfo为0
★ 种子解析
★ 简历解析
★ 会议纪要解析
★ 相与为一成语解析
★ 寻根究底成语解析
★ 详尽词语解析
★ 解析安徽企业家
★ 镜子素材解析