Android应用启动界面分析(Starting Window)

来源:互联网 发布:sra数据上传 编辑:程序博客网 时间:2024/06/14 12:10

       当我们打开一个应用程序时,会出现一个启动界面,不同的手机,启动界面不一样,甚至有的手机还会出现一段时间的黑屏,出现时间的长短与启动启动的速度有关。

启动画面是什么?

       应用程序启动时,我们看到了启动的画面,它上面显示的是看到的内容,内容是在哪儿配置的呐?它究竟是什么呐?

配置内容

我们在应用程序配置清单AndroidManifest.xml中的application,activity节点配置的android:theme属性,就是应用启动时显示配置信息,如果不配置theme则获取系统默认属性,在该属性下面我们可以看到有以下节点配置:

 <style name="Theme">        <!-- Window attributes -->        <item name="windowBackground">@drawable/screen_background_selector_dark</item> </style>

       可以看到在主题的Theme中可以配置关于窗口的属性,其中windowBackground节点,当应用启动时显示就该节点配置的信息。

怎么复写?

       系统默认的启动界面一般都是白色或者黑色的画面,因此如果你的产品色是白色,而启动界面却是黑色,则会在启动过程中有一个从黑变白的过程,如果应用启动非常快,则会一闪而过,导致体验太差。从上面可以知道应用启动读取的是windowBackground节点,那我们直接复写该节点就可以了。

> 样例<style name="WelcomeTheme" parent="@android:style/Theme.Light.NoTitleBar.Fullscreen">    <item name="colorPrimary">@color/white</item>    <item name="colorPrimaryDark">@color/color_ffcccccc</item>    <item name="android:windowFullscreen">true</item>    <item name="android:windowBackground">@drawable/splash_bg</item></style>

       我们可以自定义一个主题,复写掉windowBackground节点,这样在应用启动时就会显示你配置的界面,该界面你可以显示你应用的logo等信息来增强品牌,最主要的是提升应用体验,不过也有更多厂商将这个当做了一个收入的来源,都挂了启动广告。。。。。。

显示过程

       从上面我们已经知道如果更改启动画面为自己的画面,如果只需要学会怎么复写,则到这一步就OK了; 可是作为搬砖的,我们不仅要知其然更要知其所以然。因此我们从代码的角度来看看他的显示过程。

       我们点击home界面的应用图标,可以看到是在home应用程序里面其实调用了startActivity(intent);intent中对应的是包名与类名,这里的类就是我们的主activity,主acitivity主要用下面的信息来进行区分:

<intent-filter>    <action android:name="android.intent.action.MAIN" />    <category android:name="android.intent.category.LAUNCHER" /></intent-filter>

       Home应用程序中是怎么获取到该类名的?我们在前一篇Android应用程序包解析过程浅析中有关于AndroidManifest的解析过程,解析的内容经过ActivityManagerService读取action为MAIN,category为LAUNCHER的activity显示到界面上。
       因此我们跟进到startActivity中去看看:

@Overridepublic void startActivity(Intent intent) {    this.startActivity(intent, null);}@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {    if (options != null) {        startActivityForResult(intent, -1, options);    } else {        // Note we want to go through this call for compatibility with        // applications that may have overridden the method.        startActivityForResult(intent, -1);    }}public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {    if (mParent == null) {        Instrumentation.ActivityResult ar =            mInstrumentation.execStartActivity(                this, mMainThread.getApplicationThread(), mToken, this,                intent, requestCode, options);        if (ar != null) {            mMainThread.sendActivityResult(                mToken, mEmbeddedID, requestCode, ar.getResultCode(),                ar.getResultData());        }        if (requestCode >= 0) {            // If this start is requesting a result, we can avoid making            // the activity visible until the result is received.  Setting            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the            // activity hidden during this time, to avoid flickering.            // This can only be done when a result is requested because            // that guarantees we will get information back when the            // activity is finished, no matter what happens to it.            mStartedActivity = true;        }        cancelInputsAndStartExitTransition(options);        // TODO Consider clearing/flushing other event sources and events for child windows.    } else {        if (options != null) {            mParent.startActivityFromChild(this, intent, requestCode, options);        } else {            // Note we want to go through this method for compatibility with            // existing applications that may have overridden it.            mParent.startActivityFromChild(this, intent, requestCode);        }    }}

       startActivity调用了两个参数的startActivity,之后最终都调用了startActivityForResult函数,我们可以看到这里最终调用了mInstrumentation.execStartActivity,我们去execStartActivity看看里面执行了什么?

public ActivityResult execStartActivity(        Context who, IBinder contextThread, IBinder token, Activity target,        Intent intent, int requestCode, Bundle options) {    IApplicationThread whoThread = (IApplicationThread) contextThread;    Uri referrer = target != null ? target.onProvideReferrer() : null;    if (referrer != null) {        intent.putExtra(Intent.EXTRA_REFERRER, referrer);    }    if (mActivityMonitors != null) {        synchronized (mSync) {            final int N = mActivityMonitors.size();            for (int i=0; i<N; i++) {                final ActivityMonitor am = mActivityMonitors.get(i);                if (am.match(who, null, intent)) {                    am.mHits++;                    if (am.isBlocking()) {                        return requestCode >= 0 ? am.getResult() : null;                    }                    break;                }            }        }    }    try {        intent.migrateExtraStreamToClipData();        intent.prepareToLeaveProcess();        int result = ActivityManagerNative.getDefault()            .startActivity(whoThread, who.getBasePackageName(), intent,                    intent.resolveTypeIfNeeded(who.getContentResolver()),                    token, target != null ? target.mEmbeddedID : null,                    requestCode, 0, null, options);        checkStartActivityResult(result, intent);    } catch (RemoteException e) {        throw new RuntimeException("Failure from system", e);    }    return null;}

       首先判断是否已经有同名的activity并且requestCode大于0,requestCode大于0表示有数据需要返回,如果不存在着调用ActivityManagerNative.getDefault().startActivity,ActivityManagerNative.getDefault()获取的是什么呐?ActivityManagerNative.getDefault()的代码如下:

/** * Retrieve the system's default/global activity manager. */static public IActivityManager getDefault() {    return gDefault.get();}private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {    protected IActivityManager create() {        IBinder b = ServiceManager.getService("activity");        if (false) {            Log.v("ActivityManager", "default service binder = " + b);        }        IActivityManager am = asInterface(b);        if (false) {            Log.v("ActivityManager", "default service = " + am);        }        return am;    }};

       get从ServiceManager获取name为“activity”从服务,该返回一个IActivityManager服务,可以看到他是一个进程间通信的客户端,那远端又是谁?

       在前一篇 Android应用程序管理服务启动过程浅析(PackageManagerService)文章中,说init进程fork一个zygote进程,在zygote进程中调用了SystemServer,SystemServer调用startBootstrapServices启动了众多服务,其中就包括ActivityManagerService,之后调用了ActivityManagerService.setSystemProcess函数,将该服务加入到ServiceManager中,加入name为acitivity的ActivityManagerService。因此这里获取的其实ActivityManagerService的远端代理,因此真正执行的地方在ActivityManagerService中。

       因此我们去看看ActivityManagerService的startActivity,可以看到它直接调用了startActivityAsUser,startActivityAsUser中又调用了mStackSupervisor.startActivityMayWait,我们看看真正执行的地方startActivityMayWait:

final int startActivityMayWait(IApplicationThread caller, int callingUid,        String callingPackage, Intent intent, String resolvedType,        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,        IBinder resultTo, String resultWho, int requestCode, int startFlags,        ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,        Bundle options, boolean ignoreTargetSecurity, int userId,        IActivityContainer iContainer, TaskRecord inTask) {        ..............    int res = startActivityLocked(caller, intent, resolvedType, aInfo,            voiceSession, voiceInteractor, resultTo, resultWho,            requestCode, callingPid, callingUid, callingPackage,            realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,            componentSpecified, null, container, inTask);    .........}

       我们去掉了部分代码,看到里面继续调用了startActivityLocked函数,我们忽略里面其它的代码,看到里面调用了startActivityUncheckedLocked函数,我们来看看该函数:

final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,        boolean doResume, Bundle options, TaskRecord inTask) {    final Intent intent = r.intent;    final int callingUid = r.launchedFromUid;    final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;    final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;    final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;    int launchFlags = intent.getFlags();    final boolean launchTaskBehind = r.mLaunchTaskBehind            && !launchSingleTask && !launchSingleInstance            && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;    ...............    boolean movedHome = false;    ActivityStack targetStack;    intent.setFlags(launchFlags);    final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0;    // We may want to try to place the new activity in to an existing task.  We always    // do this if the target activity is singleTask or singleInstance; we will also do    // this if NEW_TASK has been requested, and there is not an additional qualifier telling    // us to still place it in a new task: multi task, always doc mode, or being asked to    // launch this as a new task behind the current one.    if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&            (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)            || launchSingleInstance || launchSingleTask) {        // If bring to front is requested, and no result is requested and we have not        // been given an explicit task to launch in to, and        // we can find a task that was started with this same        // component, then instead of launching bring that one to the front.        if (inTask == null && r.resultTo == null) {            // unique task, so we do a special search.            ActivityRecord intentActivity = !launchSingleInstance ?                    findTaskLocked(r) : findActivityLocked(intent, r.info);            ............        }    }    ...........    boolean newTask = false;    boolean keepCurTransition = false;    TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?            sourceRecord.task : null;    // Should this be considered a new task?    if (r.resultTo == null && inTask == null && !addingToTask            && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {        newTask = true;        targetStack = computeStackFocus(r, newTask);        targetStack.moveToFront("startingNewTask");        if (reuseTask == null) {            r.setTask(targetStack.createTaskRecord(getNextTaskId(),                    newTaskInfo != null ? newTaskInfo : r.info,                    newTaskIntent != null ? newTaskIntent : intent,                    voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),                    taskToAffiliate);            if (DEBUG_TASKS) Slog.v(TAG_TASKS,                    "Starting new activity " + r + " in new task " + r.task);        } else {            r.setTask(reuseTask, taskToAffiliate);        }    }      .........    if (newTask) {        EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);    }    ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);    targetStack.mLastPausedActivity = null;    targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);    if (!launchTaskBehind) {        // Don't set focus on an activity that's going to the back.        mService.setFocusedActivityLocked(r, "startedActivity");    }    return ActivityManager.START_SUCCESS;}

       首先获取lunchMode,这个与你在activity配置相关,之后resultTo为null表示Home启动程序不需要等待返回结果,因此会执行findActivityLocked,第一次执行这里返回为空,之后会设置newTask为true,表示会创建一个新的task来处理activity。之后会继续执行targetStack.startActivityLocked:

final void startActivityLocked(ActivityRecord r, boolean newTask,        boolean doResume, boolean keepCurTransition, Bundle options) {    TaskRecord rTask = r.task;    .......    r.putInHistory();    if (!isHomeStack() || numActivities() > 0) {        // We want to show the starting preview window if we are        // switching to a new task, or the next activity's process is        // not currently running.        mWindowManager.addAppToken(task.mActivities.indexOf(r),                r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,                (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,                r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);        boolean doShow = true;        if (newTask) {            // Even though this activity is starting fresh, we still need            // to reset it to make sure we apply affinities to move any            // existing activities from other tasks in to it.            // If the caller has requested that the target task be            // reset, then do so.            if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {                resetTaskIfNeededLocked(r, r);                doShow = topRunningNonDelayedActivityLocked(null) == r;            }        } else if (options != null && new ActivityOptions(options).getAnimationType()                == ActivityOptions.ANIM_SCENE_TRANSITION) {            doShow = false;        }        if (r.mLaunchTaskBehind) {            // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we            // tell WindowManager that r is visible even though it is at the back of the stack.            mWindowManager.setAppVisibility(r.appToken, true);            ensureActivitiesVisibleLocked(null, 0);        } else if (SHOW_APP_STARTING_PREVIEW && doShow) {            // Figure out if we are transitioning from another activity that is            // "has the same starting icon" as the next one.  This allows the            // window manager to keep the previous window it had previously            // created, if it still had one.            ActivityRecord prev = mResumedActivity;            if (prev != null) {                // We don't want to reuse the previous starting preview if:                // (1) The current activity is in a different task.                if (prev.task != r.task) {                    prev = null;                }                // (2) The current activity is already displayed.                else if (prev.nowVisible) {                    prev = null;                }            }            mWindowManager.setAppStartingWindow(                    r.appToken, r.packageName, r.theme,                    mService.compatibilityInfoForPackageLocked(                            r.info.applicationInfo), r.nonLocalizedLabel,                    r.labelRes, r.icon, r.logo, r.windowFlags,                    prev != null ? prev.appToken : null, showStartingIcon);            r.mStartingWindowShown = true;        }    }    if (doResume) {        mStackSupervisor.resumeTopActivitiesLocked(this, r, options);    }}

       我们忽略其他信息,经过一系列判断条件可以看到执行了mWindowManager.setAppStartingWindow,是不是感觉曙光就在眼前:

@Overridepublic void setAppStartingWindow(IBinder token, String pkg,        int theme, CompatibilityInfo compatInfo,        CharSequence nonLocalizedLabel, int labelRes, int icon, int logo,        int windowFlags, IBinder transferFrom, boolean createIfNeeded) {    if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,            "setAppStartingWindow()")) {        throw new SecurityException("Requires MANAGE_APP_TOKENS permission");    }        if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Creating StartingData");        wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,                labelRes, icon, logo, windowFlags);        Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);        // Note: we really want to do sendMessageAtFrontOfQueue() because we        // want to process the message ASAP, before any other queued        // messages.        if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Enqueueing ADD_STARTING");        mH.sendMessageAtFrontOfQueue(m);    }}

       在函数执行的末尾可以看到发送了一个ADD_STARTING的message,我们去handleMessage的ADD_STARTING的分支看看调用内容,这里代码就不在贴出来了,看到这里调用了WindowManagerPolicy.addStartingWindow返回一个view,WindowManagerPolicy是什么?可以看到WindowManagerPolicy是一个interface,那它的实现类是什么?它的实现类就是PhoneWindowManager,因此真正调用的是PhoneWindowManager.addStartingWindow函数:

@Overridepublic View addStartingWindow(IBinder appToken, String packageName, int theme,        CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,        int icon, int logo, int windowFlags) {        PhoneWindow win = new PhoneWindow(context);        win.setIsStartingWindow(true);        final TypedArray ta = win.getWindowStyle();        if (ta.getBoolean(                    com.android.internal.R.styleable.Window_windowDisablePreview, false)            || ta.getBoolean(                    com.android.internal.R.styleable.Window_windowShowWallpaper,false)) {            return null;        }        Resources r = context.getResources();        win.setTitle(r.getText(labelRes, nonLocalizedLabel));        win.setType(            WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);        synchronized (mWindowManagerFuncs.getWindowManagerLock()) {            // Assumes it's safe to show starting windows of launched apps while            // the keyguard is being hidden. This is okay because starting windows never show            // secret information.            if (mKeyguardHidden) {                windowFlags |= FLAG_SHOW_WHEN_LOCKED;            }        }        // Force the window flags: this is a fake window, so it is not really        // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM        // flag because we do know that the next window will take input        // focus, so we want to get the IME window up on top of us right away.        win.setFlags(            windowFlags|            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,            windowFlags|            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);        params.setTitle("Starting " + packageName);        wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);        view = win.getDecorView();        wm.addView(view, params);        // Only return the view if it was successfully added to the        // window manager... which we can tell by it having a parent.        return view.getParent() != null ? view : null;

       这个首先创建了一个PhoneWindow作为显示窗口,之后设置了setIsStartingWindow为true,表示是一个starting window,之后设置了window的title,type,flags等属性,并且设置了layout的属性的宽高均为MATCH_PARENT,之后设置了窗口出现的动画,最后调用getDecorView获取当前view,每一个PhoneWindow都有一个DecorView,DecorView里面又调用了installDecor,installDecor创建一个DecorView:

private void installDecor() {    if (mDecor == null) {        mDecor = generateDecor();        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);        mDecor.setIsRootNamespace(true);        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);        }    }    if (mContentParent == null) {        mContentParent = generateLayout(mDecor);    }   }protected ViewGroup generateLayout(DecorView decor) {    // Apply data from current theme.    TypedArray a = getWindowStyle();    // The rest are only done if this window is not embedded; otherwise,    // the values are inherited from our container.    if (getContainer() == null) {        if (mBackgroundDrawable == null) {            if (mBackgroundResource == 0) {                mBackgroundResource = a.getResourceId(                        R.styleable.Window_windowBackground, 0);            }    }    // Remaining setup -- of background and title -- that only applies    // to top-level windows.    if (getContainer() == null) {        final Drawable background;        if (mBackgroundResource != 0) {            background = getContext().getDrawable(mBackgroundResource);        } else {            background = mBackgroundDrawable;        }        mDecor.setWindowBackground(background);    return contentParent;}

       我们忽略其他的代码,终于看到解析了Window_windowBackground属性,并且将该属性设置为DecorView的背景,最后在获取WindowManagerService,将该view显示到界面上。到此starting window显示出来。当整个应用真正启动起来后会remove到该view。这样就无缝的切换了整个启动过程。

总结

       我们忽略了很多代码,其实上述的过程更可以看做是应用程序的启动过程,starting window只是其中的一步。整个启动流程会比上面执行更多的代码,但是逻辑是一致的,因此可以自己去看看源代码。read the fuck source code!

1 0
原创粉丝点击