Android快速启动窗口技术

以Android7.0为例,我们知道Android应用在启动时候,PhoneWindowManager会添加一个空白启动窗口,叫做addStartingWindow,在界面加载完毕后,会removeStartingWindow。

 
    /** {@inheritDoc} */
    @Override
    public View addStartingWindow(IBinder appToken, String packageName, int theme,
            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
            int icon, int logo, int windowFlags) {
        if (!SHOW_STARTING_ANIMATIONS) {
            return null;
        }
        if (packageName == null) {
            return null;
        }

        WindowManager wm = null;
        View view = null;

        try {
            Context context = mContext;
            if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow " + packageName
                    + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
                    + Integer.toHexString(theme));
            if (theme != context.getThemeResId() || labelRes != 0) {
                try {
                    context = context.createPackageContext(packageName, 0);
                    context.setTheme(theme);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }

            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);

            win.setDefaultIcon(icon);
            win.setDefaultLogo(logo);

            win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT);

            final WindowManager.LayoutParams params = win.getAttributes();
            params.token = appToken;
            params.packageName = packageName;
            params.windowAnimations = win.getWindowStyle().getResourceId(
                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
            params.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;

            if (!compatInfo.supportsScreen()) {
                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
            }

            params.setTitle("Starting " + packageName);

            wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
            view = win.getDecorView();

            if (win.isFloating()) {
                // Whoops, there is no way to display an animation/preview
                // of such a thing!  After all that work...  let's skip it.
                // (Note that we must do this here because it is in
                // getDecorView() where the theme is evaluated...  maybe
                // we should peek the floating attribute from the theme
                // earlier.)
                return null;
            }

            if (DEBUG_STARTING_WINDOW) Slog.d(
                TAG, "Adding starting window for " + packageName
                + " / " + appToken + ": "
                + (view.getParent() != null ? view : null));

            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;
        } catch (WindowManager.BadTokenException e) {
            // ignore
            Log.w(TAG, appToken + " already running, starting window not displayed. " +
                    e.getMessage());
        } catch (RuntimeException e) {
            // don't crash if something else bad happens, for example a
            // failure loading resources because we are loading from an app
            // on external storage that has been unmounted.
            Log.w(TAG, appToken + " failed creating starting window", e);
        } finally {
            if (view != null && view.getParent() == null) {
                Log.w(TAG, "view not successfully added to wm, removing view");
                wm.removeViewImmediate(view);
            }
        }

        return null;
    }

空白启动窗口给用户的操作体验是很差的,尤其是低端手机,或者应用冷启动时间较长时。所以就想到能不能在应用冷启动的时候,对启动窗口进行截屏,然后将截图缓存起来。当下一次应用冷启动时候,用截图的窗口代替原来的空白启动窗口呢?答案是可以的。流程如下图所示。问题的关键是截屏时机在哪里?冷启动过程中,WindowState中的mAttrs(WindowManager.LayoutParams)的type,有一个切换过程,从WindowManager.LayoutParams.TYPE_APPLICATION_STARTING  --->WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW,

            if (mService.isFastStartingWindowSupport() && mWin.mAttrs.type == WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW  && mService.lastLayoutParamsType== android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING  && mWin.mAppToken != null) {
                mCanScreenshot = true;
                doCacheBitmap();
            }
            mService.lastLayoutParamsType = mWin.mAttrs.type;//application start , mWin.mAttrs.type from  3--->1

lastLayoutParamsType是在    WMS中新声明的

public int lastLayoutParamsType = -1;

当然这个变化还有你的应用androidmanifest中application的设置有关。

上面截屏的逻辑是定义在WindowStateAnimator中performShowLocked的,在此时截屏,其实整个界面还是没有绘制完成的,看下面的doCacheBitmap,需要子线程延时一定时间,同时,对截屏的图像进行判断。

    void doCacheBitmap() {
        AsyncTask task = new AsyncTask() {
            @Override
            protected Void doInBackground(Void... para) {
                //android.os.Process.setThreadPriority
                //    (android.os.Process.THREAD_PRIORITY_FOREGROUND);
                try {
                    if (mWmSleep != -1) {
                        Thread.sleep(mWmSleep);
                    } else {
                        Thread.sleep(250);
                    }
                    if(mWin.mWinAnimator.mSurfaceController == null){
                       // in some case , mSurfaceController is null
                         return null;
                    }
                    if(!mCanScreenshot){
                        return null;
                    }
                    Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AsyncScreenshot");
                    Bitmap bmShot = SurfaceControl.screenshot(new Rect(),
                                        (int) mWin.mWinAnimator.mSurfaceController.mSurfaceW,
                                        (int) mWin.mWinAnimator.mSurfaceController.mSurfaceH,
                                            mSurfaceController.mSurfaceLayer,
                                            mSurfaceController.mSurfaceLayer, false, 0);
                    Slog.i(TAG, "doCacheBitmap, mToken =" + mWin.mToken);
                    int leftTopColor = bmShot.getPixel(0, 0);
                    int rightTopColor = bmShot.getPixel(mContext.getResources().getDisplayMetrics().widthPixels - 1, 0);
                    int leftBottomColor = bmShot.getPixel(0, mContext.getResources().getDisplayMetrics().heightPixels - 1);
                    int rightBottomColor = bmShot.getPixel(mContext.getResources().getDisplayMetrics().widthPixels - 1, mContext.getResources().getDisplayMetrics().heightPixels - 1);
                    int averageColor = (getAverageRGB(leftTopColor) + getAverageRGB(rightTopColor) + getAverageRGB(leftBottomColor) + getAverageRGB(rightBottomColor)) / 4;
                    if (averageColor < 5) {
                        //in some case, the bitmap is a starting window(it is a picture with black boder,or the picture is whole black) , not a started window , so return
                        return null;
                    }

                    if (bmShot != null && mWin.mToken != null) {
                        mService.setBitmapByToken(mWin.mToken.token
                                , bmShot.copy(bmShot.getConfig(), true));
                        bmShot.recycle();
                    }
                      Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return null;
            }
        };
        task.execute();
    }

 

应用快速启动流程如下图所示:

(1) 用户在点击应用的时候,ActivityManagerService会执行startActivity方法,然后ActivityStack执行startActivityLocked方法,然后ActivityRecord执行showStartingWindow方法,最后调用WindowManagerService的setAppStartingWindow来设置一个启动窗口。

(2) WindowManagerService会判断当前系统是否支持FSW并且当前窗口是否有Bitmap缓存,如果是开机后第一次启动,则执行Android系统原有方法addStartingWindow,添加一个空白窗口。如果系统支持FSW并且当前窗口有Bitmap缓存,则执行addFastStartingWindow方法,添加一个非空白窗口。

(3) 应用冷启动时,WindowMangerService通过WindowStateAnimator进行窗口缓存。WindowMangerService收到configChange广播,则清除bitmap缓存。

 

Android快速启动窗口技术_第1张图片

 

 

加入该技术后,给用户的感觉是应用秒开,对比测试友商手机,冷启动速度都要比我们的差,加入该技术的应用,冷启动速度只有300ms。

该技术的局限性在于,仅适用于启动界面变化较小的APP,变化较大的应用不适合,第三方应用基本都不适合。

第一篇博客就写到这里把。

你可能感兴趣的:(Android快速启动窗口技术)