Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 應用啟動界面分析(Starting Window)

Android 應用啟動界面分析(Starting Window)

編輯:關於Android編程

當我們打開一個應用程序時,會出現一個啟動界面,不同的手機,啟動界面不一樣,甚至有的手機還會出現一段時間的黑屏,出現時間的長短與啟動啟動的速度有關。

啟動畫面是什麼?

應用程序啟動時,我們看到了啟動的畫面,它上面顯示的是看到的內容,內容是在哪兒配置的吶?它究竟是什麼吶?

配置內容

我們在應用程序配置清單AndroidManifest.xml中的application,activity節點配置的android:theme屬性,就是應用啟動時顯示配置信息,如果不配置theme則獲取系統默認屬性,在該屬性下面我們可以看到有以下節點配置:

可以看到在主題的Theme中可以配置關於窗口的屬性,其中windowBackground節點,當應用啟動時顯示就該節點配置的信息。

怎麼復寫?

系統默認的啟動界面一般都是白色或者黑色的畫面,因此如果你的產品色是白色,而啟動界面卻是黑色,則會在啟動過程中有一個從黑變白的過程,如果應用啟動非常快,則會一閃而過,導致體驗太差。從上面可以知道應用啟動讀取的是windowBackground節點,那我們直接復寫該節點就可以了。

> 樣例

我們可以自定義一個主題,復寫掉windowBackground節點,這樣在應用啟動時就會顯示你配置的界面,該界面你可以顯示你應用的logo等信息來增強品牌,最主要的是提升應用體驗,不過也有更多廠商將這個當做了一個收入的來源,都掛了啟動廣告。。。。。。

顯示過程

從上面我們已經知道如果更改啟動畫面為自己的畫面,如果只需要學會怎麼復寫,則到這一步就OK了; 可是作為搬磚的,我們不僅要知其然更要知其所以然。因此我們從代碼的角度來看看他的顯示過程。

我們點擊home界面的應用圖標,可以看到是在home應用程序裡面其實調用了startActivity(intent);intent中對應的是包名與類名,這裡的類就是我們的主activity,主acitivity主要用下面的信息來進行區分:


    
    

Home應用程序中是怎麼獲取到該類名的?解析的內容經過ActivityManagerService讀取action為MAIN,category為LAUNCHER的activity顯示到界面上。
因此我們跟進到startActivity中去看看:

@Override
public void startActivity(Intent intent) {
    this.startActivity(intent, null);
}

@Override
public 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= 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 gDefault = new Singleton() {
    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,是不是感覺曙光就在眼前:

@Override
public 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函數:

@Override
public 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. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved