Android 的布局流程
不考虑AMS binder机制,那么Android 的布局流程的最开始的入口(把前面的当作一个黑盒子,那么后续动作的第一个入口),则是在ActivityThread的performLaunchActivity方法。activity的context也是在这个方法中初始化的。
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
//intent中有一些配置信息
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
这里看的是布局的原理,不是启动的原理,所以紧接着,要接着看布局方面的,接着找源码,找到了window的赋值
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
这里能看到,将window赋值为r.mPendingRemoveWindow;
然后接着追r.mPendingRemoveWindow的赋值,能看到它的赋值为
if (r.activity.mWindowAdded) {
if (r.mPreserveWindow) {
// Hold off on removing this until the new activity's
// window is being added.
r.mPendingRemoveWindow = r.window;
r.mPendingRemoveWindowManager = wm;
// We can only keep the part of the view hierarchy that we control,
// everything else must be removed, because it might not be able to
// behave properly when activity is relaunching.
r.window.clearContentView();
} else {
wm.removeViewImmediate(v);
}
}
然后紧接着看 r.window的赋值,发现它的值,就是引入了activity中的一个成员变量window
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
目前这个属性还未有值,只是拿到这个赋值。
它的值真正的赋值,是在下一步中,及activity的attach中
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
其中这个r指的是ActivityClientRecord,它里面的信息,是在上面提到的黑盒子里初始化的,目前看布局原理就先不考虑这个了。
在activity的attach方法中,可以看到mWindow的赋值
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
然后我们看布局放上去的方法,一般在activity的oncreate中的setcontentview中,点进去发现它调用的方法为
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
而getwindow即mWindow,也就是PhoneWindow
所以,查看它的相应方法
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
在第一步,进行了installDecor()跟进去接着看
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
.....
.....
然后点进generateLayout(mDecor)
它就是各种操作,根据不同值得到不同的layoutResource(系统内置的一些)然后加载,假如得到一个layoutResource之后,则会
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
在进入,就能看到
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
这个layoutResource还不是我们自己定义的layout,这是系统匹配出来的一个layoutResource
然后在通过addview的方式,也就把刚刚文件上的布局,放到了DecorView上了
**所以activity一打开,首先展示的就是这个layout**
其实看源码也相当于看一个树形结构,从一个分叉深入进去后,想要顺序看,还是得接着回到刚刚引入深入点的那个位置
然后将源码回退到phoneWindow中,接着往下看。看到
然后通过刚刚那一系列深入,能看出mContentParent其实也就是那个layout中的content。
mLayoutInflater.inflate(layoutResID, mContentParent);
然后点入inflate方法进去,看到很多inflate方法,但是最终,他都是会调到这个inflate里,即
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
所以,我们这里的root也就是刚刚传入过来的content控件
所以,整体结构相当于
image.png
然后在inflate方法中,接着看
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
attachToRoot这个参数,表示你是写代码add进去的view,还是从xml解析中进入的。通常写代码add的都是ture,从xml中进入的是false,所以,当为false的时候,会把params设置进去。
接着看createViewFromTag方法是怎么创建temp这个view的
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
onCreateView里,最后创建调用的还是createView
紧接着看createview中
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
首先 clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);是通过名字找到的clazz
然后在 constructor = clazz.getConstructor(mConstructorSignature);
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
所以,在自定义控件中,这个两个参数的构造函数一定不能少,它的初始化都是调用了这个两个参数的方法。随后就把这个构造方法和名字缓存起来了,放入了sConstructorMap
然后在这个方法的下面(源码没放全,毕竟都挺多的,放进去通篇都是源码了)
try {
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
通过newInstance得到这个view,然后进行return
所以,如果要实现换肤,这里是一条思路,可以在其中做些事情,通过自定义layoutInfalter,在createView方法中进行某些操作
还有一处,在createViewFromTag方法中,在view==null判断前,还有View view = tryCreateView(parent, name, context, attrs);操作,这个方法里用到了各种factory
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
从中可以很清楚的看到,当我们设置了factory或者factory2后(这俩工厂2继承1,然后2中的参数带有父控件,1不带),就可以直接对view进行初始化,也就可以隔断前面创建view的过程了。
所以,我们也可以通过设置factory来完成换肤,工厂内的oncreateview方法,抄源码然后做响应的自己需求的修改就好了。记得这个工厂的设置要放在setcontentview之前。很显而易见。。。
所以,View temp的创建这里,可以延伸出两种换肤方案。
资源文件
在C++层,会干一个事情,生成一个resource.arsc文件,类似于数据库文件,整个这个文件是一个二进制的文件。在这个文件中,标示了res这个路径下的每一个资源所对应的信息。
image.png
所以在我们加载一个apk包的时候,会有一个类去负责读这个文件的信息和这个res目录下的信息。
现在接着回ActivityThread里。。。找到handleBindApplication方法,找到资源加载的过程都在什么时间点
首先注意到这个仪表信息类,这个东西还是很重要的。很多事情要通过它来完成
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
然后初始化application
// Allow disk access during application and provider setup. This could
// block processing ordered broadcasts, but later processing would
// probably end up doing the same disk access.
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
app = data.info.makeApplication(data.restrictedBackupMode, null);
// Propagate autofill compat state
app.setAutofillOptions(data.autofillOptions);
// Propagate Content Capture options
app.setContentCaptureOptions(data.contentCaptureOptions);
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
// If the app targets < O-MR1, or doesn't change the thread policy
// during startup, clobber the policy to maintain behavior of b/36951662
if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
|| StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
StrictMode.setThreadPolicy(savedPolicy);
}
}
将application初始化之后,然后通过仪表类mInstrumentation调用它的oncreate方法 mInstrumentation.callApplicationOnCreate(app);
从创建application中点进去
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
这里先初始化来一个contextImpl,接着点进去
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
String opPackageName) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null, opPackageName);
context.setResources(packageInfo.getResources());
return context;
}
在这里有一句很重要的话
context.setResources(packageInfo.getResources());
从包信息中获取资源,也就是从apk中去拿资源
然后接着看里面具体是怎么获取资源的,点进去(LoadedApk类的getResources方法)
public Resources getResources() {
if (mResources == null) {
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
} catch (NameNotFoundException e) {
// This should never fail.
throw new AssertionError("null split not found");
}
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources;
}
然后从mResources的初始化中接着进去(到了ResourceManager类的getResources方法)
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
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);
}
}
重要的是 return getOrCreateResources(activityToken, key, classLoader);获取或者创建资源,再点进去
找到再往下的一个入口
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
再深入
private ActivityResources getOrCreateActivityResourcesStructLocked(
@NonNull IBinder activityToken) {
ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
if (activityResources == null) {
activityResources = new ActivityResources();
mActivityResourceReferences.put(activityToken, activityResources);
}
return activityResources;
}
(看源码中,看到在集合类里去取我们需要的东西,这个集合类刚开始一般都为空,一个套路,所以可以先暂时不看这个,往下看)
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
发现resourcesImpl还是从集合中去找,紧接着继续往下,找到了其初始化的位置
// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
此处通过放入一个key来创建了资源,这个key暂时还不知道是干什么的,然后我们接着深入这个方法里面去查看
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
会发现它里面给我们造了一个AssetManager,然后接着进入AssetManager的初始化中createAssetManager(key);
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*
* This can be overridden in tests so as to avoid creating a real AssetManager with
* real APK paths.
* @param key The key containing the resource paths to add to the AssetManager.
* @return a new AssetManager.
*/
@VisibleForTesting
@UnsupportedAppUsage
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
final AssetManager.Builder builder = new AssetManager.Builder();
// 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.
if (key.mResDir != null) {
try {
builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
try {
builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
Log.e(TAG, "failed to add split asset path " + splitResDir);
return null;
}
}
}
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
try {
builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
true /*overlay*/));
} catch (IOException e) {
Log.w(TAG, "failed to add overlay path " + idmapPath);
// continue.
}
}
}
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
try {
builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
// continue.
}
}
}
}
return builder.build();
}
builder.addApkAssets这个api的调用,就帮我们实现了资源文件的路径的加载(里面就是一个数据结构列表的添加,但是通过构造ApkAssets对象,其内部是native实现)
* The main implementation is native C++ and there is very little API surface exposed here. The APK
* is mainly accessed via {@link AssetManager}.
它中间最重要的是ApkAssets这个类,从这个类的注视中也能看出,它主要有native实现,通过构造方法中在进入nativeLoad
所以,也能得出,这个apk加载好以后,生成resources.arsc文件。所以换肤的策略,也可以使用assceManager这个api进行这个皮肤apk路径的添加,他就会把这个apk里的resources.arsc以及res信息加载进我们的apk中去
所以,我们的app就相当于一个宿主apk,然后皮肤的apk就相当于插件apk。(addApkAssets这个方法中加入插件apk相关信息)
它的整体封装结构为
image.png所以,我们平常在使用resource的时候,其内部就是调用到resourcesImpl里,然后调到mAssets这个量里面的方法。
所以我们AssetManager只干一个事情,就是它通过resource.arsc这个表找到对应的资源,然后再通过读流去取这些数据。所以ResourceImpl Resources相对AssetManager都是一个包装,包着它,顺便加了一些其他数据。从AssetManager内的一些加载方法中可以看出,它根据一些信息去获取这些资源,那么我们也就可以使用它来实现换肤的一些操作
所以,可以看出换肤的基本步骤为
- 制作皮肤包APK
- 收集XML数据
- 利用view的生产对象的过程Factory2接口(注意setFactory2中的实现,默认这个设置只能用一次,其内部的一个标识就会改变。所以可以通过反射去修改它的这个标识)
- 记录需要换肤的属性
- 读取皮肤包内容(记得先加载进来)不同版本,具体的api操作变了,记得区分
- 执行换肤操作
网友评论