1.Android程序流程
众所周知,我们的java程序想要开启需要依赖于main方法,也就是我们的程序入口(主线程)进入,但是在我们日常开发android程序的过程当中我们并没有发现main方法的存在,那么android当中的是如何开始运行的?
熟悉的朋友们可能都知道在android当中存在一个叫做ActivityThread的类,这个类代表的是android当中的主线程,而在这个类当中我们看到了比较熟悉的main方法,那么现在是否可以认为我们的android在打开app时是首先调用的是当前这个类的main,也就是此处为我们的启动点:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
这里省略掉部分代码...............
Looper.prepareMainLooper();//这里开启主线程的Looper创建
ActivityThread thread = new ActivityThread();//主函数再次开启一个Activity线程
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();//这里开启主线程的消息轮训
throw new RuntimeException("Main thread loop unexpectedly exited");
}
OK,我们开始进入thread.attach(false)这个方法中查看做了什么:
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
这里省略掉部分代码...............
RuntimeInit.setApplicationObject(mAppThread.asBinder());
//这个IActivityManager是Activity管理接口,
final IActivityManager mgr = ActivityManager.getService();那么这个mgr得到的到底是什么?继续看下一步
try {
//这里调用attachApplication()这个方法
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
} else {
这里省略掉部分代码...............
}
// add dropbox logging to libcore
DropBox.setReporter(new DropBoxReporter());
}
进入ActivityManager中查看getService()这个方法:
/**
* @hide
*/
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
在这个当中,里面调用了的系统的ActivityManagerService这个服务,并且给出了一个Binder接口
了解Binder机制的小伙伴看到这里应该很眼熟,在图中的create方法中进行了一个跨进程的相关操作
1:获得系统的IBinder的实现类
2:将系统(服务端)的Binder对象转换成客户端可以使用的接口类对象
也就是说getService这个方法所返回的对象其实就是和系统进行跨进程通信的,从对象的类名称可知是Activity的管理类。mgr通过attachApplication方法和mAppThread进行了绑定,mAppThread的类是ApplicationThread,父类为IApplicationThread.Stub,和Binder中的Stub的作用一样,用来接收系统(服务端)发过来的跨进程消息。这也是为什么我们讲Activity是跨进程访问的原因。
attachApplication方法另外一个作用是在这里的作用其实实际上是ActivityThread通过attach获取到后,然后将applciationThread将其关联,把activity相关信息存储在applciationThread里面,apllicationThread的类为activity的各种状态做了相对应的准备工作。如下为attachApplication方法的具体源码:
这个时候我们需要关注,ApplicationThread(ApplicationThread这个类是在ActivityThread中的内部类)当中做了什么?
当我们打开ApplicationThread中我们会看到一堆的schedle方法,这些方法的名称其实就可以给我们表明,代表的是在执行Activity的某种状态时调用的计划执行方法,这时我们会看到一个scheduleLaunchActivity方法,表示计划加载时调用的。这里我们发现了一个很有意思的事情:
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();//这个对象就是我们的Activity
r.token = token;
r.ident = ident;
r.intent = intent;
//...一系列的赋值操作
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);//将当前我们创建的Activity发送了出去
}
当走到这里我们会发现最终我们调用的是Handler的消息通信机制,也就是说,在这里我们可以总结一下,当Activity状态改变时,都会有对应的一个消息发送出去,而接收这里,我能发现通过发送时不同的状态,这边调用了不同的handlerXXXActivity方法。
在ActivityThread类中有一个Handler类名为H,该类就是用来处理不同的发送消息,以便改变Activity状态等操作的,如下:
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");//进行Activity启动的处理
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY: {
// .....
} break;
//....等等一系列的判断处理操作
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
进入handleLaunchActivity方法中发现有个调用了performLaunchActivity方法:
ClassLoader cl = appContext.getClassLoader();
activity = this.mInstrumentation.newActivity(cl, component.getClassName(), r.intent);//创建了一个Activity对象
继续往下看,mInstrumentation.callActivityOnCreate(...)看到这里,我们貌似发现了Activity的生命周期的调用痕迹,那么其实到此为止,我门可以得出一个结论,Application运行的过程当中,对于Activity的操作,状态转变,其实实际上是通过Handler消息机制来完成的,Application当中只管去发, 由消息机制负责调用,因为在main方法当中我门的Looper轮训器是一直在进行轮训的。而当我们在加载Activity的时候,当中调用了一个performLaunchActivity()方法,在这个中间我发现了我们onCreate的调用痕迹如下:
private Activity performLaunchActivity(ActivityThread.ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if(r.packageInfo == null) {
r.packageInfo = this.getPackageInfo((ApplicationInfo)aInfo.applicationInfo, r.compatInfo, 1);
}
//.............
if(r.isPersistable()) {
this.mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
this.mInstrumentation.callActivityOnCreate(activity, r.state);
}
//..........
}
那么这个mInstrumentation是什么呢?
每一个ActivityThread对象都有一个Instrumentation mInstrumentation;成员变量。mInstrumentation的初始化在ActivityThread,handleBindApplication函数中在callActivityOnCreate,callApplicationOnCreate,newActivity等基本上在application和activity的所有生命周期调用中,都会先调用instrumentation的相应方法。并且针对应用内的所有activity都生效。Instrumentation为程序员提供了一个强大的能力,有更多的可能性进入android app框架执行流程。
也就是说,到目前为止我们能够明白,整个Application加载Activity的整套流程是怎么回事,那么接下来我们需要关注的是,在onCreate当中我们所写的setContentView到底干了什么
2.setContentView
getWindow()方法获取的就是Window对象,
Window类上有一句注释,大概的意思是:这个Window的实现类有且只有一个实现类,那就是PhoneWindow。所以说,setContentView方法实际上市PhoneWindow来调用的,查看源码通过观察源码:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//这个getWindow()方法获取的就是Window对象,而只有一个实现类就是PhoneWindow类。
initWindowDecorActionBar();
}
这个时候通过一系列线索我找到了最终的位置PhoneWindow类,进入phoneWindow中找到setContentView方法如下:
public void setContentView(int layoutResID) {
if(this.mContentParent == null) {
this.installDecor();
} else if(!this.hasFeature(12)) {
this.mContentParent.removeAllViews();
}
if(this.hasFeature(12)) {
Scene newScene = Scene.getSceneForLayout(this.mContentParent, layoutResID, this.getContext());
this.transitionTo(newScene);
} else {
this.mLayoutInflater.inflate(layoutResID, this.mContentParent);
}
this.mContentParent.requestApplyInsets();
//...
}
这个时候我们会看到他做了两个事情,一个是installDecor,另一个是inflate,这两个后一个不难猜出他是在进行布局文件的解析, 前面的我们认为她是在初始化某个东西:
private void installDecor() {
this.mForceDecorInstall = false;
if(this.mDecor == null) {
this.mDecor = this.generateDecor(-1);
this.mDecor.setDescendantFocusability(262144);
this.mDecor.setIsRootNamespace(true);
if(!this.mInvalidatePanelMenuPosted && this.mInvalidatePanelMenuFeatures != 0) {
this.mDecor.postOnAnimation(this.mInvalidatePanelMenuRunnable);
}
} else {
this.mDecor.setWindow(this);
}
if(this.mContentParent == null) {
this.mContentParent = this.generateLayout(this.mDecor);
this.mDecor.makeOptionalFitsSystemWindows();
DecorContentParent decorContentParent = (DecorContentParent)this.mDecor.findViewById(16908823);
//.....
}
}
进来之后发现他初始化了两个东西,一个叫做mDecor,一个叫做mContentParent
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
我们看到了,1:mDecor是一个DecorView
2:mContentParent是一个ViewGroup
透过注释的翻译,其实我们就能很明确知道这两个是用来干嘛的
// This is the view in which the window contents are placed. It is either(这是窗口内容放置的视图)
// mDecor itself, or a child of mDecor where the contents go.(它要么是mDecor本身,要么是mDecor的子类的内容。)
//This is the top-level view of the window, containing the window decor.(这是在窗口当中的顶层View,包含窗口的decor)
一个代表的是顶层view,一个用来装他下面的视图内容
在接着往下看的时候,我门发现,generateLayout方法当中,发现了在此处进行了大量的requestFeature的调用,也就是说,我们的requestFeature设置其实是在setContentView方法当中就开始了, 这也是为什么我们自己要去getWindow.requestFeature时必须在setContent之前的原因:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
if (false) {
System.out.println("From style:");
String s = "Attrs:";
for (int i = 0; i < R.styleable.Window.length; i++) {
s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
+ a.getString(i);
}
System.out.println(s);
}
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
}
然后在下面我门会发现在做了一件事情,
// Inflate the window 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;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
//.....一系列判断赋值操作
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;//该布局就是下面的xml布局文件,也是Activity出事化后最初的界面
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
再次进入onResourcesLoaded方法中去:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
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();
}
当前这里竟然在加载布局文件,并且生成了一个view, 但是好像貌似不是我门自己的,所以我们需要去探寻他到底加载了一个什么布局?点击R.layout.screen_simple进入
This is an optimized layout for a screen, with the minimum set of features enabled. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>
这是我找到了一个比较有意思的组件,在这个上面我看到了一句这样的注释
//This is an optimized layout for a screen, with the minimum set of features enabled.
这是一个屏幕的优化布局,具有最小的特征集启用。通过注释和一些资料分析, 得到了一个比较坑的结果。从网络上查找得出如下图:
这是DecorView默认的一个渲染,然后我门自己的布局都是渲染到她的FrameLayout上的。
那么在这里我门现在能够明白,installDector其实实际上是在初始化两个视图容器,然后加载系统的R资源及特征,产生了一个基本布局,那么接着回到之前我门关注的另外一个方法mLayoutInflater.inflate(layoutResID, mContentParent),这个方法就比较好理解了,
/** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy. * @return The root View of the inflated hierarchy. If root was supplied, * this is the root View; otherwise it is the root of the inflated * XML file. */ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
这这段注释上面我门就可以得到一个信息
//Inflate a new view hierarchy from the specified xml resource.(从指定的视图当中获取试图的层次结构,意思就是,现在在加载自己的资源)而具体流程就不贴代码了给各位上一张图
那么在这里我门就能够明白,setContentView其实做了两件比较核心的事情,就是加载环境配置,和自己的布局,那么接下来我门需要考虑的事情就是,他到底怎么画到界面上的
3.UI是如何绘制的?
通过前面两个章节,我门了解到,程序对于activity生命周期的调用,以及我们的视图资源的由来。这是我门需要找到的是我门的绘制起点在哪?
在ActivityThread启动时, 我发现在加载handleLaunchActivity方法调用performLaunchActivity方法之后又调用了一个handleResumeActivity在这里我发现了绘制流程的开始
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityThread.ActivityClientRecord r = (ActivityThread.ActivityClientRecord)this.mActivities.get(token);
if(checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
//......
WindowManager wm;
if(r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(4);
wm = a.getWindowManager();
LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = 1;
l.softInputMode |= forwardBit;
if(r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if(impl != null) {
impl.notifyChildRebuilt();
}
}
if(a.mVisibleFromClient) {
if(!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if(!willBeVisible) {
r.hideForNow = true;
}
}
}
通过前面的流程我门知道,onCreate之行完成之后,所有资源交给WindowManager保管
在这里,将我们的VIew交给了WindowManager,此处调用了addView,继续进入WindowManagerGlobal中的addView方法中:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } //....一系列判断 ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
进入addView之后我们发现了一段这样的代码,他将视图,和参数还有我门的一个ViewRoot对象都用了容器去装在了起来,那么在此处我门可以得出,是将所有的相关对象保存起来
mViews保存的是View对象,DecorView
mRoots保存和顶层View关联的ViewRootImpl对象
mParams保存的是创建顶层View的layout参数。
而WindowManagerGlobal类也负责和WMS通信
而在此时,有一句关键代码root.setView,这里是将我们的参数,和视图同时交给了ViewRoot,那么这个时候我们来看下ViewRoot当中的setView干了什么?终于在ViewRootImpl类中的setView方法中让我发现了让我明白的一步
view.assignParent(this);//this就是ViewRoot
在这里我门会看到view.assignParent的设置是this, 那么也就是说在view当中parent其实实际上是ViewRoot
那么在setContentView当中调用了一个setLayoutParams()是调用的ViewRoot的,而在ViewRoot当中发现了setLayoutParams和preformLayout对requestLayout方法的调用。在requestLayout当中发现了对scheduleTraversals方法的调用而scheduleTraversals当中调用了doTraversal的访问,最终访问到了performTraversals(),而在这个里面,我发现了整体的绘制流程的调用。当前里面依次是用了:
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
UI绘制先回去测量布局,然后在进行布局的摆放,当所有的布局测量摆放完毕之后,进行绘制。至此整体UI绘制过程我们就已经非常清楚了。 我门可以根据这种绘制的流程来操作自己的自定义组件。