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 文件夹下面
如何将添加的主题资源编译到固件里面去
在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</item> <item>textfield_active_holo_dark.9.png</item> <item>bookmarks_widget_thumb_selector_focused.9.png</item> <item>bookmarks_widget_thumb_selector_pressed.9.png</item> </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
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<Integer, Void, Void> { 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 里面有着类似以下对应的信息
<Module name="Browser" path="/system/app/Browser.apk"> <item>progress.9.png</item> |
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<Drawable.ConstantState> 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<Drawable.ConstantState>(cs)); } else { mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(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; 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<Integer, Void, Void> { 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; } |