mtk 主题功能学习笔记

1. 如何添加一套自己的主题

这里以Jb3为例:

添加资源

  在mediatek/frameworks/themes/ 下面添加一个新的主题文件夹.可以参看原有的主题theme-res-mint/theme-res-mocha/theme-res-raspberry/.

只要将对应图片资源放到对应的文件夹下面就可以了.

 

系统编译时, theme-res-xxx下面android.mk配置的

$(shell perl $(LOCAL_PATH)/copy_res.pl $(LOCAL_PATH))

会执行copy_res.pl 将theme-res-xxx 下面的资源统一拷贝到theme-res-xxx 下面的res 文件夹下面

mtk 主题功能学习笔记_第1张图片

如何将添加的主题资源编译到固件里面去

  在alps/build/target/product/common.mk文件中,找到MTK_THEMENANAGER_APP这个关键字,在如下这个判断中,添加自己的资源包的名字

     ifeq($(strip $(MTK_THEMEMANAGER_APP)),yes)

     PRODUCT_PACKAGES += theme-res-mint \

                              theme-res-mocha \

theme-res-raspberry \

                              theme-res-xxx

    endif

 

如果需要主题里面需要增加其他资源,比如: mipmap raw 等.

mipmap文件夹下的应用图标可以直接放在themes-res-xxxx相应应用程序的res/mipmap-xxxx下,修改copy-res.pl的 if (substr($mkdirinres, 0, 8) ne "drawable") {next;}添加mipmap的判断即可让目前的MTK ThemeManager识别到mipmap下的资源。

这是一种修改app icon的方法.这种方法不仅可以将launcher 里面显示的icon 改掉,还可以把从系统级别上起到效果.

如何添加额外的图片资源(默认的3个主题都没有的图片)

frameworks/base/data/etc/thememap.xml

frameworks/base/libs/androidfw/MTKThemeManager.cpp

 

查了一下源码,系统里面具体那些文件会根据主题的不同而不同的,是有什么决定的呢?图片资源和主题是如果一一映射的.

thememap.xml 里面配置了这些信息,下面举一个例子:

<Module name="Browser" path="/system/app/Browser.apk">

     <item>progress.9.png

     textfield_active_holo_dark.9.png

     bookmarks_widget_thumb_selector_focused.9.png

     bookmarks_widget_thumb_selector_pressed.9.png

Module>

上面就是Browser 浏览器和主题相关的图片资源了.

一个应用对应一个Module, name 的值就对应的编译过程中对应的LOCAL_PACKAGE_NAME, path 就是路径.

如何设定默认主题为mint,mocha或raspberry

以mint主题为例:

1,修改文件 frameworks/base/core/java/android/content/res/Configuration.java,将变量 SKIN_UNDEFINED 值赋为 "/system/framework/theme-res-mint.apk";

 

 2,修改文件 frameworks/base/libs/androidfw/AssetManager.cpp,修改变量 static const char* kDefaultThemePath的值为:

"/system/framework/theme-res-mint.apk".

mtk 的主题功能涉及到了那些代码

packages/apps/Settings/src/com/mediatek/thememanager

mtk 主题功能学习笔记_第2张图片

frameworks/base/libs/androidfw/MTKThemeManager.cpp

frameworks/base/data/etc/thememap.xml

ThemeManager.java

@Override

    public void onItemClick(AdapterView parent, View view,int position,long id) {

       mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);

        if (mCurrentPosition != position) {

            newSetThemeTask(this.getActivity()).execute(position);

            mCurrentPosition = position;

        } else {

           mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);

        }

    }

 

private class SetThemeTask extends AsyncTask {

        public SetThemeTask(Context context) {

            mContext = context;

        }

        @Override

        protected Void doInBackground(Integer... types) {

            int position = types[0];

            IActivityManager am = ActivityManagerNative.getDefault();

            try {

                // The mThemeDatas list will be cleared when onDestroy is called,

                // so null pointer exception would happens when calling mThemeDatas.get(position),

                // synchronized set theme and destroy to fix this issue.

                synchronized (mLock) {

                    if (mThemeDatas == null) {

                        Xlog.e(TAG"doInBackground error occured, mThemeDatas becomes null.");

                    }

                    Configuration config = am.getConfiguration();

                    config.skin =mThemeDatas.get(position).getThemePath()

                            .toString();

                    Xlog.d(TAG"doInBackground() am.updateConfiguration() config.skin = "

                            + config.skin);

                    am.updateConfiguration(config);

                }

            } catch (RemoteException e) {

                Xlog.e(TAG"Update configuration for theme changed failed.");

                e.printStackTrace();

            }

            return null;

具体可以继续查看ActivityManagerService.java 的相关方法

updateConfiguration(xxx),这个方法主要是修改persist.sys.skin 这个属性的值

具体原理的分析

谁来解析这个thememap .xml文件呢?

MTKThemeManager.cpp 类负责这个工作,没有想到是个cpp,本来以为是个java 类.应该是c++的解析速度更快.

主要是函数: void MTKThemeManager::parseThemeMapIfNeeded() 来完成这个工作.(为方便阅读,删除部分不重要的代码)

static const char* kThemeMapFile = "/system/etc/theme/thememap.xml";

static const char* kThemeModuleName = "Module";

static const char* kThemePathAttr = "path";

static const char* kThemeItemName = "item";

// kThemeMapFile 就是之前提到的参与到随主题功能切换的资源图片的文件

 

void MTKThemeManager::parseThemeMapIfNeeded()

{

    /* Load theme map xml file. */

    TiXmlDocument* pDoc = new TiXmlDocument(kThemeMapFile);

    pDoc->LoadFile(); //加载文件

 

    /* Get the root node(thememap) and the first module child node.*/

    TiXmlElement *pRootElement = pDoc->RootElement();

    TiXmlElement *pFirstModuleElement = pRootElement->FirstChildElement(kThemeModuleName);

 

    /* Get module node count to create the path map array.*/

int moduleCnt = getChildNodeCount(kThemeModuleName,pRootElement, pFirstModuleElement);

 //得到总的参与到主题功能里面的app 数量

 

    mPathMap = new ThemePathMap[moduleCnt];

   

    MTKThemeManager::mModuleCount = moduleCnt;

 

    TiXmlNode *pModuleNode = pFirstModuleElement;

    TiXmlNode *pItemNode = NULL;

    TiXmlNode *pFirstItemElement = NULL;

    int itemCnt = 0;

    int moduleIndex = 0;

    int tempIndex = 0;

 

    /* Parse the whole xml by module. */

    while (pModuleNode != NULL) {

        mPathMap[moduleIndex].path = ((TiXmlElement*)pModuleNode)->Attribute(kThemePathAttr);

   

        pFirstItemElement = pModuleNode->FirstChildElement(kThemeItemName);

        itemCnt = getChildNodeCount(kThemeItemName,pModuleNode, pFirstItemElement);

        mPathMap[moduleIndex].fileCount = itemCnt;

        if (itemCnt == 0) {

            mPathMap[moduleIndex].fileList = NULL;

            continue;

        }

 

// itemFileList  :保存解析的信息,可以供查询

        ThemeFileList *itemFileList = newThemeFileList[itemCnt];

 

        pItemNode = pFirstItemElement;

        tempIndex = 0;

        /* Parse all items in the current module pModuleNode. */

        while (pItemNode != NULL) {

            itemFileList[tempIndex++].fileName = ((TiXmlElement*)pItemNode)->GetText();

          

            pItemNode = (TiXmlElement *)pModuleNode->IterateChildren(kThemeItemName, pItemNode);

        }

 

        mPathMap[moduleIndex].fileList = itemFileList;

        moduleIndex++;

 

        pModuleNode = (TiXmlElement *)pRootElement->IterateChildren(kThemeModuleName, pModuleNode);

    }

}

 

 

又是谁调用了MTKThemeManager 的  parseThemeMapIfNeeded 方法:

在frameworks/base/libs/androidfw/AssetManager.cpp 里面可以看到

构造函数

AssetManager::AssetManager(CacheMode cacheMode)

    : mLocale(NULL), mVendor(NULL),

      mResources(NULL), mConfig(new ResTable_config),

      mCacheMode(cacheMode), mCacheValid(false)

{

    //……….

#if MTK_THEMEMANAGER_APP   

    mThemePath = new asset_path()

 #ifdef THEME_NO_BUILD

    ALOGV("THEME_NO_BUILD is set for host.");

 #else

      mThemeManager = MTKThemeManager::getInstance();

    mThemeManager->parseThemeMapIfNeeded();

 #endif

#endif

}

AssetManager  在构造函数里面调用了 mThemeManager->parseThemeMapIfNeeded() 来解析

thememap.xml .

 

继续跟踪AssetManager   里面还有哪里使用mThemeManager .

函数getAssetFromTheme  里面调用了mThemeManager mThemeManager->isFileInMap

mThemeManager->isFileInMap 就是判断文件fileName 的资源是否是在thememap.xml 里面有着类似以下对应的信息

progress.9.png

isFileInMap 会调用方法

findFileInList(ThemeFileList *fileList, int listLen, const char* fileName) 来查询该文件是否在thememap.xml 的映射范围之内

Asset* AssetManager::getAssetFromTheme(int which, const char* fileName,

        asset_path themePath, AccessMode mode) {

    Asset* pAsset = NULL;

    char* assetPath = (char*) mAssetPaths.itemAt(which).path.string();

    if (0 == strncmp(assetPath, kISmsPath, 22)) {

         assetPath = "/data/app/ISmsService.apk";

    }

   

    if (mThemeManager != NULL && mThemeManager->isFileInMap(assetPath,

            fileName)) { //判断文件是否在主题map 范围之内

        themePath.type = mAssetPaths.itemAt(which).type;

 

        if (which == 0x00) { // frameworks resource use default filename to access

            pAsset = openNonAssetInPathLocked(fileName, mode,themePath);

        } else { // applications resource

            char* apkFileName = (char*) malloc(strlen(fileName) + 60);

            memset(apkFileName, 0x00, strlen(fileName) + 60);

              appendApkPath(assetPath, fileName, apkFileName);

            pAsset = openNonAssetInPathLocked(apkFileName, mode,themePath);

            free(apkFileName);

            apkFileName = NULL;

        }

    }

 

    return pAsset;

}

 

同样逆推,会发现openNonAsset 调用了getAssetFromTheme

 

系统如何获取图片等资源

当我们调用Resources.java 来获取图片等资源的时候会调用getDrawable 方法, getDrawable最终把这还是使用loadDrawable 方法来处理.

Resources.java

public Drawable getDrawable(int id) throws NotFoundException {

TypedValue value;

synchronized (mAccessLock) {

            value = mTmpValue;

            if (value == null) {

                value = new TypedValue();

            } else {

                mTmpValue = null;

            }

            getValue(id, value, true);

   }

 Drawable res = loadDrawable(value, id);

synchronized (mAccessLock) {

            if (mTmpValue == null) {

                mTmpValue = value;

            }

}

return res;

}

 

loadDrawable 方法在获取资源的时候会先判断该资源是否在缓存中,如果在缓存中就直接获取返回,如果没有就继续查询.同时使用color配置的图片的资源也会先去缓存中读取,这样可以提高代码的效率.如果还是没有找到就会调用AssetManager.java 的openNonAsset 方法,再接着分析会发现调用的是Jni 的一个方法openNonAssetNative,Jni 最终会去调用AssetManager.cpp 的openNonAsset的方法来根据不同主题去获取对应的资源.

 

frameworks/base/core/jni/android_util_AssetManager.cpp  àopenNonAssetNative

static jint android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz,

jint cookie,

jstring fileName,

jint mode)

{

    AssetManager* am = assetManagerForJavaObject(env, clazz);

if (am == NULL) {

return 0;

}

 

    ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz);

 

ScopedUtfChars fileName8(env, fileName);

if (fileName8.c_str() == NULL) {

return -1;

}

 

if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM

&& mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {

jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");

return -1;

}

 

    Asset* a = cookie

        ? am->openNonAsset((void*)cookie, fileName8.c_str(), (Asset::AccessMode)mode)

        : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode);

 

if (a == NULL) {

jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());

return -1;

}

 

//printf("Created Asset Stream: %p\n", a);

 

return (jint)a;

}

 

Resources.java

Drawable loadDrawable(TypedValue value, int id)

            throws NotFoundException {

 

         if (TRACE_FOR_PRELOAD) {

                   // Log only framework resources

                   if ((id >>> 24) == 0x1) {//framework resources id 是以0x1 开头, mediatek Resource id 是一0x2 开头3方资源一般以0x7 开头

                            final String name = getResourceName(id);

                            if (name != null) android.util.Log.d("PreloadDrawable", name);

                   }

         }

 

         boolean isColorDrawable = false;

         if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&

                            value.type <= TypedValue.TYPE_LAST_COLOR_INT) {

                   isColorDrawable = true;

         }

         final long key = isColorDrawable ? value.data :

                            (((long) value.assetCookie) << 32) | value.data;

 

         Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);//从缓存中读取,如果缓存中已经存在,就直接获取并返回,缓存中没有的话就继续执行下面的代码去查询获取对应的资源文件.

 

         if (dr != null) {

                   return dr;

         }

         Drawable.ConstantState cs = null;

 

         /// M: Theme manager @{

         Boolean checkPreload = true;

         if (FeatureOption.MTK_THEMEMANAGER_APP) {

                   String skin= Configuration.getSkin();

                   if ((skin != null) &&

                            !(skin.equals(sDefaultSkin))){

                            checkPreload = false;

                   }

         }

         /// @}

 

//使用color配置的图片可以也直接从缓存中读取,提高效率.

         if (checkPreload) {/// M: Theme manager

                   if (isColorDrawable) {

                            cs = sPreloadedColorDrawables.get(key);

                   } else {

                            cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);

                   }

         }/// M: Theme manager

 

         if (cs != null) {

                   dr = cs.newDrawable(this);

         } else {

                   if (isColorDrawable) {

                            dr = new ColorDrawable(value.data);

                   }

 

                   if (dr == null) {

                            if (value.string == null) {

                                     throw new NotFoundException(

                                                        "Resource is not a Drawable (color or path): " + value);

                            }

 

                            String file = value.string.toString();

 

                            if (TRACE_FOR_MISS_PRELOAD) {

                                     // Log only framework resources

                                     if ((id >>> 24) == 0x1) {

                                               final String name = getResourceName(id);

                                               if (name != null) android.util.Log.d(TAG, "Loading framework drawable #"

                                                                 + Integer.toHexString(id) + ": " + name

                                                                 + " at " + file);

                                     }

                            }

 

                            if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "

                                               + value.assetCookie + ": " + file);

 

                            if (file.endsWith(".xml")) {

                                     Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);

                                     try {

                                               XmlResourceParser rp = loadXmlResourceParser(

                                                                 file, id, value.assetCookie, "drawable");

                                               dr = Drawable.createFromXml(this, rp);

                                               rp.close();

                                     } catch (Exception e) {

                                               Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

                                               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);

 

                            } else {

                                     Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);

                                     try {

                                               InputStream is = mAssets.openNonAsset(

                                                                 value.assetCookie, file, AssetManager.ACCESS_STREAMING);

         //System.out.println("Opened file " + file + ": " + is);

                                               dr = Drawable.createFromResourceStream(this, value, is,

                                                                 file, null);

                                               is.close();

         //System.out.println("Created stream: " + dr);

                                     } catch (Exception e) {

                                               Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

                                               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);

                            }

                   }

         }

 

         if (dr != null) {

                   dr.setChangingConfigurations(value.changingConfigurations);

                   cs = dr.getConstantState();

                   if (cs != null) {

                            if (mPreloading) {

                                     final int changingConfigs = cs.getChangingConfigurations();

                                     if (isColorDrawable) {

                                               if (verifyPreloadConfig(changingConfigs, 0, value.resourceId,

                                                                 "drawable")) {

                                                        sPreloadedColorDrawables.put(key, cs);

                                               }

                                     } else {

                                               if (verifyPreloadConfig(changingConfigs,

                                                                 LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {

                                                        if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) {

                                                                 // If this resource does not vary based on layout direction,

                                                                 // we can put it in all of the preload maps.

                                                                 sPreloadedDrawables[0].put(key, cs);

                                                                 sPreloadedDrawables[1].put(key, cs);

                                                        } else {

                                                                 // Otherwise, only in the layout dir we loaded it for.

                                                                 final LongSparseArray preloads

                                                                                    = sPreloadedDrawables[mConfiguration.getLayoutDirection()];

                                                                 preloads.put(key, cs);

                                                        }

                                               }

                                     }

                            } else {

                                     synchronized (mAccessLock) {

                                               //Log.i(TAG, "Saving cached drawable @ #" +

                                               //        Integer.toHexString(key.intValue())

                                               //        + " in " + this + ": " + cs);

                                               if (isColorDrawable) {

                                                        mColorDrawableCache.put(key, new WeakReference(cs));

                                               } else {

                                                        mDrawableCache.put(key, new WeakReference(cs));

                                               }

                                     }

                            }

                   }

         }

 

         return dr;

}

 

系统如何根据不同主题来选择对应的资源

应用层获取资源的时候,不管当前系统使用的那个主题,同一个图片资源的id都是一样的,当前系统是如何根据这个id 来返回对应的图片资源呢?

 

系统在获取资源的时候,会根据是否选择了其他的主题来修改获取对应图片的Asset 对象

AssetManager.cpp

Asset* AssetManager::openNonAsset(void* cookie, const char* fileName,AccessMode mode)

{

    const size_t which = ((size_t)cookie)-1;

#if MTK_THEMEMANAGER_APP  

memset(kThemeApkPath, 0x00, 100);

//获取当前的主题.java 里面可以使用Configuration 的 getSkin方法

    property_get("persist.sys.skin", kThemeApkPath, kDefaultThemePath);

    mThemePath->path.setTo(kThemeApkPath);

#endif

    AutoMutex _l(mLock);

    if (mCacheMode != CACHE_OFF && !mCacheValid)

        loadFileNameCacheLocked();

#if MTK_THEMEMANAGER_APP

    if (which < mAssetPaths.size()) {

        Asset* pAsset = getAssetFromTheme(which, fileName, *mThemePath,mode);

//获取主题对应Asset 对象,如果对应的图片文件不在主题所修能修改文件(Map)里面就会返回null.

        if (pAsset == NULL) {

            pAsset = openNonAssetInPathLocked(

                    fileName, mode, mAssetPaths.itemAt(which));

        }

        if (pAsset != NULL) {

            return pAsset != kExcludedAsset ? pAsset : NULL;

        }

    }

#else

   //……….

#endif

    return NULL;

}

 

AssetManager.cpp

Asset* AssetManager::getAssetFromTheme(int which, const char* fileName,

                   asset_path themePath, AccessMode mode) {

         Asset* pAsset = NULL;

char* assetPath = (char*) mAssetPaths.itemAt(which).path.string();

if (0 == strncmp(assetPath, kISmsPath, 22)) {

assetPath = "/data/app/ISmsService.apk";

    }

   

    if (mThemeManager != NULL && mThemeManager->isFileInMap(assetPath,

                            fileName)) {

                   ALOGV("filename mapped success which = %d, fileName = %s.\n", which,

                                     fileName);

                   themePath.type = mAssetPaths.itemAt(which).type;

 

                   if (which == 0x00) { // frameworks resource use default filename to access

                            pAsset = openNonAssetInPathLocked(fileName, mode, themePath);

                   } else { // applications resource

                            char* apkFileName = (char*) malloc(strlen(fileName) + 60);

                            memset(apkFileName, 0x00, strlen(fileName) + 60);

              appendApkPath(assetPath, fileName, apkFileName);

                            pAsset = openNonAssetInPathLocked(apkFileName, mode, themePath);

                            free(apkFileName);

                            apkFileName = NULL;

                   }

         }

 

         return pAsset;

}

 

同时在frameworks/base/core/java/android/content/res/Resources.java 里面发现Resources.java也可以根据资源的类型和当前主题来调用对应的资源.

public Bitmap getThemeImage(String themePackagePath, String itemType) {

    Bitmap bitmap = null;

    int cookie = 0;       

//………………………..

    cookie = getAssets().addAssetPath(themePackagePath);

    StringBuilder themeDrawablePath = new StringBuilder("res/drawable-");

themeDrawablePath.append(generateCurrentDensitySuffix());       

    themeDrawablePath.append(itemType + ".png");

    //……….

    try {               

        InputStream is = getAssets().openNonAsset(cookie, themeDrawablePath.toString());

        bitmap = BitmapFactory.decodeStream(is);

    } catch (IOException e) {

        Log.e(TAG, "IOException happend when getThemeImage cookie = " + cookie);

    }

    return bitmap;

}

 

Framework java层和C++ 层是如何工作的。

AssetManager.java 和 AssetManager.cpp 之间的关联是通过C++文件android_util_AssetManager.cpp 来实现的。

 

frameworks/base/core/jni/android_util_AssetManager.cpp

设置应是如何把用户选择的主题通知到系统

用户单击主题item 的时候,会调用异步更新类SetThemeTask 来通知framework 层来更新AssetManager ,这样在系统调用资源的时候,如果该资源是在主题功能范围之内就会使用主题里面的资源,而不去默认的应用里面找。

ThemeManager.java

@Override

public void onItemClick(AdapterView parent, View view, int position,long id) {

   mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);

   if (mCurrentPosition != position) {

      new SetThemeTask(this.getActivity()).execute(position);//根据实际选择的theme 来通知framework.

      mCurrentPosition = position;

   } else {

      mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);

   }

}

 

private class SetThemeTask extends AsyncTask {

    public SetThemeTask(Context context) {

       mContext = context;

    }

 

    @Override

    protected Void doInBackground(Integer... types) {

       int position = types[0];

       IActivityManager am = ActivityManagerNative.getDefault();

       try {

           // The mThemeDatas list will be cleared when onDestroy is called,

           // so null pointer exception would happens when calling mThemeDatas.get(position),

           // synchronized set theme and destroy to fix this issue.

           synchronized (mLock) {

              if (mThemeDatas == null) {

                  Xlog.e(TAG, "doInBackground error occured, mThemeDatas becomes null.");

              }

              Configuration config = am.getConfiguration();

              config.skin = mThemeDatas.get(position).getThemePath()

                     .toString();

              Xlog.d(TAG, "doInBackground() am.updateConfiguration() config.skin = "

                     + config.skin);

              am.updateConfiguration(config); // 调用这个方法更新config.skin 之后,会导致重新调用oncreate() 方法。

           }

       } catch (RemoteException e) {

           Xlog.e(TAG, "Update configuration for theme changed failed.");

           e.printStackTrace();

       }

       return null;

    }

 

    @Override

    protected void onPreExecute() {

       showSetThemeDialog();

    }

 

    @Override

    protected void onPostExecute(Void unused) {

       mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);

    }

 

    private Context mContext;

}

 

你可能感兴趣的:(Android,源码分析)