前言
接上一章节,我们实现了 创建一个应用进程的完整流程,接下来从ActivityThread 的attach开始,我们尝试在linux环境下实现 onCreate的 setContentView 函数 和onResume 函数,我们不会用到binder等跨进程通信机制,尽量用简洁的代码复现 setContentView 的将xml文件装成DecorView为根节点的树形结构的过程。
从0开始实现 onCreate 的setContentView
一、ActivityThread 的入口main()
ActivityThread 的入口函数 创建了looper 消息循环。只调用了attach函数。
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
Looper.loop();
}
二、反射的方式创建Acitivty 实例
在android源码中 是通过binder调用 发送 BIND_APPLICATION 消息 走到
handleBindApplication ,我们简化这种方式。直接看是怎么反射Activity类.
public Activity newActivity(String className) {
Log.d(TAG, "newActivity className " + className);
ContextImpl context = new ContextImpl();
try {
Class<?> clazz = Class.forName(className);
Activity activity = (Activity) clazz.newInstance();
activity.attach(context );
return activity;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
源码用 ClassLoader.loadClass(className).newInstance()的方式来创建Activity, 效果是一样的。
三、在PhoneWindow实现 setContentView
setContentView 在onCreate函数中。 在我们自己的实现过程中,将输入参数替换成File。
public void setContentView(File file) {
Log.d(TAG, "setContentView");
if (mContentParent == null) {
installDecor();
}
mLayoutInflater.inflate(file, mContentParent);
}
1. installDecor
installDecor 的关键作用是 new DecorView。generateLayout 的作用和 inflate 一样。
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
protected DecorView generateDecor(int featureId) {
Context context;
context = getContext();
return new DecorView(context, featureId, this);
}
2. LayoutInflater.inflate
在实现 inflate 前,看下这个算法的基本原理。
例如 如果我们的 xml
如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/notice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="horizontal!"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:layout_below="@id/notice"
/>
</RelativeLayout>
<Button
android:id="@+id/portrait"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
/>
</LinearLayout>
转换下 图形
按照先序遍历的规则 , 遍历出的节点 是 LinearLayout - RelativeLayout -TextView-Button - Button。 LayoutInflater inflate 实现的也是这个经典的先序遍历 模型。
public class LayoutInflater {
final Object[] mConstructorArgs = new Object[2];
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
private static final String TAG ="LayoutInflater." ;
protected final Context mContext;
private static final String[] sClassPrefixList = {
"com.widget.",
"android.widget.",
"android.webkit.",
"android.app."
};
public static LayoutInflater from(Context context) {
LayoutInflater layoutInflater = new LayoutInflater(context);
return layoutInflater;
}
LayoutInflater(Context context) {
mContext = context;
}
public View inflate(File file, ViewGroup root) {
return inflate(file, root, root != null);
}
public View inflate(File file, ViewGroup root, boolean attachToRoot) {
try {
XmlPullParserFactory parserFactory = XmlPullParserFactory.newInstance();
XmlPullParser parser = parserFactory.newPullParser();
parser.setInput(new FileInputStream(file), "UTF-8");
return inflate(parser, root, attachToRoot);
} catch (Exception e) {
}
return null;
}
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
Log.d(TAG, "inflate");
View result = root;
try{
// 快速跳到根节点
advanceToRootNode(parser);
final String name = parser.getName();
final View temp = createViewFromTag(root, name, mContext, null);
rInflateChildren(parser, temp, null, true);
if( root == null ){
result = temp;
}
}catch (Exception e){
e.printStackTrace();
}finally {
}
return result;
}
private void advanceToRootNode(XmlPullParser parser) throws IOException, XmlPullParserException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
}
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
try {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws IOException, XmlPullParserException, ClassNotFoundException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG
|| parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
Log.d(TAG, " rInflate name "+name);
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(null);
rInflateChildren( parser, view , attrs, true);
viewGroup.addView( view, params);
}
}
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) throws ClassNotFoundException {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) throws ClassNotFoundException {
View view;
view = onCreateView(context, parent, name, attrs);
return view;
}
public View onCreateView( Context viewContext, View parent,
String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(parent, name, attrs);
}
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs){
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (Exception e) {
// In this case we want to let the base class take a crack
// at it.
return null;
}
}
return null;
}
public final View createView(String name, String prefix, AttributeSet attrs){
return createView(mContext, name, prefix, attrs);
}
public final View createView( Context viewContext, String name,
String prefix, AttributeSet attrs){
Class<? extends View> clazz = null;
Constructor<? extends View> constructor;
try{
clazz = Class.forName(prefix != null ? (prefix + name) : name).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
View view = constructor.newInstance( args );
return view;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
和实际结果相符。
2023-12-20 19:28:49 LayoutInflater.inflate
2023-12-20 19:58:58 LayoutInflater. name LinearLayout
2023-12-20 19:28:49 LayoutInflater. rInflate name RelativeLayout
2023-12-20 19:28:49 LayoutInflater. rInflate name TextView
2023-12-20 19:28:49 LayoutInflater. rInflate name Button
2023-12-20 19:28:49 LayoutInflater. rInflate name Button
总结
本文对 setContentView 函数 将xml 转成DecorView 的过程进行了简化。了解到解析xml文件实际上是先序遍历的使用。
代码实现上传到:
https://github.com/MrXiangVip/framework.git