主頁 > 前端設計 > Android系統啟動流程(四)Launcher行程啟動程序決議(附帶面試題)

Android系統啟動流程(四)Launcher行程啟動程序決議(附帶面試題)

2020-10-08 10:50:21 前端設計

前面我們分析了init行程,zygote行程,SystemServer行程,本篇的Launcher是系統啟動流程的最后一個行程,

1 Launcher概述

Launcher行程是一個系統的應用程式,位于packages/apps/Launcher3中,它用于顯示已經安裝的應用程式,它通過訪問PackageManagerService獲取安裝的應用程式,然后將他們封裝成一個個的快捷圖示顯示到螢屏上,每一個圖示包含了被啟動應用程式的Intent資訊,點擊之后就可以啟動對應應用程式,

2 Launcher行程決議

private void startOtherServices() {
	mActivityManagerService.systemReady(new Runnable() {
		@Override
            public void run() {
			...
			mSystemServiceManager.startBootPhase(
                        SystemService.PHASE_ACTIVITY_MANAGER_READY);
		}
	}
}

Launcher的入口函式在System.startOtherServices方法中,我們進入systemReady方法中:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    public void systemReady(final Runnable goingCallback) {
    	...
		mStackSupervisor.resumeFocusedStackTopActivityLocked();
		...
	}

進入到了ActivityStackSupervisor.resumeFocusedStackTopActivityLocked中

frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

	
	boolean resumeFocusedStackTopActivityLocked() {
        return resumeFocusedStackTopActivityLocked(null, null, null);
    }

    boolean resumeFocusedStackTopActivityLocked(
            ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
        if (targetStack != null && isFocusedStack(targetStack)) {
            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
        }
        final ActivityRecord r = mFocusedStack.topRunningActivityLocked();
        if (r == null || r.state != RESUMED) {
            mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
        }
        return false;
    }

在上面最后呼叫到了mFocusedStack.resumeTopActivityUncheckedLocked,mFocusedStack是ActivityStack型別的,我們進入查看原始碼:

frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
        if (mStackSupervisor.inResumeTopActivity) {
            // Don't even start recursing.
            return false;
        }

        boolean result = false;
        try {
            // Protect against recursion.
            mStackSupervisor.inResumeTopActivity = true;
            if (mService.mLockScreenShown == ActivityManagerService.LOCK_SCREEN_LEAVING) {
                mService.mLockScreenShown = ActivityManagerService.LOCK_SCREEN_HIDDEN;
                mService.updateSleepIfNeededLocked();
            }
            result = resumeTopActivityInnerLocked(prev, options);
        } finally {
            mStackSupervisor.inResumeTopActivity = false;
        }
        return result;
    }

	private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        ...
        
        final TaskRecord prevTask = prev != null ? prev.task : null;
        if (next == null) {
            /***
             * 這里啟動了Launcher
             */
            return isOnHomeDisplay() &&
                    mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason);
        }
    }

上面從resumeTopActivityUncheckedLocked呼叫到了resumeTopActivityInnerLocked中,最后呼叫到了ActivityStackSupervisor.resumeHomeStackTask函式,

frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

	boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev, String reason) {
        ...
        
        // Only resume home activity if isn't finishing.
        if (r != null && !r.finishing) {
            mService.setFocusedActivityLocked(r, myReason);
            return resumeFocusedStackTopActivityLocked(mHomeStack, prev, null);
        }
        return mService.startHomeActivityLocked(mCurrentUser, myReason);
    }

接著進入到了ActivityManagerService.startHomeActivityLocked

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

boolean startHomeActivityLocked(int userId, String reason) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                && mTopAction == null) {
            // We are running in factory test mode, but unable to find
            // the factory test app, so just sit around displaying the
            // error message and don't try to start anything.
            return false;
        }
        // 1 
        Intent intent = getHomeIntent();
        ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
        if (aInfo != null) {
            intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
            // Don't do this if the home app is currently being
            // instrumented.
            aInfo = new ActivityInfo(aInfo);
            aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
            ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                    aInfo.applicationInfo.uid, true);
            if (app == null || app.instrumentationClass == null) {
                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
                mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
            }
        } else {
            Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
        }

        return true;
    }

在注釋1處的getHomeIntent方法代碼如下:

	Intent getHomeIntent() {
        Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        intent.setComponent(mTopComponent);
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_HOME);//1 
        }
        return intent;
    }

在注釋1處添加了一個CATEGORY_HOMECATEGORY_HOME = "android.intent.category.HOME",這里我們鎖定就是啟動了Launcher頁面,因為我們查看系統應用Launcher的清單檔案中匹配了它,Launcher的清單檔案如下:

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.launcher3">
    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="16"/>

    <uses-permission android:name="android.permission.CALL_PHONE" />
    ...
    
    <application
        android:allowBackup="@bool/enable_backup"
        android:backupAgent="com.android.launcher3.LauncherBackupAgentHelper"
        android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher_home"
        android:label="@string/app_name"
        android:largeHeap="@bool/config_largeHeap"
        android:restoreAnyVersion="true"
        android:supportsRtl="true" >

        <activity
            android:name="com.android.launcher3.Launcher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:theme="@style/Theme"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="nosensor"
            android:configChanges="keyboard|keyboardHidden|navigation"
            android:resumeWhilePausing="true"
            android:taskAffinity=""
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
            </intent-filter>
        </activity>      
    </application>
</manifest>

從上面可以看出Launcher的intent-filter匹配了上面的Intent,所以Launcher就被啟動起來了,就會執行onCreate方法,


3 Launcher行程圖示顯示程序

Launcher.onCreate函式如下:

packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
       
        super.onCreate(savedInstanceState);
		//1
        LauncherAppState app = LauncherAppState.getInstance();
		//2
        mModel = app.setLauncher(this);
       
        setContentView(R.layout.launcher);
        
        if (!mRestoring) {
           		//3
                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
            } else {
                // We only load the page synchronously if the user rotates (or triggers a
                // configuration change) while launcher is in the foreground
                mModel.startLoader(mWorkspace.getRestorePage());
            }
        }
    }

注釋1處單例獲取LauncherAppState的實體,在注釋2處呼叫它的setLauncher函式并將Launcher物件傳入,setLauncher函式如下所示:

packages/apps/Launcher3/src/com/android/launcher3/LauncherAppState.java

 	LauncherModel setLauncher(Launcher launcher) {
        getLauncherProvider().setLauncherProviderChangeListener(launcher);
        mModel.initialize(launcher);
        mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
            new LauncherAccessibilityDelegate(launcher) : null;
        return mModel;
    }

接著呼叫到了LauncherModel的initialize函式:

packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java

/**
     * Set this as the current Launcher activity object for the loader.
     */
    public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            // Disconnect any of the callbacks and drawables associated with ItemInfos on the
            // workspace to prevent leaking Launcher activities on orientation change.
            unbindItemInfosAndClearQueuedBindRunnables();
            mCallbacks = new WeakReference<Callbacks>(callbacks);
        }
    }

我們看到上面其實就是設定了一個回呼函式,回呼函式就是Launcher自身,不過是以一個弱參考的方式,這個回呼后面會用到,

接著分析onCreate中的注釋3的部分,launcherModel.startLoader方法:

  @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    static {
        sWorkerThread.start();
    }
    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());

	public void startLoader(int synchronousBindPage, int loadFlags) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        InstallShortcutReceiver.enableInstallQueue();
        synchronized (mLock) {
            // Clear any deferred bind-runnables from the synchronized load process
            // We must do this before any loading/binding is scheduled below.
            synchronized (mDeferredBindRunnables) {
                mDeferredBindRunnables.clear();
            }

            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                // If there is already one running, tell it to stop.
                stopLoaderLocked();
                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                } else {
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);
                }
            }
        }
    }

上面創建了一個HandlerThread的子執行緒的Handler,用來處理耗時任務,在上面的最后post了一個任務,這個任務的類是LoaderTask.java,實作了Runnable介面,run方法如下:

	public void run() {
            synchronized (mLock) {
                if (mStopped) {
                    return;
                }
                mIsLoaderTaskRunning = true;
            }
            // Optimize for end-user experience: if the Launcher is up and // running with the
            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
            // workspace first (default).
            keep_running: {
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                //1 
                loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                // second step
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                // 2
                loadAndBindAllApps();
            }

            // Clear out this reference, otherwise we end up holding it until all of the
            // callback runnables are done.
            mContext = null;

            synchronized (mLock) {
                // If we are still the last one to be scheduled, remove ourselves.
                if (mLoaderTask == this) {
                    mLoaderTask = null;
                }
                mIsLoaderTaskRunning = false;
                mHasLoaderCompletedOnce = true;
            }
        }

上面的兩個注釋寫的很清楚,在注釋1處loadAndBindWorkspace是加載作業空間,在注釋2處loadAndBindAllApps用來加載所有的app,

loadAndBindAllApps原始碼如下:

 	private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {
            	// 1 
                loadAllApps();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                }
                updateIconCache();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
                }
            } else {
                onlyBindAllApps();
            }
        }

注釋1處呼叫了loadAllApps(),原始碼如下:

	private void loadAllApps() {
		...
        mHandler.post(new Runnable() {
            public void run() {
                final long bindTime = SystemClock.uptimeMillis();
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                if (callbacks != null) {
                    callbacks.bindAllApplications(added);//1
                    ...
                }
            }
        });
       ...
    }

這里的callbacks就是上面弱參考存盤的Launcher,所以我們直接查看Launcher中的bindAllApplications,代碼如下:

packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

	 public void bindAllApplications(final ArrayList<AppInfo> apps) {
        if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
            mTmpAppsList = apps;
            return;
        }
        if (mAppsView != null) {
        	//1 
            mAppsView.setApps(apps);
        }
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.bindAllApplications(apps);
        }
    }

在注釋1處呼叫了 mAppsView的setApps,mAppsView是一個AllAppsContainerView型別的,我們查看它的setApps方法:

packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java

	private final AlphabeticalAppsList mApps;
	
	public void setApps(List<AppInfo> apps) {
        mApps.setApps(apps);
    }

這里我們查看onFinishInflate函式

    
    private AllAppsRecyclerView mAppsRecyclerView;
	
	@Override
    protected void onFinishInflate() {
        super.onFinishInflate();

		//1 初始化控制元件
        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
        //2 傳遞應用
        mAppsRecyclerView.setApps(mApps);
        mAppsRecyclerView.setLayoutManager(mLayoutManager);
        //3 設定adapter
        mAppsRecyclerView.setAdapter(mAdapter);
        mAppsRecyclerView.setHasFixedSize(true);
        mAppsRecyclerView.addOnScrollListener(mElevationController);
        mAppsRecyclerView.setElevationController(mElevationController);
    }

onFinishInflate在xml加載完成之后就會呼叫,這里launcher中app的顯示邏輯和我們平時將資料顯示到一個RecyclerView上基本一致,所以這里也比較好理解,

4 Android系統啟動流程

在此對整個Android系統啟動流程做一個全面的總結:

1 按Power鍵啟動電源及系統啟動

當按下電源鍵,引導芯片代碼開始從固化在ROM中預定義的地方開始執行,加載引導程式Bootloader到RAM,然后執行引導程式,

2 引導程式Bootloader

引導程式是Android作業系統被拉起來之前的一個程式,類似于window一樣,它的作用就是把系統拉起運行起來,它是針對特定的主板與芯片的,設備制造商要么使用很受歡迎的引導程式比如redboot、uboot、qibootloader或者開發自己的引導程式,它不是Android作業系統的一部分,引導程式是OEM廠商或者運營商加鎖和限制的地方,

3 linux內核啟動

Android內核與桌面linux內核啟動的方式差不多,內核啟動時,設定快取、被保護存盤器、計劃串列,加載驅動,當內核完成系統設定,它首先在系統檔案中尋找”init”檔案,然后啟動root行程或者系統的第一個行程,

4 init行程啟動
創建和掛載啟動所需要的檔案目錄,初始化和啟動屬性服務,決議init.rc配置并且啟動Zygote行程,

5 Zygote行程啟動

創建JavaVM并為JavaVM注冊JNI,創建服務端Socket,啟動SystemServer行程,

6 SystemServer行程啟動

啟動Binder執行緒池和SystemServiceManager,并且啟動各種系統服務,

7 Launcher啟動

被SystemServer行程啟動的ActivityManagerService會啟動Launcher,Launcher啟動后會將已安裝應用的快捷圖示顯示到界面上,

5 面試題

1 Android系統啟動流程? 答案如上所述

2 什么是寫時拷貝(copy- on-write)?

寫時拷貝copy_on_write技術: fork的實作是使用copy_on_write技術實作的,它是一種可以推遲甚至避免拷貝的技術,內核此時并不復制整個行程的地址空間,而是讓父子行程共享同一個地址空間,只有在子行程真正需要寫入資料的時候才會賦值地址空間,從而使得每個行程擁有自己的行程空間,也就是順澩的復制是在需要寫入資料的時候才會進行,在此之前只會以只讀的形式共享,

3 孤兒行程和僵尸行程是什么?

fork系統呼叫之后,父子行程將交替執行,執行順序不定,如果父行程先退出,子行程還沒退出那么子行程的父行程將變為init行程(托孤給了init行程),(注:任何一個行程都必須有父行程)如果子行程先退出,父行程還沒退出,那么子行程必須等到父行程捕獲到了子行程的退出狀態才真正結束,否則這個時候子行程就成為僵行程(僵尸行程:只保留一些退出資訊供父行程查詢).

4 system_server 為什么要在 Zygote 中啟動,而不是由 init 直接啟動呢?

Zygote 作為一個范訓器,可以提前加載一些資源,這樣 fork() 時基于 Copy-On-Write 機制創建的其他行程就能直接使用這些資源,而不用重新加載,比如 system_server 就可以直接使用 Zygote 中的 JNI函式、共享庫、常用的類、以及主題資源,

5 為什么要專門使用 Zygote 行程去范訓應用行程,而不是讓 system_server 去范訓呢?

system_server 相比 Zygote 多運行了 AMS、WMS 等服務,這些對一個應用程式來說是不需要的,另外行程的 fork() 對多執行緒不友好,僅會將發起呼叫的執行緒拷貝到子行程,這可能會導致死鎖,而system_server 中肯定是有很多執行緒的,

6 多執行緒的行程fork呼叫為什么會導致死鎖?

多執行緒的行程的fork呼叫:復制整個用戶空間的資料(通常使用 copy-on-write 的策略,
所以可以實作的速度很快)以及所有系統物件,然后僅復制當前執行緒到子行程,這里:所有父行程中別的執行緒,到了子行程中都是突然蒸發掉的假設這么一個環境,在 fork 之前,有一個子執行緒 lock 了某個鎖,獲得了對鎖的所有權,fork 以后,在子行程中,所有的額外執行緒都人間蒸發了,而鎖卻被正常復制了,在子行程看來,這個鎖沒有主人,所以沒有任何人可以對它解鎖,當子行程想 lock 這個鎖時,不再有任何手段可以解開了,程式發生死鎖

7 Zygote 為什么不采用 Binder 機制進行 IPC 通信?

Binder 機制中存在 Binder 執行緒池,是多執行緒的,如果 Zygote 采用 Binder 的話就存在上面說的fork() 與 多執行緒的問題了,其實嚴格來說,Binder 機制不一定要多執行緒,所謂的 Binder 執行緒只不過是在回圈讀取 Binder 驅動的訊息而已,只注冊一個 Binder 執行緒也是可以作業的,比如 service manager就是這樣的,實際上 Zygote 盡管沒有采取 Binder 機制,它也不是單執行緒的,但它在 fork() 前主動停止了其他執行緒,fork() 后重新啟動了,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/162829.html

標籤:其他

上一篇:從一次專案重構說起CSS3自定義變數在專案中是如何使用的?

下一篇:c語言字串處理報錯 warning,C4996,sprintf,deprecated,C4996,strcpy,C4996,strcat

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more