Android资源的含义
Android使用xml文件描述各种资源,包括字符串、颜色、尺寸、主题、布局、甚至是图片(selector,layer-list)
资源可分为两部分
-
属性
在APK程序中,属性定义在
res/values/attrs.xml
中,在系统中属性位于framework/base/core/res/res/values/attrs.xml
文件中
<declare-styleable name="Window">
<attr name="windowBackground" format="reference"/>
<attr name="windowContentOverlaly" />
<attr name="windowFrame" />
<attr name="windowTitle" />
</declare-styleable>
styleable相当于一个属性集合,其在R.java文件中对应一个int[]数组,aapt为styleable中的每个attr(属性)分配一个id值,int[]中的每个id对应着styleable中的每一个attr。 <declare-styleable name="Window">
中的Window相当于属性集合的名称。
<attr name="windowBackground">
中的windowBackground相当于属性的名称
属性名称在应用程序范围内必须唯一
如果一个attr
后面仅仅有一个name
,那么这就是引用;如果不光有name
还有format
那就是声明。
windowBackground是属性的声明,其不能在其他styleable中再次声明;windowTitle则是属性的引用,其声明是在别的styleable中
- 值
常见的值一般有以下几种:
- String,Color,boolean,int类型:在
res/values/xxx.xml
文件中指定 - Drawable类型:在
res/drawable/xxx
中指定 - layout(布局):在
res/layout/xxx.xml
中指定 - style(样式):在
res/values/xxx.xml
中指定
值的类型大致分为两类,一类是基本类型,一类是引用类型
对于int,boolean等类型在声明属性时使用如下方式:
<attr name="width" format="integer"/>
<attr name="text" format="string" />
<attr name="centerInParent"="boolean"/>
对于Drawable,layout等类型在声明属性时:
<attr name="background" format="reference"/>
加载资源
在使用资源时首先要把资源加载到内存。Resources的作用主要就是加载资源,应用程序需要的所有资源(包括系统资源)都是通过此对象获取。一般情况下每个应用都会仅有一个Resources对象。
要访问资源先要获取Resources对象,常见的获取Resources的两种方法
使用Context获取
我们知道Context是一个抽象类,真正实现类是ContextImpl,所以调用的getResources()方法其实是在ContextImpl中的
@Override
public Resources getResources() {
return mResources;
}
看看在哪里赋值的
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
找调用setResources的地方
setResources(container.mResources);
发现在创建ContextImpl的时候构造函数中会调用这个方法
container.mResources向上查找,发现有一个ResourcesManager的类,进入里面查找创建Resources的方法,发现有一个getResources方法
public Resources getResources(
@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] legacyOverlayDirs,
@Nullable String[] overlayPaths,
@Nullable String[] libDirs,
@Nullable Integer overrideDisplayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader,
@Nullable List<ResourcesLoader> loaders) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
//计算哈希值
final ResourcesKey key = new ResourcesKey(
resDir, //代表资源文件所在路径,实际是指APK程序所在路径
splitResDirs,
combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
libDirs,
overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
overrideConfig,
compatInfo,
loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
if (overrideDisplayId != null) {
rebaseKeyForDisplay(key, overrideDisplayId);
}
Resources resources;
if (activityToken != null) {
Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
overrideDisplayId, classLoader, assetsSupplier);
} else {
//获取Resources对象
resources = createResources(key, classLoader, assetsSupplier);
}
return resources;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
如果一个应用程序没有访问该应用程序以外的资源,那么mActivieResources变量中就仅有一个Resources对象。当应用程序想要访问其他应用程序的资源则需要构建不同的ResourcesKey,也就是需要不同的resDir,毕竟每一个ResourcesKey对应一个Resources对象,这样该应用程序就可以访问其他应用程序中的资源。
进入createResources方法
private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
@Nullable ApkAssetsSupplier apkSupplier) {
synchronized (mLock) {
if (DEBUG) {
Throwable here = new Throwable();
here.fillInStackTrace();
Slog.w(TAG, "!! Create resources for key=" + key, here);
}
//创建一个ResourcesImpl对象
ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
if (resourcesImpl == null) {
return null;
}
return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
ResourcesImpl这个类包含 AssetManager 和与其关联的所有缓存。 资源只是这个类的包装器。 当发生配置更改时,客户端可以保留相同的 Resources 引用,因为底层 ResourcesImpl 对象将被更新或重新创建。
进入createResourcesLocked方法
private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
//创建Resources对象
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
//給resources设置ResourcesImpl对象
resources.setImpl(impl);
//设置callback
resources.setCallbacks(mUpdateCallbacks);
//把resources放入集合
mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
private final ArrayList mResourceReferences = new ArrayList<>();
到此通过context获取Resources流程大致走完。
使用PackageManager获取
这种方式主要是用来访问其他应用程序中的资源,最典型的就是切换主题,但这种主题一般仅限于一个应用程序内部
PackageManager是一个抽象类,真正实现类是ApplicationPackageManager。内部方法一般调用远程PackageManagerService
ApplicationPackageManager在构造时传入一个远程服务的引用IPackageManager,该对象是通过调用getPackageManager()静态方法获取的。这种获取远程服务的方法和大多数获取远程服务的方法类似。
进入ActivityThread类的getPackageManager方法
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
return sPackageManager;
}
final IBinder b = ServiceManager.getService("package");
sPackageManager = IPackageManager.Stub.asInterface(b);
return sPackageManager;
}
获得了PackageManager对象后,接着调用getResourcesForApplication()方法
@Override
public Resources getResourcesForApplication(@NonNull ApplicationInfo app,
@Nullable Configuration configuration) throws NameNotFoundException {
if (app.packageName.equals("system")) {
Context sysuiContext = mContext.mMainThread.getSystemUiContext();
if (configuration != null) {
sysuiContext = sysuiContext.createConfigurationContext(configuration);
}
return sysuiContext.getResources();
}
final boolean sameUid = (app.uid == Process.myUid
//创建Resources对象
final Resources r = mContext.mMainThread.getTopLevelResources(
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.overlayPaths, app.sharedLibraryFiles,
mContext.mPackageInfo, configuration);
if (r != null) {
return r;
}
throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
}
进入getTopLevelResources方法
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] legacyOverlayDirs,
String[] overlayPaths, String[] libDirs, LoadedApk pkgInfo,
Configuration overrideConfig) {
return mResourcesManager.getResources(null, resDir, splitResDirs, legacyOverlayDirs,
overlayPaths, libDirs, null, overrideConfig, pkgInfo.getCompatibilityInfo(),
pkgInfo.getClassLoader(), null);
}
进入ResourcesManager类的getResources方法
public Resources getResources(
@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] legacyOverlayDirs,
@Nullable String[] overlayPaths,
@Nullable String[] libDirs,
@Nullable Integer overrideDisplayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader,
@Nullable List<ResourcesLoader> loaders) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
//计算哈希值
final ResourcesKey key = new ResourcesKey(
resDir,//代表资源文件所在路径,实际是指APK程序所在路径
splitResDirs,
combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
libDirs,
overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
overrideConfig,
compatInfo,
loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
if (overrideDisplayId != null) {
rebaseKeyForDisplay(key, overrideDisplayId);
}
Resources resources;
if (activityToken != null) {
Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
overrideDisplayId, classLoader, assetsSupplier);
} else {
//创建resources对象
resources = createResources(key, classLoader, assetsSupplier);
}
return resources;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
发现两种方式最后都走到了ResourcesManager类的getResources方法中来创建Resources
进入createResources方法
private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
@Nullable ApkAssetsSupplier apkSupplier) {
synchronized (mLock) {
if (DEBUG) {
Throwable here = new Throwable();
here.fillInStackTrace();
Slog.w(TAG, "!! Create resources for key=" + key, here);
}
//创建一个ResourcesImpl对象
ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
if (resourcesImpl == null) {
return null;
}
return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
进入createResourcesLocked方法
private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
resources.setCallbacks(mUpdateCallbacks);
mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
进入Resources类的构造函数中
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
//创建ResourcesImpl对象
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
进入CompatResources类的构造函数中
//Version of resources generated for apps targeting <26.
public class CompatResources extends Resources {
private WeakReference<Context> mContext;
public CompatResources(ClassLoader cls) {
super(cls);
mContext = new WeakReference<>(null);
}
}
最后都进入Resources类中
public static Resources getSystem() {
synchronized (sSync) {
Resources ret = mSystem;
if (ret == null) {
ret = new Resources();
mSystem = ret;
}
return ret;
}
}
mSystem
是一个引用Resources
类自身实例的静态变量
进入Resources构造方法中
private Resources() {
this(null);
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
final Configuration config = new Configuration();
config.setToDefaults();
mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
new DisplayAdjustments());
}
查看ResourcesImpl的构造方法
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
}
创建ResourcesImpl
实例时传入的AssetManager
对象,是通过静态方法AssetManager.getSystem()
来获取的
Zygote
进程初始化的时候会调用Resources类的getSystem()方法
/**
* preloadResources 函数调用关系展示
*/
class ZygoteInit{
public static void main(String argv[]) {
preload();
}
static void preload(...) {
preloadResources();
}
private static void preloadResources() {
mResources = Resources.getSystem();
mResources.startPreloading();
......
preloadDrawables(ar);
......
preloadColorStateLists(ar);
......
mResources.finishPreloading();
}
private static int preloadDrawables() {
......
mResources.getDrawable(id, null);
......
}
private static int preloadColorStateLists() {
......
mResources.getColorStateList(id, null);
......
}
}
在Zygote
进程初始化时mSystem
就被赋值了。
所有的应用程序都是从Zygote
进程fork
而来的,这意味着所有进程中的mSystem
引用的Resource
实例在整个Android
系统中是共享的
加载App的资源
应用程序打包的最终文件是xxx.apk。APK本身是一个zip文件,可以使用压缩工具解压。系统在安装应用程序时首先解压,并将其中的文件放到指定目录。其中有一个文件名为_resources.arsc_,APK所有的资源均在其中定义。
_resources.arsc_是一种二进制格式的文件。aapt在对资源文件进行编译时,会为每一个资源分配唯一的id值,程序在执行时会根据这些id值读取特定的资源,而resources.arsc文件正是包含了所有id值得一个数据集合。在该文件中,如果某个id对应的资源是String或者数值(包括int,long等),那么该文件会直接包含相应的值,如果id对应的资源是某个layout或者drawable资源,那么该文件会存入对应资源的路径地址。
加载资源时,首先加载resources.arsc,然后根据id值找到指定的资源。
加载framework资源
统资源是在zygote进程启动时被加载的,并且只有当加载了系统资源后才开始启动其他应用进程,从而实现其他应用进程共享系统资源的目标。
启动第一步就是加载系统资源,加载完毕后再调用startSystemServer()启动系统进程,并最后调用runSelectLoopMode()开始监听Socket,并启动指定的应用进程。加载系统资源是通过preloadResources完成的
private static void preloadResources() {
try {
mResources = Resources.getSystem();
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
int N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_color_state_lists);
N = preloadColorStateLists(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
if (mResources.getBoolean(
com.android.internal.R.bool.config_freeformWindowManagement)) {
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resource in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
}
}
mResources.finishPreloading();
} catch (RuntimeException e) {
Log.w(TAG, "Failure preloading resources", e);
}
}
Resources.getSystem()创建Resources对象,一般情况下应用程序不应该调用此方法,因为该方法返回的Resources仅能访问Framework资源。
当Resources对象创建完成后,调用preloadDrawables()和preloadColorStateLists()装在需要”预装载”的资源
private static int preloadDrawables(TypedArray ar) {
int N = ar.length();
for (int i = 0; i < N; i++) {
int id = ar.getResourceId(i, 0);
if (id != 0) {
if (mResources.getDrawable(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded drawable resource #0x"
+ Integer.toHexString(id)
+ " (" + ar.getString(i) + ")");
}
}
}
return N;
}
private static int preloadColorStateLists(TypedArray ar) {
int N = ar.length();
for (int i = 0; i < N; i++) {
int id = ar.getResourceId(i, 0);
if (id != 0) {
if (mResources.getColorStateList(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded color resource #0x"
+ Integer.toHexString(id)
+ " (" + ar.getString(i) + ")");
}
}
}
return N;
}
在Resources类中,相关资源读取函数需要将读取到的资源缓冲起来,以便以后使用。
@UnsupportedAppUsage
private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
@UnsupportedAppUsage
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<>();
@UnsupportedAppUsage
private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
sPreloadedComplexColors = new LongSparseArray<>();
private boolean mPreloading;
当应用程序创建新的Resources对象时可以访问系统资源
mPreloading字段用来区分是zygote装在资源还是普通应用进程装在资源。因为zygote与普通进程装载资源的方式类似,所以增加mPreloaded变量进行区分。
mPreloaded在startPreloading()中被置为true,在finishPreloading()中被置为false,而startPreloading()和finishPreloading()正是在ZygoteInit.java的preloadResources()中被调用,这就区别了zygote调用和普通进程调用。
在Resources的具体资源读取方法中,会判断mPreloaded变量,如果为true,则同时把读取到的资源存储到三个静态列表中,否则把资源放到非静态列表中,这些非静态列表的作用范围为调用者所在进程。
loadDrawable方法的资源加载情况
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
if (value.density == density) {
value.density = mMetrics.densityDpi;
} else {
value.density = (value.density * mMetrics.densityDpi) / density;
}
}
try {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);
if (name != null) {
Log.d("PreloadDrawable", name);
}
}
}
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
cachedDrawable.setChangingConfigurations(value.changingConfigurations);
return cachedDrawable;
}
}
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
boolean needsNewDrawableAfterCache = false;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(wrapper, value, id, density);
}
if (dr instanceof DrawableContainer) {
needsNewDrawableAfterCache = true;
}
final boolean canApplyTheme = dr != null && dr.canApplyTheme();
if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
}
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
if (useCache) {
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
if (needsNewDrawableAfterCache) {
Drawable.ConstantState state = dr.getConstantState();
if (state != null) {
dr = state.newDrawable(wrapper);
}
}
}
}
return dr;
} catch (Exception e) {
String name;
try {
name = getResourceName(id);
} catch (NotFoundException e2) {
name = "(missing name)";
}
final NotFoundException nfe = new NotFoundException("Drawable " + name
+ " with resource ID #0x" + Integer.toHexString(id), e);
nfe.setStackTrace(new StackTraceElement[0]);
throw nfe;
}
}
进入cacheDrawable方法
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
}
if (mPreloading) {
final int changingConfigs = cs.getChangingConfigurations();
if (isColorDrawable) {
if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
sPreloadedColorDrawables.put(key, cs);
}
} else {
if (verifyPreloadConfig(
changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
sPreloadedDrawables[0].put(key, cs);
sPreloadedDrawables[1].put(key, cs);
} else {
sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
}
}
}
} else {
synchronized (mAccessLock) {
caches.put(key, theme, cs, usesTheme);
}
}
}
Framework包含了更多的资源,zygote所加载的仅仅是一小部分。对于那些非”预装载”的系统资源则不会被缓冲到静态列表变量中,这时应用进程如果需要一个非预装载资源则会在各自进程中保持一个资源缓冲。
最后
有小伙伴私信问Compose的问题,好不好用啊,现在要不要学啊?
其实答案很简单,自从谷歌2019年公布了声明式UI框架Jetpack Compose后,两年多的时间,各种大力宣传,和大量资源的倾斜,API功能都趋于稳定了。
至于好不好用,各种用过的同行都是持肯定态度的。优势大概就是这四点:
强大的工具和直观的Kotlin API
简化并加速了Android上的UI开发
可以帮助开发者用更少更直观的代码创建View
有更强大的功能,以及还能提高开发速度
这么大的优势,毋庸置疑,肯定是要学的嘛,而且越快掌握越好。别等刀架到脖子上了,才去练金钟罩。
至于怎么快速上手,可以给大家免费分享一份**《Jetpack Compose 完全开发手册》**,手把手教大家从入门到精通。
第一章 初识 Jetpack Compose
-
为什么我们需要一个新的UI 工具?
-
Jetpack Compose的着重点
加速开发
强大的UI工具
直观的Kotlin API
- API 设计
- Compose API 的原则
一切都是函数
顶层函数(Top-level function)
组合优于继承
信任单一来源
- 深入了解Compose
Core
Foundation
Material
- 插槽API
第二章 Jetpack Compose构建Android UI
- Android Jetpack Compose 最全上手指南
Jetpack Compose 环境准备和Hello World
布局
使用Material design 设计
Compose 布局实时预览
……
- 深入详解 Jetpack Compose | 优化 UI 构建
Compose 所解决的问题
Composable 函数剖析
声明式 UI
组合 vs 继承
封装
重组
……
- 深入详解 Jetpack Compose | 实现原理
@Composable 注解意味着什么?
执行模式
Positional Memoization (位置记忆化)
存储参数
重组
……
第三章 Jetpack Compose 项目实战演练(附Demo)
- Jetpack Compose应用1
开始前的准备
创建DEMO
遇到的问题
- Jetpack Compose应用2
- Jetpack Compose应用做一个倒计时器
数据结构
倒计时功能
状态模式
Compose 布局
绘制时钟
- 用Jetpack Compose写一个玩安卓App
准备工作
引入依赖
新建 Activity
创建 Compose
PlayTheme
画页面
底部导航栏
管理状态
添加页面
- 用Compose Android 写一个天气应用
开篇
画页面
画背景
画内容
……
- 用Compose快速打造一个“电影App”
成品
实现方案
实战
不足
……
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
有需要的话可以点下面二维码免费领取↓↓↓