插件式换肤框架搭建 - 资源加载源码分析

1. 概述



  大部分控件我们都会使用,但是我们未必知道其资源加载的原理,目前换肤的框架比较多我们可以随随便便拿过来用,但早在几年前这些资料是比较少的,如果想做一个换肤的框架那就只能自己一点一点啃源码。

  如果说我们现在不去用第三方的开源框架,要做一个换肤的功能,摆在我们面前的其实只有一个问题需要解决,那就是如何读取另外一个皮肤apk中的资源。

 

  所有分享大纲:2017Android进阶之路与你同行

  视频讲解地址:http://pan.baidu.com/s/1bC3lAQ

2. 资源加载源码分析



2.1 我们先来看一下ImageView的scr属性到底是怎么加载图片资源的:

    <ImageView
        android:layout_width="wrap_content"
        android:src="@drawable/app_icon"
        android:layout_height="wrap_content" />

    // ImageView.java 解析属性
    final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
    // 通过TypedArray获取图片
    final Drawable d = a.getDrawable(R.styleable.ImageView_src);
    if (d != null) {
      setImageDrawable(d);
    }

    // TypedArray.getDrawable() 方法
    public Drawable getDrawable(@StyleableRes int index) {
       // 省略部分代码....
       // 加载资源其实是通过mResources去获取的
       return mResources.loadDrawable(value, value.resourceId, mTheme);
    }

2.2 Resource创建过程分析:

  

   我们在Activity中也经常这样使用context.getResources().getColor(R.id.title_color),那么这个Resources实例是怎么创建的呢?我们可以先从context的实现类ContextImpl入手

private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
       ......
       Resources resources = packageInfo.getResources(mainThread);

       if (resources != null) {
       // 不会走此分支,因为6.0中还不支持多屏显示,虽然已经有不少相关代码了,7.0以及正式支持多屏操作了
          if (displayId != Display.DEFAULT_DISPLAY
            || overrideConfiguration != null
            || (compatInfo != null && compatInfo.applicationScale
                    != resources.getCompatibilityInfo().applicationScale)) {
              ......
            }
       }
       ......
       mResources = resources;
}

// packageInfo.getResources 方法
public Resources getResources(ActivityThread mainThread) {
       // 缓存机制,如果LoadedApk中的mResources已经初始化则直接返回,
       // 否则通过ActivityThread创建resources对象
       if (mResources == null) {
           mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                   mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
       }
       return mResources;
}

最终会来到ResourcesManager的getResources方法

    public @NonNull Resources getResources(@Nullable IBinder activityToken,
            @Nullable String resDir, //app资源文件夹路径,实际上是apk文件的路径,如/data/app/包名/base.apk
            @Nullable String[] splitResDirs, //针对一个app由多个apk组成(将原本一个apk切片为若干apk)时,每个子apk中的资源文件夹
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs, // app依赖的共享jar/apk路径
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
            // 以apk路径为参数创建key
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo);
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
            return getOrCreateResources(activityToken, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

    private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {
            // .......

            if (activityToken != null) {
                // 根据key从缓存里面找找 ResourcesImpl
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    if (DEBUG) {
                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
                    }
                    // 如果 resourcesImpl 有 那么根据resourcesImpl 和classLoader 从缓存找找 Resource
                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                            resourcesImpl);
                }

                // We will create the ResourcesImpl object outside of holding this lock.
            } else {
                // .......
            }
        }

        // If we‘re here, we didn‘t find a suitable ResourcesImpl to use, so create one now.
        // 这个比较重要  createResourcesImpl 通过 key
        ResourcesImpl resourcesImpl = createResourcesImpl(key);

        synchronized (this) {
            // .......
            final Resources resources;
            if (activityToken != null) {
                // 根据resourcesImpl和classLoader获取Resources
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
            }
            return resources;
        }
    }

    private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        // 创建AssetManager
        final AssetManager assets = createAssetManager(key);
        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        // 根据AssetManager 创建一个ResourcesImpl 其实找资源是: Resources -> ResourcesImpl -> AssetManager
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

    @VisibleForTesting
    protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        // 创建一个AssetManager对象
        AssetManager assets = new AssetManager();

        // resDir can be null if the ‘android‘ package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the ‘android‘ package
        // already.
        // 将app中的资源路径都加入到AssetManager对象中
        if (key.mResDir != null) {
            // 这个方法很重要,待会我们就是用它去加载皮肤的apk
            if (assets.addAssetPath(key.mResDir) == 0) {
                throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
            }
        }

        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {
                // 仅仅选择共享依赖中的apk,因为jar中不会有资源文件
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
                        Log.w(TAG, "Asset path ‘" + libDir +
                                "‘ does not exist or contains no resources.");
                    }
                }
            }
        }
        return assets;
    }

    /**
     * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
     * otherwise creates a new Resources object.
     */
    private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl) {
        // Find an existing Resources that has this ResourcesImpl set.
        final int refCount = mResourceReferences.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
            // 从软引用缓存里面找一找
            Resources resources = weakResourceRef.get();
            if (resources != null &&
                    Objects.equals(resources.getClassLoader(), classLoader) &&
                    resources.getImpl() == impl) {
                if (DEBUG) {
                    Slog.d(TAG, "- using existing ref=" + resources);
                }
                return resources;
            }
        }

        // Create a new Resources reference and use the existing ResourcesImpl object.
        // 创建一个Resources ,Resource有好几个构造方法,每个版本之间有稍微的差别
        // 有的版本是用的这一个构造方法 Resources(assets, dm, config, compatInfo)
        Resources resources = new Resources(classLoader);
        resources.setImpl(impl);
        // 加入缓存
        mResourceReferences.add(new WeakReference<>(resources));
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

【看了这么多我们大致可以总结一下Resources的创建流程了:】

- packageInfo.getResources(mainThread) -> mainThread.getTopLevelResources() -> mResourcesManager.getResources() -> getOrCreateResources() 这里首先会找ResourcesImpl缓存如果有则会获取Resource缓存返回;

- 如果没有ResourcesImpl缓存,那么回去创建ResourcesImpl,ResourcesImpl的创建依赖于AssetManager ;

- AssetManager的创建是通过直接实例化对象调用了一个addAssetPath(path)方法把应用的apk路径添加到AssetManager,addAssetPath()方法请看源码解释。

- 创建好ResourcesImpl之后会再去缓存中找Resource如果没有,那么则会创建Resource并将其缓存,创建我们看到的源码是new Resources(classLoader),resources.setImpl(impl) 而不同的版本可能是 new Resources(assets, dm, config, compatInfo) 具体请看6.0源码。

3. 加载皮肤资源



  如果大致知道了资源的加载流程以及Resource的创建过程,现在我们要去加载另外一个apk中的资源就好办了,只需要自己创建一个Resource对象,下面这段代码网上找一大堆,如果分析过源码相信你会有更深的认识:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Resources superRes = getResources();
            // 创建AssetManager,但是不能直接new所以只能通过反射
            AssetManager assetManager = AssetManager.class.newInstance();
            // 反射获取addAssetPath方法
            Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
            // 皮肤包的路径:  本地sdcard/plugin.skin
            String skinPath = Environment.getExternalStorageDirectory().getAbsoluteFile()+ File.separator+"plugin.skin";
            // 反射调用addAssetPath方法
            addAssetPathMethod.invoke(assetManager, skinPath);
            // 创建皮肤的Resources对象
            Resources skinResources = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
            // 通过资源名称,类型,包名获取Id
            int bgId = skinResources.getIdentifier("main_bg","drawable","com.hc.skin");
            Drawable bgDrawable = skinResources.getDrawable(bgId);
            // 设置背景
            findViewById(R.id.activity_main).setBackgroundDrawable(bgDrawable);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. AssetManager创建过程分析

  下面的分析希望不要有强迫症,看不懂其实也不打紧因为涉及到JNI。通过前面的分析可知,Android系统中实际对资源的管理是AssetManager类.每个Resources对象都会关联一个AssetManager对象,Resources将对资源的操作大多数委托给了AssetManager。当然有些源码还有一层 ResourcesImpl 刚刚我们也看到了。

  另外还会存在一个native层的AssetManager对象与java层的这个AssetManager对象相对应,而这个native层AssetManager对象在内存的地址存储在java层的AssetManager.mObject中。所以在java层AssetManager的jni方法中可以快速找到它对应的native层的AssetManager对象。

4.1 AssetManager的init()

     /**
     * Create a new AssetManager containing only the basic system assets.
     * Applications will not generally use this method, instead retrieving the
     * appropriate asset manager with {@link Resources#getAssets}.    Not for
     * use by applications.
     * {@hide}
     */
    public AssetManager() {
        synchronized (this) {
            if (DEBUG_REFS) {
                mNumRefs = 0;
                incRefsLocked(this.hashCode());
            }
            init(false);
            if (localLOGV) Log.v(TAG, "New asset manager: " + this);
            ensureSystemAssets();
        }
    }

   // ndk的源码路径
   // frameworks/base/core/jni/android_util_AssetManager.cpp
   // frameworks/base/libs/androidfw/AssetManager.cpp
   private native final void init(boolean isSystem);
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
    if (isSystem) {
        verifySystemIdmaps();
    }
    //  AssetManager.cpp
    AssetManager* am = new AssetManager();
    if (am == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", "");
        return;
    }

    am->addDefaultAssets();

    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);
    // framework/framework-res.apk
    // 初始化的时候会去加载系统的framework-res.apk资源
    // 也就是说我们为什么能加载系统的资源如颜色、图片、文字等等
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}

4.2 AssetManager的addAssetPath(String path)方法

bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
    asset_path ap;

    // 省略一些校验代码

    // 判断是否已经加载过了
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = static_cast<int32_t>(i+1);
            }
            return true;
        }
    }

    // 检查路径是否有一个androidmanifest . xml
    Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
            kAndroidManifest, Asset::ACCESS_BUFFER, ap);
    if (manifestAsset == NULL) {
        // 如果不包含任何资源
        delete manifestAsset;
        return false;
    }
    delete manifestAsset;
    // 添加
    mAssetPaths.add(ap);

    // 新路径总是补充到最后
    if (cookie) {
        *cookie = static_cast<int32_t>(mAssetPaths.size());
    }

    if (mResources != NULL) {
        appendPathToResTable(ap);
    }

    return true;
}

bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    // skip those ap‘s that correspond to system overlays
    if (ap.isSystemOverlay) {
        return true;
    }

    Asset* ass = NULL;
    ResTable* sharedRes = NULL;
    bool shared = true;
    bool onlyEmptyResources = true;
    MY_TRACE_BEGIN(ap.path.string());
    // 资源覆盖机制,暂不考虑
    Asset* idmap = openIdmapLocked(ap);
    size_t nextEntryIdx = mResources->getTableCount();
    ALOGV("Looking for resource asset in ‘%s‘\n", ap.path.string());
    // 资源包路径不是一个文件夹,那就是一个apk文件了
    if (ap.type != kFileTypeDirectory) {
        // 对于app来说,第一次执行时,肯定为0,因为mResources刚创建,还没对其操作
        // 下面的分支 指挥在参数是系统资源包路径时,才执行,
        // 而且系统资源包路径是首次被解析的
        // 第二次执行appendPathToResTable,nextEntryIdx就不会为0了
        if (nextEntryIdx == 0) {
            // mAssetPaths中存储的第一个资源包路径是系统资源的路径,
            // 即framework-res.apk的路径,它在zygote启动时已经加载了
            // 可以通过mZipSet.getZipResourceTable获得其ResTable对象
            sharedRes = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTable(ap.path);
            // 对于APP来说,肯定不为NULL
            if (sharedRes != NULL) {
                // 得到系统资源包路径中resources.arsc个数
                nextEntryIdx = sharedRes->getTableCount();
            }
        }
        // 当参数是mAssetPaths中除第一个以外的其他资源资源包路径,
        // 比如app自己的资源包路径时,走下面的逻辑
        if (sharedRes == NULL) {
            // 检查该资源包是否被其他进程加载了,这与ZipSet数据结构有关,后面在详细介绍
            ass = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTableAsset(ap.path);
            // 对于app自己的资源包来说,一般都会都下面的逻辑
            if (ass == NULL) {
                ALOGV("loading resource table %s\n", ap.path.string());
                // 创建Asset对象,就是打开resources.arsc
                ass = const_cast<AssetManager*>(this)->
                    openNonAssetInPathLocked("resources.arsc",
                                             Asset::ACCESS_BUFFER,
                                             ap);
                if (ass != NULL && ass != kExcludedAsset) {
                    ass = const_cast<AssetManager*>(this)->
                        mZipSet.setZipResourceTableAsset(ap.path, ass);
                }
            }
            // 只有在zygote启动时,才会执行下面的逻辑
            // 为系统资源创建 ResTable,并加入到mZipSet里。
            if (nextEntryIdx == 0 && ass != NULL) {
                // If this is the first resource table in the asset
                // manager, then we are going to cache it so that we
                // can quickly copy it out for others.
                ALOGV("Creating shared resources for %s", ap.path.string());
                // 创建ResTable对象,并把前面与resources.arsc关联的Asset对象,加入到这个ResTabl中
                sharedRes = new ResTable();
                sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.setZipResourceTable(ap.path, sharedRes);
            }
        }
    } else {
        ALOGV("loading resource table %s\n", ap.path.string());
        ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                                     Asset::ACCESS_BUFFER,
                                     ap);
        shared = false;
    }

    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
        ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
        // 系统资源包时
        if (sharedRes != NULL) {
            ALOGV("Copying existing resources for %s", ap.path.string());
            mResources->add(sharedRes);
        } else {
            // 非系统资源包时,将与resources.arsc关联的Asset对象加入到Restable中
            // 此过程会解析resources.arsc文件。
            ALOGV("Parsing resources for %s", ap.path.string());
            mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
        }
        onlyEmptyResources = false;

        if (!shared) {
            delete ass;
        }
    } else {
        mResources->addEmpty(nextEntryIdx + 1);
    }

    if (idmap != NULL) {
        delete idmap;
    }
    MY_TRACE_END();

    return onlyEmptyResources;
}

  大家应该之前了解过这个文件resources.arsc, 如果没了解过可以在网上找篇文章看一下。apk在打包的时候会生成它,我们解压apk就应该能够看到他。这里面基本都是存放的资源的索引,之所以不同的分辨率可以加载不同的图片它可是个大功臣。

5. 资源的查找过程

  现在我们回到最开始的loadDrawable()方法,drawable资源是有实际资源文件的。这类资源索引的过程大体上分为两个步骤,解析资源ID代表的资源的路径;装载资源文件并缓存。

  drawable是缓存到Resources.mDrawableCache中。加载drawable的时候,要先检查下这个缓存中是否有,有的话,直接返回,就不需要加载了。没有缓存的话,说明还没加载该资源文件,所以要先加载加载之后在缓存到mDrawableCache中。而loadDrawable()方法中又是通过loadDrawableForCookie()来加载drawable的:

    private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
        // drawable资源项的值是一个字符串,代表文件的路径
        if (value.string == null) {
            throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
                    + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
        }

        final String file = value.string.toString();

       .
        final Drawable dr;

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
        try {
            if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(this, rp, theme);
                rp.close();
            } else {
                // 如果drawable是图片文件的话,打开它
                // assetCookie-1就是图片所在的资源包路径在native层AssetManager.mAssetPaths数组中的索引
                // 下面这个方法就是打开这个文件了
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                dr = Drawable.createFromResourceStream(this, value, is, file, null);
                is.close();
            }
        } catch (Exception e) {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            final NotFoundException rnf = new NotFoundException(
                    "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
            rnf.initCause(e);
            throw rnf;
        }
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

        return dr;
    }

  到这里为止就彻底搞清楚资源的查找与加载过程了:索引+加载+缓存。

  所有分享大纲:2017Android进阶之路与你同行

  视频讲解地址:http://pan.baidu.com/s/1bC3lAQ

时间: 2024-08-01 23:36:28

插件式换肤框架搭建 - 资源加载源码分析的相关文章

springMVC容器加载源码分析

springmvc是一个基于servlet容器的轻量灵活的mvc框架,在它整个请求过程中,为了能够灵活定制各种需求,所以提供了一系列的组件完成整个请求的映射,响应等等处理.这里我们来分析下springMVC的源码. 首先,spring提供了一个处理所有请求的servlet,这个servlet实现了servlet的接口,就是DispatcherServlet.把它配置在web.xml中,并且处理我们在整个mvc中需要处理的请求,一般如下配置: <servlet> <servlet-name

Maxwin-z/XListView-Android(下拉刷新上拉加载)源码解析(一)

本次解析的内容,是github上一个用于下拉刷新上拉加载的控件xlistview,这个功能相信大家在开发的过程中会经常用到. 控件的源码地址是https://github.com/Maxwin-z/XListView-Android 在这个控件之前,我看过一些相同功能的控件,挑选后觉得XListView功能比较完善,而且易于理解.在android-open-project里面,有提到一个DropDownListView,个人使用过以后,觉得功能是具备了,但是操作体验不好,原因就是没有使用到Scr

Android插件化探索(二)资源加载

前情提要 在探索资源加载方式之前,我们先来看看上一篇中没细讲的东西.还没看过的建议先看上一篇Android插件化探索(一)类加载器DexClassLoader. PathClassLoader和DexClassLoader的区别 DexClassLoader的源码如下: public class DexClassLoader extends BaseDexClassLoader { //支持从任何地方的apk/jar/dex中读取 public DexClassLoader(String dex

从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用ApplicationPart动态加载控制器和视图

标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图作者:Lamond Lu地址:https://www.cnblogs.com/lwqlun/p/11137788.html源代码:https://github.com/lamondlu/Mystique 前言# 如果你使用过一些开源CMS的话,肯定会用过其中的的插件化功能,用户可以通过启用或者上传插件包的方式动态添加一些功能,那么在ASP.NET Core MVC中如

Android图片异步加载框架Universal Image Loader的源码分析

项目地址:https://github.com/nostra13/android-universal-image-loader 1. 功能介绍 1.1 Android Universal Image Loader Android Universal Image Loader 是一个强大的.可高度定制的图片缓存,本文简称为UIL. 简单的说 UIL 就做了一件事--获取图片并显示在相应的控件上. 1.2 基本使用 1.2.1 初始化 添加完依赖后在Application或Activity中初始化I

Agile框架之懒人加载源码剖析 (转)

大家都知道懒人加载可以让HTML5页面中的img图片可以在界面可见的时候进行显示,而不是一下子全部显示出来,极大的减缓了页面的加载效率和服务器的请求压力. 那么,在Agile中,通过给img设置data-source即可达到懒人加载的效果,这时候可以给img设置placeholder指明图片在请求完成之前显示的默认图片. 示例一: <li class="table-view-cell media"><a class="navigate-right"

Maxwin-z/XListView-Android(下拉刷新上拉加载)源码解析(二)

转载请注明出处http://blog.csdn.net/crazy__chen/article/details/45980399 源文件下载地址http://download.csdn.net/detail/kangaroo835127729/8736887 本文主要是贴出xlistview的源代码和一个使用实例,没有过多讲解 使用实例,MainActivity public class MainActivity extends Activity { private LinkedList<Str

*CI框架装载器Loader.php源码分析

http://www.bitscn.com/pdb/php/201411/404680.html 顾名思义,装载器就是加载元素的,使用CI时,经常加载的有: $this->load->library()$this->load->view()$this->load->model()$this->load->database()$this->load->helper()$this->load->config()$this->load

框架之认证接口的源码分析及自定义接口的使用

目录 rest_framework框架之认证的使用和源码实现流程 rest_framework框架之认证的使用和源码实现流程 一.认证功能的源码流程 (一).创建视图函数 Note 创建视图函数后,前端发起请求,url分配路由,执行视图类,视图类中执行对应方法必须经过dispatch()即调度方法 from rest_framework.views import APIView from django.shortcuts import HttpResponse import json class