原文地址:AndroidQ SystemUI之锁屏加载(上)滑动锁屏

原文格式我个人感觉看着不是很顺,所以稍微调整下,便于我自己阅读理解。

========================================================================

本篇来分析下Android锁屏的加载流程,锁屏加载比较复杂,涉及framework和SystemUI,这篇主要分析SystemUI部分,锁屏有两种,一种滑动锁屏,另一种密码锁屏,也叫Bouncer。
前面AndroidQ SystemUI之启动 中分析了SystemUI启动过程中会加载一个config数组,里面定义了SystemUI的重要的类,之后遍历此数组,以此调用其Start方法,我们就从StatusBar.start方法开始分析锁屏相关的流程

StatusBar.start
public void start() {

createAndAddWindows(result);

}
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
}

makeStatusBarView方法里面会初始化很多SystemUI的View

makeStatusBarView
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {

...
    inflateStatusBarWindow(context);
    ...

}

inflateStatusBarWindow
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
}

这个方法,加载了整个StatusBar的顶层自定义布局,类型为StatusBarWindowView,继承FrameLayout,布局文件是super_status_bar.xml,

<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:sysui=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:background="#698541"
android:fitsSystemWindows=“true”>

<com.android.systemui.statusbar.BackDropView
        android:id="@+id/backdrop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        sysui:ignoreRightInset="true"
        >
    <ImageView android:id="@+id/backdrop_back"
               android:layout_width="match_parent"
               android:scaleType="centerCrop"
               android:layout_height="match_parent" />
    <ImageView android:id="@+id/backdrop_front"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:scaleType="centerCrop"
               android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>

<com.android.systemui.statusbar.ScrimView
    android:id="@+id/scrim_behind"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:importantForAccessibility="no"
    sysui:ignoreRightInset="true"
    />

<FrameLayout
    android:id="@+id/status_bar_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

<include layout="@layout/status_bar_expanded"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="invisible" />

<include layout="@layout/brightness_mirror" />

<com.android.systemui.statusbar.ScrimView
    android:id="@+id/scrim_in_front"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:importantForAccessibility="no"
    sysui:ignoreRightInset="true"
/>

<LinearLayout
    android:id="@+id/lock_icon_container"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="@dimen/status_bar_height"
    android:layout_gravity="top|center_horizontal">
    <com.android.systemui.statusbar.phone.LockIcon
        android:id="@+id/lock_icon"
        android:layout_width="@dimen/keyguard_lock_padding"
        android:contentDescription="@string/accessibility_unlock_button"
        android:src="@*android:drawable/ic_lock"
        android:scaleType="center" />
    <com.android.keyguard.KeyguardMessageArea
        android:id="@+id/keyguard_message_area"
        style="@style/Keyguard.TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/keyguard_lock_padding"
        android:gravity="center"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:focusable="true" />
</LinearLayout>

</com.android.systemui.statusbar.phone.StatusBarWindowView>

接着我们看下StatusBarWindowView的onFinishInflate方法

onFinishInflate
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mStackScrollLayout = findViewById(R.id.notification_stack_scroller);
mNotificationPanel = findViewById(R.id.notification_panel);

mBrightnessMirror = findViewById(R.id.brightness_mirror);
mLockIcon = findViewById(R.id.lock_icon);
}

这里分别加载了id为notification_stack_scroller,notification_panel,brightness_mirror,lock_icon的View,
notification_stack_scroller是锁屏上承载notification的View
notification_panel是滑动锁屏
brightness_mirror是调节亮度条的View
lock_icon是锁屏上的那个锁一样的View

再回到makeStatusBarView方法

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {

...
 //此方法已经完成StatusBar顶层View,已经滑动锁屏相关View的加载
    inflateStatusBarWindow(context);
    //获取滑动锁屏的View
    mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
    //获取锁屏上承载通知的View
    mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
    ...

}

makeStatusBarView方法中获取到了滑动锁屏的View,再回到createAndAddWindows

public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
}

mStatusBarWindowController.add
下面代码很明显了,通过mWindowManager添加了一个窗口,这样SystemUI的顶层自定义ViewGroup就被添加到了WMS,自然,滑动锁屏作为mStatusBarWindow的子View也就被添加了

public void add(ViewGroup statusBarView, int barHeight) {

//创建LayoutParams,定义了一些窗口属性
    mLp = new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            barHeight,
            WindowManager.LayoutParams.TYPE_STATUS_BAR,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
            PixelFormat.TRANSLUCENT);
            //创建Token
    mLp.token = new Binder();
    //位置在最顶部
    mLp.gravity = Gravity.TOP;
    mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
    mLp.setTitle("StatusBar");
    mLp.packageName = mContext.getPackageName();
    mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    mStatusBarView = statusBarView;
    mBarHeight = barHeight;
    //添加到WMS
    mWindowManager.addView(mStatusBarView, mLp);
    mLpChanged.copyFrom(mLp);
    onThemeChanged();
}

我们会发现密码锁屏并不是直接写死在xml文件中的,密码锁屏是通过动态加载的,我们接下来分析密码锁屏是如何创建以及添加到Keyguard布局中的
回到最开始的StatusBar的start方法中,在调用createAndAddWindows加载完SyatemUI相关View之后接着会调用startKeyguard方法开启锁屏流程

public void start() {
 ...
 createAndAddWindows(result);
 ...
 startKeyguard();
}

startKeyguard
protected void startKeyguard() {
        KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
        ...
        mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
                mStatusBarWindow.findViewById(R.id.lock_icon_container));
               
        ...
    }

首先获取了一个锁屏非常重要的类KeyguardViewMediator,这个类主要负责处理锁屏相关事务,接着调用registerStatusBar方法,这里传了一个getBouncerContainer()的ViewGroup过去,这个ViewGroup就是StartBar中加载的SystemUI最顶层ViewGroup,布局为super_status_bar的StatusBarWindowView,后面创建的密码锁屏会动态添加到这里面

keyguardViewMediator.registerStatusBar
 public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
            ViewGroup container, NotificationPanelView panelView,
            BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer) {
        mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
                biometricUnlockController, mDismissCallbackRegistry, lockIconContainer);
        return mStatusBarKeyguardViewManager;
    }

接着调用

StatusBarKeyguardViewManager.registerStatusBar
public void registerStatusBar(StatusBar statusBar,
            ViewGroup container,
            NotificationPanelView notificationPanelView,
            BiometricUnlockController biometricUnlockController,
            DismissCallbackRegistry dismissCallbackRegistry,
            ViewGroup lockIconContainer) {
        mStatusBar = statusBar;
        //StatusBar传递过来的SystemUI顶层ViewGroup
        mContainer = container;
        //锁屏上承载lockIcon的ViewGroup
        mLockIconContainer = lockIconContainer;
        if (mLockIconContainer != null) {
            mLastLockVisible = mLockIconContainer.getVisibility() == View.VISIBLE;
        }
        //生物识别相关
        mBiometricUnlockController = biometricUnlockController;
        //创建密码锁屏Bouncer
        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
                mExpansionCallback);
        mNotificationPanelView = notificationPanelView;
        //给滑动锁屏界面设置监听器
        notificationPanelView.setExpansionListener(this);
    }

来看看Bouncer的创建

SystemUIFactory.createKeyguardBouncer
 public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
            LockPatternUtils lockPatternUtils,  ViewGroup container,
            DismissCallbackRegistry dismissCallbackRegistry,
            KeyguardBouncer.BouncerExpansionCallback expansionCallback) {
        return new KeyguardBouncer(context, callback, lockPatternUtils, container,
                dismissCallbackRegistry, FalsingManagerFactory.getInstance(context),
                expansionCallback, KeyguardUpdateMonitor.getInstance(context),
                new Handler(Looper.getMainLooper()));
    }

这里直接new了一个Bouncer对象,我们跟进KeyguardBouncer这个类会发现它并没有继承View或者ViewGroup,说明它并不是真正的密码锁屏的View,KeyguardBouncer其实是用来管理密码锁屏的,从它提供的方法就能看出来,show,hide,inflateView等

public class KeyguardBouncer {
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
            LockPatternUtils lockPatternUtils, ViewGroup container,
            DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
            BouncerExpansionCallback expansionCallback,
            KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) {
        mContext = context;
        mCallback = callback;
        mLockPatternUtils = lockPatternUtils;
        mContainer = container;
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
        mFalsingManager = falsingManager;
        mDismissCallbackRegistry = dismissCallbackRegistry;
        mExpansionCallback = expansionCallback;
        mHandler = handler;
        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
    }
 }

到这里我们发现从StatusBar.start->StatusBar.startKeyguard->KeyguardViewMediator.registerStatusBar->StatusBarKeyguardViewManager.registerStatusBar->SystemUIFactory.createKeyguardBouncer->new KeyguardBouncer
在KeyguardBouncer构造方法中也只是做了些初始化,并没有涉及到和密码锁屏的创建及添加相关的操作

其实不管是滑动锁屏还是密码锁屏加载都是通过KeyguardViewMediator中的doKeyguardLocked方法,此方法非常重要,可以说是整个锁屏的核心方法,任何形式的锁屏,开机启动锁屏,点击power键锁屏,SIM卡状态变化锁屏都会调用此方法,我们就从此方法为入口分析锁屏加载流程

KeyguardViewMediator.doKeyguardLocked
private void doKeyguardLocked(Bundle options) {
       //省略一些不需要加载锁屏的情况,如外部禁用,系统没准备好等
        ....
        showLocked(options);
    }

private void showLocked(Bundle options) {
       
        mShowKeyguardWakeLock.acquire();
        Message msg = mHandler.obtainMessage(SHOW, options);
        mHandler.sendMessage(msg);
    }

通过Handler发送消息

private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW:
                    handleShow((Bundle) msg.obj);
                    break;

调用handleShow方法

private void handleShow(Bundle options) {
    ....
         mStatusBarKeyguardViewManager.show(options);
         ....
    }

StatusBarKeyguardViewManager.show
public void show(Bundle options) {
        mShowing = true;
        ....
        reset(true /* hideBouncerWhenShowing */);
    }

reset
public void reset(boolean hideBouncerWhenShowing) {
        if (mShowing) {
            //不需要显示锁屏的情况
            if (mOccluded && !mDozing) {
                ...
            } else {
                showBouncerOrKeyguard(hideBouncerWhenShowing);
            }
   ...
        }
    }

接着调用showBouncerOrKeyguard方法,此方法中就是判断显示滑动锁屏,还是显示密码锁屏,参数hideBouncerWhenShowing代表是否隐藏Bouncer

showBouncerOrKeyguard
 protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
        if (mBouncer.needsFullscreenBouncer() && !mDozing) {
            //隐藏滑动锁屏
            mStatusBar.hideKeyguard();
            //显示密码锁屏
            mBouncer.show(true /* resetSecuritySelection */);
        } else {
           //显示滑动锁屏
            mStatusBar.showKeyguard();
            if (hideBouncerWhenShowing) {
                hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
                mBouncer.prepare();
            }
        }
        updateStates();
    }

什么情况下会直接显示密码锁屏界面呢?

KeyguardBouncer.needsFullscreenBouncer
public boolean needsFullscreenBouncer() {
        ensureView();
        if (mKeyguardView != null) {
            SecurityMode mode = mKeyguardView.getSecurityMode();
            return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
        }
        return false;
    }

当前获取的Bouncer的类型是SimPin或者SimPuk时,通过mKeyguardView.getSecurityMode()获取当前Bouncer的类型时最终是调到KeyguardSecurityModel的getSecurityMode方法,通过KeyguardUpdateMonitor这个类获取当前SIM卡的状态来判断的,当状态是PIN_REQUIRED或者PUK_REQUIRED则说明应该直接显示类型为SecurityMode.SimPin或者SecurityMode.SimPuk的密码锁屏,所以当我们插入一张带密码的SIM卡时会立即显示sim pin界面

if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
            return SecurityMode.SimPuk;
        }

if (SubscriptionManager.isValidSubscriptionId(
                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
            return SecurityMode.SimPin;
        }

我们再来看看needsFullscreenBouncer中调用的ensureView方法

protected void ensureView() {
        ...
        //首次调用mRoot为空
        if (mRoot == null || forceRemoval) {
            inflateView();
        }
    }

inflateView
protected void inflateView() {
       //在初始化View之前先移除,以保持干净
        removeView();
        mHandler.removeCallbacks(mRemoveViewRunnable);
        mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
        mKeyguardView = mRoot.findViewById(R.id.keyguard_host_view);
        mKeyguardView.setLockPatternUtils(mLockPatternUtils);
        mKeyguardView.setViewMediatorCallback(mCallback);
        mContainer.addView(mRoot, mContainer.getChildCount());
        mStatusBarHeight = mRoot.getResources().getDimensionPixelOffset(
                com.android.systemui.R.dimen.status_bar_height);
        mRoot.setVisibility(View.INVISIBLE);
        mRoot.setAccessibilityPaneTitle(mKeyguardView.getAccessibilityTitleForCurrentMode());

final WindowInsets rootInsets = mRoot.getRootWindowInsets();
        if (rootInsets != null) {
            mRoot.dispatchApplyWindowInsets(rootInsets);
        }
    }

此方法代码很明显,就是加载布局,加载View,然后addView,mRoot是名为keyguard_bouncer的layout,这是Bouncer的根布局

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    android:fitsSystemWindows="true">

<include
        style="@style/BouncerSecurityContainer"
        layout="@layout/keyguard_host_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</FrameLayout>

这里面直接include了一个名为keyguard_host_view的layout,KeyguardHostView是一个自定义ViewGroup,里面包含KeyguardSecurityContainer,KeyguardSecurityContainer立马包含KeyguardSecurityViewFlipper

<com.android.keyguard.KeyguardHostView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
    android:id="@+id/keyguard_host_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
                                                  from this view when bouncer is shown -->

<com.android.keyguard.KeyguardSecurityContainer
        android:id="@+id/keyguard_security_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        androidprv:layout_maxHeight="@dimen/keyguard_security_max_height"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:padding="0dp"
        android:fitsSystemWindows="true"
        android:layout_gravity="center">
        <com.android.keyguard.KeyguardSecurityViewFlipper
            android:id="@+id/view_flipper"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:clipToPadding="false"
            android:paddingTop="@dimen/keyguard_security_view_top_margin"
            android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
            android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
            android:gravity="center">
        </com.android.keyguard.KeyguardSecurityViewFlipper>
    </com.android.keyguard.KeyguardSecurityContainer>

</com.android.keyguard.KeyguardHostView>

接着通过addView将mRoot添加到mContainer,mContainer我们前面有分析过,它是StatusBar传递过来的SystemUI顶层ViewGroup,接着mRoot.setVisibility(View.INVISIBLE)让mRoot暂时不可见
ensureView分析完了,此方法也只是初始化一些layout

接着再回到showBouncerOrKeyguard中去

showBouncerOrKeyguard
 protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
        if (mBouncer.needsFullscreenBouncer() && !mDozing) {
            mStatusBar.hideKeyguard();
            mBouncer.show(true /* resetSecuritySelection */);
        } else {
            mStatusBar.showKeyguard();
            if (hideBouncerWhenShowing) {
                hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
                mBouncer.prepare();
            }
        }
        updateStates();
    }

我们分析了直接显示Bouncer的情况,是在SIM卡状态为PIN_REQUIRED或者PUK_REQUIRED时,如果是普通情况则会调用StatusBar.showKeyguard显示滑动锁屏

StatusBar.showKeyguard
public void showKeyguard() {
        ...
        updateIsKeyguard();
        ...
    }

updateIsKeyguard
private boolean updateIsKeyguard() {
        ...
        boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
                || keyguardForDozing) && !wakeAndUnlocking;
        if (shouldBeKeyguard) {
            if (isGoingToSleep()
                    && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF) {
                // Delay showing the keyguard until screen turned off.
            } else {
                showKeyguardImpl();
            }
        } else {
            return hideKeyguardImpl();
        }
        return false;
    }

满足显示Keyguard的条件shouldBeKeyguard则调用showKeyguardImpl

showKeyguardImpl
public void showKeyguardImpl() {
        ...
        updatePanelExpansionForKeyguard();
        ...
        }
    }

updatePanelExpansionForKeyguard
private void updatePanelExpansionForKeyguard() {
 //状态为KEYGUARD,不是MODE_WAKE_AND_UNLOCK模式,不显示Bouncer
        if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
            instantExpandNotificationsPanel();
        } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
            instantCollapseNotificationPanel();
        }
    }

instantExpandNotificationsPanel
@Override
    public void instantExpandNotificationsPanel() {
        makeExpandedVisible(true);
        //展开滑动锁屏
        mNotificationPanel.expand(false /* animate */);
        
    }

makeExpandedVisible
void makeExpandedVisible(boolean force) {
        ...
        mStatusBarWindowController.setPanelVisible(true);
        ...
    }

StatusBarWindowController.setPanelVisible
public void setPanelVisible(boolean visible) {
        ...
        apply(mCurrentState);
    }

apply
private void apply(State state) {
        applyKeyguardFlags(state);
        applyForceStatusBarVisibleFlag(state);
        applyFocusableFlag(state);
        applyForceShowNavigationFlag(state);
        adjustScreenOrientation(state);
        applyHeight(state);
        applyUserActivityTimeout(state);
        applyInputFeatures(state);
        applyFitsSystemWindows(state);
        applyModalFlag(state);
        applyBrightness(state);
        applyHasTopUi(state);
        applyNotTouchable(state);
        applyStatusBarColorSpaceAgnosticFlag(state);
        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
            mWindowManager.updateViewLayout(mStatusBarView, mLp);
        }
        if (mHasTopUi != mHasTopUiChanged) {
            try {
                mActivityManager.setHasTopUi(mHasTopUiChanged);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to call setHasTopUi", e);
            }
            mHasTopUi = mHasTopUiChanged;
        }
        notifyStateChangedCallbacks();
    }

设置了一些数据,接着调用mNotificationPanel.expand(true)展开滑动锁屏,这样我们就能看到锁屏界面了,到此为止滑动锁屏的加载已经分析完了,只分析了一个大概流程,里面有很多细节的东西需要实际应用到之后再去跟代码

【用来参考】AndroidQ SystemUI之锁屏加载(上)滑动锁屏相关推荐

  1. 面试官:首屏加载速度慢怎么解决?

    文章目录 前言 首屏加载时间的计算 首屏的定义 首屏加载过程 计算首屏时间 加载慢的原因 面试中常涉及的解决方案 减小入口文件体积 懒加载 减小文件大小 静态资源本地缓存 UI框架按需加载 组件重复打 ...

  2. 单页应用首屏加载速度慢怎么解决?

    1.什么是首屏加载时间? 首屏加载时间是指浏览器从相应用户输入网址到首屏内容渲染完成的时间,从用户的角度来说就是:"地址栏输入网址之后网页呈现的速度".整个网站并不需要全部加载完成 ...

  3. SPA(单页应用)首屏加载慢的优化方案

    一. 什么是首屏加载时间? 首屏加载时间是指浏览器从相应用户输入网址到首屏内容渲染完成的时间. 整个网站并不需要全部加载完成,但需要展示当前可视窗口中的内容,也就是首屏. 从用户的角度来说就是:&qu ...

  4. Android6.0 keyguard锁屏加载流程分析

    锁屏界面的加载通常在android中有两种方式触发:android系统开机和screenOff(灭屏)后,再screenOn; 先来看 android系统开机时候的锁屏加载流程: 首先在系统启动过程中 ...

  5. Vue首屏加载白屏问题及解决方案

    Vue首屏加载白屏问题及解决方案 首先说一下首页加载为什么会白屏? 先说下 SPA 单页面的加载过程 首先就是 html ,也就是 FP 阶段 FP(全称"First Paint" ...

  6. 解决React首屏加载白屏的问题

    解决React首屏加载白屏的问题 参考文章: (1)解决React首屏加载白屏的问题 (2)https://www.cnblogs.com/smart-girl/p/10493205.html 备忘一 ...

  7. 项目遇到的问题总结(四):单页面首屏加载慢解决方案

    项目遇到的问题总结(四):单页面首屏加载慢解决方案 参考文章: (1)项目遇到的问题总结(四):单页面首屏加载慢解决方案 (2)https://www.cnblogs.com/myfirstboke/ ...

  8. vue首屏加载速度慢_Vue首屏加载速度优化如何提升80%?本文详解

    在Vue项目中,引入到工程中的所有js.css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏.若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首屏 ...

  9. 前端优化首屏加载速度

    执行npm run build,将打包代码部署上线后访问项目,会发现表现很糟糕,页面会出现长时间的空白等待,这是无法忍受的性能问题,迫切需要解决. 1.路由懒加载. 原来的路由引入组件 import ...

  10. Vue首屏加载速度优化,提升80%以上

    在Vue项目中,引入到工程中的所有js.css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏.若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首屏 ...

最新文章

  1. void main()是错的!
  2. 书单 | 春日必读书,少看一本都是遗憾
  3. poladuo network 轻松解决Windows系统棘手问题
  4. 2020人工神经网络第一次作业-参考答案第五部分
  5. googleapiclient.discovery
  6. JavaScript中getter/setter的实现
  7. 项目代码从GitHub上克隆到本地
  8. 【Java IO知识】读取中文乱码问题解决
  9. SQLServer2005出了点怪事~(应该是编码问题~)
  10. 用PHP写距离圣诞节还有多久,距离圣诞节还有多少天
  11. vivado 2017 安装教程
  12. Windows10 开机跳过密码验证
  13. PDF打开口令、PDF编辑限制如何解决
  14. 2345加速浏览器有哪些特点
  15. 威廉•欧奈尔选股七法
  16. Liquibase学习4 - 管理changelog
  17. java 抽象类和接口——抽象类
  18. 基于STM32单片机的差分升级(增量升级)算法
  19. fastposter v2.11.0 天花板级的海报生成器
  20. 部分RTMP协议的电视台直播地址

热门文章

  1. Android Bluetooth架构
  2. 嵌入式ERPC框架正式发布了
  3. C# 如何批量删除Excel单元格中的公式只保留数据
  4. 卖奥特曼卡片年入十亿:赚钱这事,你还得相信光的力量
  5. Android ApiDemos示例解析(110):Views-Data Widgets-1. Dialog
  6. python——个税计算器
  7. NOIP模拟赛 czy的后宫4
  8. 电力系统服务器是什么,什么是电网调度?
  9. 计算机网络和internet选项,大师为你详解win10系统设置Internet选项的处理
  10. 中国网络游戏界十大雷囧现象