Android Recovery升級原理

来源:https://www.cnblogs.com/linhaostudy/archive/2019/09/18/11543687.html
-Advertisement-
Play Games

摘要 Recovery模式指的是一種可以對安卓機內部的數據或系統進行修改的模式(類似於windows PE或DOS)。也可以稱之為安卓的恢復模式,在這個所謂的恢復模式下,我們可以刷入新的安卓系統,或者對已有的系統進行備份或升級,也可以在此恢復出廠設置(格式化數據和緩存)。 1. Recovery相關 ...


摘要

Recovery模式指的是一種可以對安卓機內部的數據或系統進行修改的模式(類似於windows PE或DOS)。也可以稱之為安卓的恢復模式,在這個所謂的恢復模式下,我們可以刷入新的安卓系統,或者對已有的系統進行備份或升級,也可以在此恢復出廠設置(格式化數據和緩存)。

1. Recovery相關概念

  • Recovery: Recovery模式指的是一種可以對安卓機內部的數據或系統進行修改的模式,也指Android的Recovery分區
  • OTA: Over-the-Air Technology,即空中下載技術,是 Android 系統提供的標準軟體升級方式。 它功能強大,提供了完全升級、增量升級模式,可以通過 SD 卡升級,也可以通過網路升級。不管是哪種方式,都有幾個過程:生成升級包、下載升級包、安裝升級包。
  • RecoverySystem:Android系統內部實現的一個工具類,Android應用層操作Recovery模式的一個重要途徑,它提供了幾個重要的API,用於實現OTA包校驗、升級以及恢復出廠設置(格式化數據和緩存)。
  • Main System:主系統模式,即Android正常開機所進入的Android系統
  • Bootloader:Bootloader是嵌入式系統在加電後執行的第一段代碼,在它完成CPU和相關硬體的初始化之後,再將操作系統映像或固化的嵌入式應用程式裝在到記憶體中然後跳轉到操作系統所在的空間,啟動操作系統運行。
  • BCB:Bootloader Control Block,啟動控制信息塊,位於misc分區,從代碼上看,就是一個結構體。

2. Android系統的啟動模式

2.1 Android 各個分區介紹

一般來說,安卓手機和平板一般包括以下標準內部分區:


Boot:包含Linux內核和一個最小的root文件系統(裝載到ramdisk中),用於掛載系統和其他的分區,並開始Runtime。正如名字所代表的意思(註:boot的意思是啟動),這個分區使Android設備可以啟動。如果沒有這個分區,Android設備通常無法啟動到Android系統。


System:這個分區幾乎包含了除kerner和ramdisk之外的整個android操作系統,包括了用戶界面、和所有預裝的系統應用程式和庫文件(AOSP中可以獲取到源代碼)。在運行的過程中,這個分區是read-only的。當然,一些Android設備,也允許在remount的情況下,對system分區進行讀寫。 擦除這個分區,相當於刪除整個安卓系統,會導致不能進入Main System, 但不會影響到Recovery。因此,可以通過進入Recovery程式或者bootloader程式中,升級安裝一個新ROM。


Userdata:用戶數據區,用戶安裝的應用程式會把數據保存在這裡,包含了用戶的數據:聯繫人、簡訊、設置、用戶安裝的程式。擦除這個分區,本質上等同於手機恢復出廠設置,也就是手機系統第一次啟動時的狀態,或者是最後一次安裝官方或第三方ROM後的狀態。在Recovery程式中進行的“data/factory reset ”操作就是在擦除這個分區。正常情況下OTA是不會清除這裡的數據的,指定要刪除數據的除外。


Cache:系統緩存區,臨時的保存應用數據(要把數據保存在這裡,需要特地的app permission), OTA的升級包也可以保存在這裡。OTA升級過程可能會清楚這個分區的數據。一般來講,Android差分包升級也需要依賴此分區存放一些中間文件。


Recovery:包括了一個完整Linux內核和一些特殊的recovery binary,可以讀取升級文件用這些文件來更新其他的分區。


Misc:一個非常小的分區,4 MB左右。recovery用這個分區來保存一些關於升級的信息,應對升級過程中的設備掉電重啟的狀況,Bootloader啟動的時候,會讀取這個分區裡面的信息,以決定系統是否進Recovery System 或 Main System。


以上幾個分區是Google官方的標準,對於第三方Android設備廠商來講,分區的情況可能稍微不一樣,比如Rockchip平臺,還增加了user分區、kernel分區和backup分區。其中:

kernel:顧名思義,是存放kernel.img鏡像的。在boot分區裡面的kernel內核鏡像損壞的情況下(比如flash損壞),bootloader會嘗試載入kerner分區裡面的內核鏡像。

backup:存放整個系統鏡像(update.img), 可用於恢復設備到出廠ROM。

user: 用戶分區,也就是平時我們所說的內置sdcard。另外還有外置的sdcard分區,用於存放用戶相片、視頻、文檔、ROM安裝包等。

2.2 Android的啟動模式

一般來講,Android有三種啟動模式:Fastboot模式,Recovery System 以及Main System。

  • Fastboot:在這種模式下,可以修改手機的硬體,並且允許我們發送一些命令給Bootloader。如使用電腦刷機,則需要進入fastboot模式,通過電腦執行命令將系統鏡像刷到通過USB刷到Android設備中中。
  • Recovery:Recovery是一個小型的操作系統,並且會載入部分文件系統,這樣才能從sdcard中讀取升級包。
  • Main System: 即我們平時正常開機後所使用的手機操作系統模式

首先說一下,正常啟動和進入Recovery的區別,一圖以概之:

image

2.3 如何進入Recovery模式

一般來講,進入recovery有兩種方式,一種是通過組合鍵進入recovery,按鍵指引的方式,各個Android平臺都不一樣,比如三星的手機是在關機狀態下同時按住【音量上】、【HOME鍵】、【電源鍵】,等待屏幕亮起後即可放開,進入Recovery模式。而Rockchip的機頂盒,則是使用按【Reset鍵】加【電源鍵】開機的方式,形式不一。

另一種,則是使用系統命令啟動到Recovery模式的,這對絕大部分Android設備是通用的:

reboot recovery

3. Recovery升級原理

3.1 應用層升級流程

在Android應用層部分,OTA系統升級流程。大概的流程圖如下所示:

image

以上部分,只介紹了應用層層面的 ota升級包的下載、校驗以及最後的發起安裝過程。在這裡,重要講解進入Recovery模式後,OTA包的升級過程。

首先,在應用層下載升級包後,會調用RecoverySystem.installPackage(Context context, File packageFile)函數來發起安裝過程,這個過程主要的原理,實際上只是往 /cache/recovery/command 寫入ota升級包存放路徑,然後重啟到recovery模式,僅此而已。

    public static void installPackage(Context context, File packageFile)
        throws IOException {
        String filename = packageFile.getCanonicalPath();
        Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");

        final String filenameArg = "--update_package=" + filename;
        final String localeArg = "--locale=" + Locale.getDefault().toString();
        bootCommand(context, filenameArg, localeArg);
    }
    
    private static void bootCommand(Context context, String... args) throws IOException {
        RECOVERY_DIR.mkdirs();  // In case we need it
        COMMAND_FILE.delete();  // In case it's not writable
        LOG_FILE.delete();
        FileWriter command = new FileWriter(COMMAND_FILE);
        try {
            for (String arg : args) {
                if (!TextUtils.isEmpty(arg)) {
                    command.write(arg);
                    command.write("\n");
                }
            }
        } finally {
            command.close();
        }
        // Having written the command file, go ahead and reboot
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        pm.reboot(PowerManager.REBOOT_RECOVERY);
        throw new IOException("Reboot failed (no permissions?)");
    }

因此,實質上等同於以下命令:

echo -e "--update_package=/mnt/sdcard/ota/update.zip" > /cache/recovery/command
reboot recovery

3.2 OTA升級包的目錄結構

OTA升級包的目錄結構大致如下所示:

|----boot.img
|----system/
|----recovery/
    |----recovery-from-boot.p
    |----etc/
            `|----install-recovery.sh
|---META-INF/
    |CERT.RSA
    |CERT.SF
    |MANIFEST.MF
    |----com/
           |----google/
                   |----android/
                          |----update-binary
                          |----updater-script
                   |----android/
                          |----metadata

其中:

  • boot.img 是更新boot分區所需要的鏡像文件。這個boot.img主要包括kernel、ramdisk。
  • system/目錄的內容在升級後會放在系統的system分區,主要是系統app,library和binary二進位文件
  • update-binary是一個二進位文件,相當於一個腳本解釋器,能夠識別updater-script中描述的操作。
  • updater-script:此文件是一個腳本文件,具體描述了更新過程。
  • metadata文件是描述設備信息及環境變數的元數據。主要包括一些編譯選項,簽名公鑰,時間戳以及設備型號等。
  • 我們還可以在包中添加userdata目錄,來更新系統中的用戶數據部分。這部分內容在更新後會存放在系統的/data目錄下。
  • update.zip包的簽名:update.zip更新包在製作完成後需要對其簽名,否則在升級時會出現認證失敗的錯誤提示。而且簽名要使用和目標板一致的加密公鑰。預設的加密公鑰及加密需要的三個文件在Android源碼編譯後生成的具體路徑為:
out/host/linux-x86/framework/signapk.jar
 
 build/target/product/security/testkey.x509.pem        
 
 build/target/product/security/testkey.pk8 
  • MANIFEST.MF:這個manifest文件定義了與包的組成結構相關的數據。類似Android應用的mainfest.xml文件。

  • CERT.RSA:與簽名文件相關聯的簽名程式塊文件,它存儲了用於簽名JAR文件的公共簽名。

  • CERT.SF:這是JAR文件的簽名文件,其中首碼CERT代表簽名者。

3.3 Recovery模式下的OTA升級流程

進入Recovery模式之後,便開始對下載的升級包進行升級,整體的流程圖如下所示:

BCB(Bootloader與Recovery通過BCB(Bootloader Control Block)通信)

image

這裡,詳解介紹一下升級流程中的各個模塊。

1. get_args(&argc, &argv)

get_args的原理流程圖如下所示:

get_args()函數的主要作用是獲取系統的啟動參數,並回寫到bootloader control block(BCB)塊中。如果系統在啟動recovery時已經傳遞了啟動參數,那麼這個函數只是把啟動參數的內容複製到函數的參數boot對象中,否則函數會首先通過get_bootloader_message()函數從/misc分區的BCB中獲取命令字元串來構建啟動參數。如果/misc分區下沒有內容,則會嘗試解析/cache/recovery/command文件並讀取文件的內容來建立啟動參數。

接著,會把啟動參數的信息通過set_bootloader_message()函數又保存到了BCB塊中。這樣做的目的是防止一旦升級或擦除數據的過程中發生崩潰或不正常斷電,下次重啟,Bootloader會依據BCB的指示,引導進入Recovery模式,從/misc分區中讀取更新的命令,繼續進行更新操作。因此,可以說是一種掉電保護機制。

get_args具體的流程如下圖所示:

image

get_args函數核心代碼如下:

static void get_args(int *argc, char ***argv) {
    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    //解析BCB模塊
    get_bootloader_message(&boot);  // this may fail, leaving a zeroed structure

    ......
    
     // --- if that doesn't work, try the command file
    if (*argc <= 1) {
        FILE *fp = fopen_path(COMMAND_FILE, "r");//COMMAND_FILE指/cache/recovery/command
        if (fp != NULL) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                if (!fgets(buf, sizeof(buf), fp)) break;
                (*argv)[*argc] = strdup(strtok(buf, "\r\n"));  // Strip newline.
            }

            check_and_fclose(fp, COMMAND_FILE);
            LOGI("Got arguments from %s\n", COMMAND_FILE);
        }
    }
    ......
    set_bootloader_message(&boot); //回寫BCB

這裡需要說一下“BCB”,即bootloader control block, 中文可以呼之為“啟動控制模信息塊”**,位於/misc分區,從代碼上看,就是一個struct 結構體 :

struct bootloader_message {  
    char command[32];  
    char status[32];  
    char recovery[1024];  
}; 

bootloader_message 結構體包含三個欄位,具體含義如下:

command 欄位中存儲的是命令,它有以下幾個可能值:

  • boot-recovery:系統將啟動進入Recovery模式
  • update-radia 或者 update-hboot:系統將啟動進入更新firmware的模式,這個更新過程由bootloader完成
  • NULL:空值,系統將啟動進入Main System主系統,正常啟動。

status 欄位存儲的是更新的結果。更新結束後,由Recovery或者Bootloader將更新結果寫入到這個欄位中。

recovery 欄位存放的是recovry模塊的啟動參數,一般包括升級包路徑。其存儲結構如下:第一行存放字元串“recovery”,第二行存放路徑信息“–update_package=/mnt/sdcard/update.zip”等。 因此,參數之間是以“\n”分割的。

2. update_package

ota升級包的存放路徑,從BCB或者/cache/recovery/command裡面解析得到的,升級包一般下載後存放在cache或sdcard分區,當然,也有一些是存放到U盤之類的外接存儲設備中的。一般賦值格式如下:

--update_package=/mnt/sdcard/update.zip 或 --update_package=CACHE:update.zip

3. int install_package (const char path, int wipe_cache, const char* install_file)

int install_package(const char* path, int* wipe_cache, const char* install_file)
{
    //install_file 為 /cache/recovery/last_install
    FILE* install_log = fopen_path(install_file, "w");
    if (install_log) {
        fputs(path, install_log);
        fputc('\n', install_log);
    } else {
        LOGE("failed to open last_install: %s\n", strerror(errno));
    }

    int result = really_install_package(path, wipe_cache); 
    if (install_log) {
        fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
        fputc('\n', install_log);
        fclose(install_log);
    }
    return result;
}

4. static int really_install_package(const char path, int wipe_cache)

really_install_package函數在install_package函數中被調用,函數的主要作用是調用ensure_path_mounted確保升級包所在的分區已經掛載,另外,還會對升級包進行一系列的校驗,在具體升級時,對update.zip包檢查時大致會分三步:

  1. 檢驗SF文件與RSA文件是否匹配;

  2. 檢驗MANIFEST.MF與簽名文件中的digest是否一致;

  3. 檢驗包中的文件與MANIFEST中所描述的是否一致

通過校驗後,調用try_update_binary函數去實現真正的升級。

5. static int try_update_binary(const char path, ZipArchive zip, int wipe_cache)

try_update_binary是真正實現對升級包進行升級的函數:

static int try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {

    const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
    ......
    const char* binary = "/tmp/update_binary";
    unlink(binary);
    int fd = creat(binary, 0755); 
    .....
    //將升級包裡面的update_binary解壓到/tmp/update_binary
    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
    close(fd);
    mzCloseZipArchive(zip);
    ......
    int pipefd[2];
    pipe(pipefd);

    const char** args = (const char**)malloc(sizeof(char*) * 5);
    args[0] = binary; //update_binary存放路徑
    args[1] = EXPAND(RECOVERY_API_VERSION);  // Recovery版本號
    char* temp = (char*)malloc(10);
    sprintf(temp, "%d", pipefd[1]);
    args[2] = temp;
    args[3] = (char*)path; //升級包存放路徑
    args[4] = NULL;

    pid_t pid = fork();//fork一個子進程
    if (pid == 0) {
        close(pipefd[0]);
        //子進程調用update-binary執行升級操作
        execv(binary, (char* const*)args);
        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
        _exit(-1);
    }
    ......
    int status;
    waitpid(pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        //安裝失敗,返回INSTALL_ERROR
        return INSTALL_ERROR;
    }
     //安裝成功,返回INSTALL_SUCCESS
    return INSTALL_SUCCESS;
}

總的來說,try_update_binary主要做了以下幾個操作:

(1)mzOpenZipArchive():打開升級包,並將相關的信息拷貝到一個臨時的ZipArchinve變數中。註意這一步並未對我們的update.zip包解壓。

(2)mzExtractZipEntryToFile(): 解壓升級包特定文件,將升級包裡面的META-INF/com/google/android/update-binary 解壓到記憶體文件系統的/tmp/update_binary中。

(3)fork創建一個子進程 , 使用系統調用函數execv( ) 去執行/tmp/update-binary程式,

(4)update-binary: 這個是Recovery OTA升級的核心程式,是一個二進位文件,實現代碼位於系統源碼bootable/recovery/updater。其實質是相當於一個腳本解釋器,能夠識別updater-script中描述的操作並執行。

(5)updater-script:updater-script是我們升級時所具體使用到的腳本文件,具體描述了更新過程,它主要用以控制升級流程的主要邏輯。具體位置位於升級包中/META-INF/com/google/android/update-script,在我們製作升級包的時候產生。在升級的時候,由update_binary程式從升級包裡面解壓到記憶體文件系統的/tmp/update_script中,並按照update_script裡面的命令,對系統進行升級。比如,一個完整包升級的update_script的內容大致如下所示:


assert(getprop("ro.product.device") == "rk31sdk" ||
       getprop("ro.build.product") == "rk301dk");
show_progress(0.500000, 0);
format("ext4", "EMMC", "/dev/block/mtd/by-name/system", "0", "/system");
mount("ext4", "EMMC", "/dev/block/mtd/by-name/system", "/system");
package_extract_dir("recovery", "/system");
package_extract_dir("system", "/system");
symlink("Roboto-Bold.ttf", "/system/fonts/DroidSans-Bold.ttf");
symlink("mksh", "/system/bin/sh");
......
set_perm_recursive(0, 0, 0755, 0644, "/system");
set_perm_recursive(0, 2000, 0755, 0755, "/system/bin");
......
set_perm(0, 0, 06755, "/system/xbin/su");
set_perm(0, 0, 06755, "/system/xbin/tcpdump");
show_progress(0.200000, 0);
show_progress(0.200000, 10);
write_raw_image(package_extract_file("boot.img"), "boot");
show_progress(0.100000, 0);
clear_misc_command();
unmount("/system");

update_script常用的命令如下:

image

因此,根據上面的升級腳本,可以知道,升級包的大致升級流程如下:

  1. 判斷是不是升級包是否適用於該設備,如果不適用,則停止升級,否則繼續。
  2. 顯示進度條
  3. 格式化system分區
  4. 掛載system分區
  5. 將ota升級包裡面的system、recovery目錄解壓到system分區
  6. 建立一些軟鏈接,升級過程需要用到
  7. 設置部分文件許可權
  8. 將升級包裡面的boot.img寫入到/boot分區
  9. 清空misc分區,即BCB塊置為NULL
  10. 卸載system分區

6. wipe data/cache

main函數,在執行完install_package後,會根據傳入的wipe_data/wipe_cache,決定是否執行/data和/cache分區的清空操作。

7. prompt_and_wait

這個函數的作用就是一直在等待用戶輸入,是一個不斷的迴圈,可以選擇Recovery模式下的一些選項進行操作,包括恢復出廠設置和重啟等。如果升級失敗, prompt_and_wait會顯示錯誤,並等待用戶響應。

8. finish_recovery

OTA升級成功,清空misc分區(BCB置零),並將保存到記憶體系統的升級日誌/tmp/recovery.log保存到/cache/recovery/last_log。重啟設備進入Main System,升級完成。

9. install-recovery.sh

從上面的流程中,可以知道,Recovery模式下的OTA升級成功,只是更新了/system和/boot兩個最核心的分區,而本身用來升級的Recovery自身並沒有在那個時候得到更新。Recovery分區的更新,是在重啟進入主系統的時候,由install-recovery.sh來更新的。這樣可以保證,即使升級失敗,Recovery模式也不會受到影響,仍然可以手動進入Recovery模式執行升級或擦除數據操作。

在Recovery升級的時候,有一句:

package_extract_dir("recovery", "/system");

這條命令就是將升級包裡面的recovery目錄的內容,解壓到/system分區

recovery目錄下的文件,主要有install-recovery.sh和 recovery-from-boot.p,目錄結構如下所示:

├── bin
│   └── install-recovery.sh
└── recovery-from-boot.p

其中:

  • recovery-from-boot.p 是boot.img和recovery.img的補丁(patch)
  • install-recovery.sh 則是來用安裝recovery-from-boot.p的升級腳本, 主要是利用android系統的 applypatch 工具來打補丁。

至此,一個完整的OTA包升級就正式完成了!

4. Bootloader、BCB、Recovery與Main System之間的交互

首先,通過前面的介紹,可以知道, Recovery System與Main System的交互,主要是通過/cache分區下的文件進行信息交互的。具體如下:

image

其中,command的值一般有以下一個或多個:

image

其次,Bootloader與Recovery和Main System之間也是存在交互的: Bootloader會通過解析BCB模塊,決定啟動系統到Recovery或Main System。而Recovery或Main System也能夠操作BCB,進而影響到Bootloader的行為。

當Main System系統關鍵進程崩潰太多次的時候,系統還會自發啟動進入到Recovery模式。

另外,部分平臺的Android設備,在Recovery模式下,也能夠對Bootloader進行升級。

Bootloader、BCB、Recovery與Main System四者相互影響,又獨立工作。它們之間斬不斷理還亂的關係,可以以下圖概括之:

image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.電腦基礎 二進位 由於電腦運行時全補是使用二進位運行的,所以必須要學習二進位的轉換 二進位|十六進位|十進位|2的n次方 | | | 00000001|01|01|2^0 00000010|02|02|2^1 00000100|04|04|2^2 00001000|08|08|2^3 000 ...
  • 鍵入dpkg reconfigure locales ...
  • 我們在linux下配置了tomcat後發現,無法訪問除了linux(如果是虛擬機的話,宿主機子根本無法訪問tomcat),解決下吧 原因是我們的tomcat訪問需要8080埠,但是從外部訪問,我們的防火牆會攔截,而你此時又恰好沒有配置8080埠,也沒有關閉防火牆,那麼就導致這樣咯, 解決步驟: ...
  • linux上古神器thefuck, 裝機必備, 命令行輸入錯誤殺手 ...
  • 更換壁紙 ​ 在kde的桌面右鍵 配置桌面 壁紙里更換壁紙,我不能直接添加圖像並應用。我的系統在這樣操作後重啟就會發現一切都被重置了。剛剛添加的圖片也不見了。 ​ 於是,我就模範原本存在壁紙文件夾里的文件,自己創建了一個壁紙文件夾 ​ 這是原本breath壁紙文件位置 我們在wallpapers文件 ...
  • CentOS 預設只有一個 root 用戶,但是 root 用戶的許可權過大,而且不利於多人協作,基於許可權管理和安全的原因,我們為系統新建一個用戶,並且使能其 SSH 登錄,同時禁止 root 用戶的登錄; ...
  • 一、ramdisk介紹 ramdisk通過直面意思就大概能理解意思,ram disk虛擬記憶體盤,將ram模擬成硬碟來使用的文件系統。對於傳統的磁碟文件系統來說,這樣做的好處是可以極大提高文件訪問速度;但由於是ram,所以 在掉電後,這部分內容不能保存。ramdisk文件系統是在系統上電後直接從磁碟一 ...
  • 比較 新的Linux發行版 已經沒有 文件了。因為已經將其服務化了。 解決方法: 1、設置 2、激活 3、添加啟動服務 手工創建或者拷貝已有的/etc/rc.local,並賦予執行許可權 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...