Android系统预制资源在XML文件中引用的特殊语法定义如下:
//可以引用系统所有资源,public & private
@*android:type/name
//只能引用系统public的资源
@android:type/name
//注意:没在frameworks/base/core/res/res/values/public.xml(也就是<sdk_path>\platforms\android-X\data\res\values\public.xml)中申明的资源App时不推荐使用的。
Android在XML文件中引用当前主题属性的语法定义如下:
?[<package_name>:][<resource_type>/]<resource_name>
//资源值允许引用当前主题中的属性的值,这个属性值只能在style资源和XML中使用,随着当前主题的切换该值也在变换,该resource_name不需要自己定义,系统会自己在当前主题下寻找,常见的譬如动画中等。
Android在XML文件中创建或者引用资源语法定义如下:
//在R.java的type内部类中添加一条静态常量id资源标识符,如果标示符(包括系统资源)已经存在则表示引用该标示符。
@+type/name
//在R.java中寻找已经定义的标识符,如果找不到则提示失败错误,一般在xml中定义有先后关系。
@type/name
//所以一般推荐直接使用+号避免不必要的意外。
Android在XML文件中xmlns语法定义如下:
//xmlns(XML Namespaces)是XML的命名空间
//通用XML命名空间格式规则
xmlns:namespace-prefix=“namespaceURI”
在Android的XML中命名空间规则如下:
xmlns:namespace-prefix=http://schemas.android.com/apk/res/应用程序包路径
在使用时规则如下:
namespace-prefix:属性
切记,xmlns的定义必须放在最外层开始的的标记中,譬如我们Activity的xml文件的根布局中的android前缀、tools前缀、自定义View的前缀等。常见的例子如下:
//android即为frameworks/base/core/res/res/values/attrs.xml中的属性
xmlns:android=“http://schemas.android.com/apk/res/android”
//开发调试利器,不再过多说明
xmlns:tools=“http://schemas.android.com/tools”
//Email App中res/values/attrs.xml等自定义属性
xmlns:settings=“http://schemas.android.com/apk/res/com.android.email”
2-6 Android应用Theme、Style使用小结
到此关于Android应用中如何定义Theme、Style及使用和继承重写相信大家已经明白了,再出现诡异的现象就可以通过查询相关API及google结合就能完全理会其中的原因了,而不是停留在能搜到复制;下面一节我们将针对上面的这些使用进行粗略的源码分析说明。
【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】
3 源码结构浅析
============
有了上面的应用使用基础,下面的源码简单浅析可能存在跳跃性和经验性,不会像之前博客那样系统性的从头到尾进行分析,而是分点点到为止,感兴趣的同学可以自行深入研读。
3-1 追根溯源Theme、Style等根源
在我们App开发中通常我们会在新建工程后的AndroidManifest.xml文件中看见工程默认引用了应用包下自定义的主题@style/AppTheme(用法完全符合上一大节的规则)。该主题在当前应用包的style.xml中定义如下:
看着木有,它活生生的继承了Theme.AppCompat.Light.DarkActionBar这个style,这玩意又在framework的support v7包下res的themes.xml文件中,具体如下:
哈哈,原来如此,这里的Theme.Light你应该十分熟悉了吧(这就是以前我们App用的不是Support包,而是默认的时候,theme默认就是这玩意哈),这玩意就在framework的base下的themes.xml中定义着呢(所以通过了android:进行引用,留意细节吧),具体如下:
到这里我们就很容易明白啦,Theme.Light的父类原来是Theme哇,也在这个文件中,如下:
看注释吧,这货有接近400多个item属性,这也就是我们Android关于Theme的开山鼻祖了,在我们自定义时其实来这看比去API查还方便呢(其实需要两个互相配合,一个查,一个看解释,哈哈),因为它里面定义了关于我们整个应用中文字样式、按钮样式、列表样式、窗体样式、对话框样式等,这些样式都是默认样式,它还有很多我们常用的扩展样式,譬如Theme.Light、Theme.NoTitleBar、Theme.NoTitleBar.Fullscreen等等,反正你要有需求来这里搞就行。当我们继承使用时只用在前加上android:即可,有些属性可能是找不到的。同理,我们所谓的style、attr等等也都是这么个框架,大致位置也类似主题Theme的,所以这里不再过多说明,自行脑补即可。
3-2 Theme、Style等res资源客户化流程
对于纯App开发来说这一个知识点可以忽略,因为本小节需要大致了解Android源码的结构和编译框架,对于固件等开发来说这个还是比较重要的,记得以前做TV盒子开发时很多系统资源需要替换及添加,也就是说会稍微涉及到修改System UI及FW的res,那时候好坑爹,虽然修改的地方不多,只是换几个图标和加几个资源,但是那时候自己还是蒙圈了一段时间才搞明白,所以说有必要啰嗦几句。
首先我们先要明白设备里系统目录下的这些常见jar与apk的来源,如下:
| 名字 | 解释 |
| — | — |
| am.jar | 执行am命令所需的java lib,对应FW的base/cmds/am目录,具体可以参考下面的Android.mk定义。 |
| framework-res.apk | Android系统资源库集合,对应FW的core/res目录,具体同理参见Android.mk定义。 |
| framework.jar | Android SDK核心代码,对应FW的base目录,具体可以参考目录下的Android.mk的MOUDLE定义。 |
| SystemUI.apk | 从Android2.2开始状态栏和下拉通知栏被分割出一个单独的SystemUI.apk,一般在system的app或者priv-app下(还有很多其他模块呢,譬如SettingProvider等,具体可以在设备下看看),对应的源码在FW的packages下的SystemUI中。 |
| Others | 其他的jar比较多,不做一一介绍,不同厂商可能还会不同定制,具体可在厂商设备的system下看看有哪些包,对应回去通过Android.mk文件寻找即可。 |
| android.jar | 切记这个特例,这货是make sdk生成的,多方整合,别以为也可以找到对应目录,木有的!还有就是这个jar很实用的,很多时候我们想用AS直接调运系统的hide API等,自己编译一个就能派上用场啦! |
有了上边这几个和我们本文相关的核心常识后我们简单说下怎么修改编译:
-
修改FW/base/XXX/下面需要修改的代码;
-
单独在XXX下mm编译生成XXX.jar(apk);
-
把编译的jar(apk)包(在out目录对应路径下)push到设备系统system的FW目录下;
-
reboot重启设备验证;
不过这里有些坑大家要明白,我们在mm前最好每次都去清除对应out/obj目录下的中间文件,特别是资源文件更新时,否则容易被坑。还有就是切记添加系统API或者修改@hide的API或者添加资源(包含添加修改public.xml等)后,需要执行make update-api命令来同步base/api下的current.txt的修改,完事再make就行啦,这些编译文档都有介绍。
有了上面这些相信大家对于客户化资源也就有了一些认识啦,想想如果我们需要用到framework.jar的hide资源或者framework-res.apk中新加的资源时又不想用反射和源码下编译怎么办?当然是编译一个no hide的jar引入我们工程即可哇,要注意我们引入以后一定是Providered的模式,也就是该jar只编译不打包入该apk,还有就是依赖的先后优先级顺序,否则又用的是sdk默认的。还有就是万能的android.jar也是一种曲线救国的办法。当然啦,如果是SDK开发则完全可以复制一份自己搞,完事编译进系统即可,同时提供给App开发。
3-3 Theme、Style加载时机及加载源码浅析
前面我们介绍了Android的Theme、Style的定义及使用及Theme、Style等res的由来,这里我们来看看这些被使用的Theme的最终是何时、怎样被加载生效的。我们都知道对于Theme有两种方式来使用,具体如下(Style等attr在View的使用也比较同类,这里只分析Theme、其他的请在View等地自行分析脑补):
-
在AndroidManifest.xml中
<application>
或者<activity>
节点设置android:theme属性; -
在Java代码中调用setTheme()方法设置Activity的Theme(须在setContentView()前设置;
可以看见,这两种方式我们都比较常用,甚至有时候还会设置Window的一些属性标记,这些标记方法都在Window类中。我们平时在设置这些Theme时总是有很多疑惑,譬如为毛只能在setContentView()前设置等等,那么下面我们就来庖丁解牛一把。故事在开始之前可能还需要你自行脑补下《Android应用setContentView与LayoutInflater加载解析机制源码分析》与《Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析》两篇文章,完事再来继续下面的内容。
关于Activity通过setContentView方法设置View的来源这里就不多说了,参考前面两篇即可,我们直接跳到PhoneWindow的setContentView方法来看下,如下:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();//每个Activity第一次进来必走
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
…
}
我们接着来看下installDecor()方法,如下:
private void installDecor() {
if (mDecor == null) {
//仅仅new DecorView(getContext(), -1)而已,也就是FrameLayout
mDecor = generateDecor();
…
}
if (mContentParent == null) {
//生成我们布局的父布局
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
…
}
}
接着我们继续看看generateLayout(mDecor);这个方法,如下:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//获取当前主题,重点!!!!!!!
TypedArray a = getWindowStyle();
…
//解析一堆主题属性,譬如下面的是否浮动window(dialog)等
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
…
// Inflate the window decor.
//依据属性获取不同的布局添加到Decor
int layoutResource;
int features = getLocalFeatures();
// System.out.println(“Features: 0x” + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
…
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
…
return contentParent;
}
一样喽,继续先看下getWindowStyle()方法是神马鬼,这个方法在其基类Window中,如下:
/**
-
Return the {@link android.R.styleable#Window} attributes from this
-
window’s theme.
*/
public final TypedArray getWindowStyle() {
synchronized (this) {
if (mWindowStyle == null) {
mWindowStyle = mContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
}
return mWindowStyle;
}
}
哎,没啥好看的,没有逻辑,就是流程,继续跟吧,去Context类看看obtainStyledAttributes(com.android.internal.R.styleable.Window)方法吧,如下:
/**
- Return the Theme object associated with this Context.
*/
@ViewDebug.ExportedProperty(deepExport = true)
public abstract Resources.Theme getTheme();
/**
-
Retrieve styled attribute information in this Context’s theme. See
-
{@link android.content.res.Resources.Theme#obtainStyledAttributes(int[])}
-
for more information.
-
@see android.content.res.Resources.Theme#obtainStyledAttributes(int[])
*/
public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
//获取当前Theme对应的TypedArray对象
return getTheme().obtainStyledAttributes(attrs);
}
哎呦我去,憋大招呢,急死人了!可以看见Context的getTheme()方法时一个抽象方法,那他的实现在哪呢,看过《Android应用Context详解及源码解析》一文的同学一定知道对于Activity来说他的实现类就是ContextThemeWapprer,那我们赶紧进去看看它到底搞了啥玩意,如下:
@Override
public Resources.Theme getTheme() {
//一旦设置有Theme则不再走后面逻辑,直接返回以前设置的Theme
if (mTheme != null) {
return mTheme;
}
//没有设置Theme则获取默认的selectDefaultTheme
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
//初始化选择的主题,mTheme就不为null了
initializeTheme();
return mTheme;
}
@Override
public void setTheme(int resid) {
//通过外部设置以后mTheme和mThemeResource就不为null了
if (mThemeResource != resid) {
mThemeResource = resid;
//初始化选择的主题,mTheme就不为null了
initializeTheme();
}
}
我勒个去,憋大招总算憋出来翔了,ContextThemeWapprer才是重头戏啊,总算看见了光明了。这里的getTheme方法有一个判断,没有设置过Theme(mTheme为空)则通过Resources.selectDefaultTheme获取默认主题,否则用setTheme设置的主题。那么我们就来先看下假设没有设置主题,使用默认主题的方法,Resources.selectDefaultTheme如下:
/**
-
Returns the most appropriate default theme for the specified target SDK version.
-
- Below API 11: Gingerbread
- APIs 11 thru 14: Holo
- APIs 14 thru XX: Device default dark
- API XX and above: Device default light with dark action bar
-
@param curTheme The current theme, or 0 if not specified.
-
@param targetSdkVersion The target SDK version.
-
@return A theme resource identifier
-
@hide
*/
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
com.android.internal.R.style.Theme,
com.android.internal.R.style.Theme_Holo,
com.android.internal.R.style.Theme_DeviceDefault,
com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
/** @hide */
public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
int dark, int deviceDefault) {
if (curTheme != 0) {
return curTheme;
}
if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
return orig;
}
if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return holo;
}
if (targetSdkVersion < Build.VERSION_CODES.CUR_DEVELOPMENT) {
return dark;
}
return deviceDefault;
}
哎呀妈呀,这不就解释了我们创建不同版本的App时默认主题不一样的原因么,哈哈,原来如果我们没有设置主题Theme,系统会依据版本给我们选择一个默认的主题,也就是上面这段代码实现了该功能。
我们回过头继续回到ContextThemeWapprer的getTheme方法,当我们已经设置了Theme该方法就直接返回了,恰巧设置Theme的方法也在ContextThemeWapprer中。那这个方法啥时候被调运的呢?这一小节一开始我们就说了Activity的Theme设置有两种方法,主动通过Java调运setTheme()和在AndroidManifest文件配置,AndroidManifest文件配置的Theme又是啥时候调运的呢?有了前面几篇博客的铺垫,我想你也一定能找到的,就在ActivityThread的performLaunchActivity()方法中,也就是我们通过startActivity()方法启动Activity时就调运了Activity的setTheme方法,这个就不多说了,感兴趣的自己进去看下就行了,也是流程憋大招,最终调用了activity.setTheme()完成了AndroidManifest文件的Theme获取。
我们现在把目光回到ContextThemeWapprer的setTheme或者getTheme中调运的initializeTheme()方法中来看看,如下:
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
theme.applyStyle(resid, true);
}
//大招!!!!!!!
private void initializeTheme() {
//这就解释了为何setTheme必须在setContentView前调运,不多解释了,很明白了吧!!!!!!!!
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
这个方法就解释了为何setTheme必须在setContentView前调运。最终通过onApplyThemeResource调运Resources.Theme的方法进行了设置,如下:
/**
-
Place new attribute values into the theme. The style resource
-
specified by resid will be retrieved from this Theme’s
-
resources, its values placed into the Theme object.
-
The semantics of this function depends on the force
-
argument: If false, only values that are not already defined in
-
the theme will be copied from the system resource; otherwise, if
-
any of the style’s attributes are already defined in the theme, the
-
current values in the theme will be overwritten.
-
@param resId The resource ID of a style resource from which to
-
obtain attribute values.
-
@param force If true, values in the style resource will always be
-
used in the theme; otherwise, they will only be used
-
if not already defined in the theme.
*/
public void applyStyle(int resId, boolean force) {
AssetManager.applyThemeStyle(mTheme, resId, force);
mThemeResId = resId;
mKey.append(resId, force);
}
到此注释也说明了一些概念,关于AssetManager的应用又是另一个大话题了,这里先不展开讨论,我们只用知道到此一个Theme就选择完成了,还有就是一个Theme的是怎么被选择出来的,当然对于Dialog等Window的Theme也是一个样子,这里不多说明,感兴趣的自行脑补即可。
到现在为止我们已经找到了Theme是怎么来的了,下来我们需要回到我们这一小节开头部分的源码分析,也就是Resources的obtainStyledAttributes()方法,还记得我们最终传递了com.android.internal.R.styleable.Window进行获取该style么。这货不就是FW中res的attr.xml中自定义的属性么,如下:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?
Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。
以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
555293)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
[外链图片转存中…(img-AcOE7kpa-1713840555294)]
最后
感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?
Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。
所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。
[外链图片转存中…(img-Ea0rFYJF-1713840555295)]
以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
-