Android Fragment使用(三) Activity, Fragment, WebView的狀態保存和恢復

来源:http://www.cnblogs.com/mengdd/archive/2016/06/13/5582244.html
-Advertisement-
Play Games

Android中的狀態保存和恢復, 包括Activity和Fragment以及其中View的狀態處理. Activity的狀態除了其中的View和Fragment的狀態之外, 還需要用戶手動保存一些成員變數. Fragment的狀態有它自己的實例狀態和其中的View狀態, 因為其生命周期的靈活性和實... ...


Android中的狀態保存和恢復

Android中的狀態保存和恢復, 包括Activity和Fragment以及其中View的狀態處理.
Activity的狀態除了其中的View和Fragment的狀態之外, 還需要用戶手動保存一些成員變數.
Fragment的狀態有它自己的實例狀態和其中的View狀態, 因為其生命周期的靈活性和實際需要的不同, 情況會多一些.
根據源碼, 列出了Fragment中實例狀態和View狀態保存和恢復的幾個入口, 便於分析查看.
最後專門講了WebView狀態保存和恢復, 問題及處理.
還有一個工具類icepick的介紹.

Activity的狀態保存和恢復

作為熱身, 先來講一下Activity的狀態保存和恢復.

什麼時候需要恢復Activity

關於Activity的銷毀和重建, 之前有這麼一篇博文: Activity的重新創建
總結來說, 就是Activity的銷毀, 分為徹底銷毀和留下數據的銷毀兩種.

徹底銷毀是指用戶主動去關閉或退出這個Activity. 此時是不需要狀態恢復的, 因為下次回來又是重新創建全新的實例.
留下數據的銷毀是指系統銷毀了activity, 但是當用戶返回來時, 會重新創建它, 讓用戶覺得它一直都在.

屏幕旋轉重建可以歸結為第二種情況, 打開Do not keep activities開關, 切換activities也是會出現第二種情況.
打開Do not keep activities開關就是為了模擬記憶體不足時的系統行為, 這裡有一篇分析

如何恢復

實際上系統已經幫我們做好了View層面基本的恢復工作, 主要是依靠下麵兩個方法:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // 在onStop()之前調用, 文檔中說並不保證在onPause()的之前還是之後
        // 我的試驗中一般是在onPause()之後
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        // 在onStart() 之後
    }

Bundle其中包含了activity中的view和fragment的各種信息, 所以調用基類的方法就可以完成基本的view層面的恢復工作.
註意這兩個方法並不是activity的生命周期回調, 對於activity來說它們不是一定會發生的.
另外需要註意的是, View必須要有id才能被恢復.

舉一個實例來說明:
Activity A start B, 那麼A的onSaveInstanceState()會在onStop()之前調用, 以防A被系統銷毀.
但是在B中按下back鍵finish()了自己後, B被銷毀的過程中, 並沒有調用onSaveInstanceState(), 是因為B並沒有被壓入task的back stack中,
也即系統知道B並不需要儲存自己的狀態.
正常情況下, 返回到A, A沒有被銷毀, 也不會調用onRestoreInstanceState(), 因為所有的狀態都還在, 並不需要重建.

如果我們打開了Do not keep activities開關, 模擬系統記憶體不足時的行為, 從A到B, 可以看到當B resume的時候A會一路走到onDestroy(),
而關掉B之後, A會從onCreate()開始走, 此時onCreate()的參數bundle就不為空了, onStart()之後會調用onRestoreInstanceState()方法, 其參數bundle中內容類似於如下:

Bundle[{android:viewHierarchyState=Bundle[mParcelledData.dataSize=272]}]

其中包含了View的狀態, 如果有Fragment, 也會包含Fragment的狀態, 其實質是保存了FragmentManagerState, 內容類似於如下:

Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@bc382e7, 2131492950=CompoundButton.SavedState{4034f96 checked=true}, 2131492951=android.view.AbsSavedState$1@bc382e7}}], android:fragments=android.app.FragmentManagerState@bacc717}]

對於上面的例子來說, B什麼時候會調用onSaveInstanceState()呢?
當從A打開B之後, 按下Home鍵, B就會調用onSaveInstanceState().
因為這時候系統不知道用戶什麼時候會返回, 有可能會把B也銷毀了, 所以保存一下它的狀態.
如果下次回來它沒有被重建, onRestoreInstanceState()就不會被調用, 如果它被重建了, onRestoreInstanceState()才會被調用.

Activity保存方法的調用時機

activity的onSaveInstanceState()onRestoreInstanceState()方法在如下情形下會調用:

  1. 屏幕旋轉重建: 先save再restore.
  2. 啟動另一個activity: 當前activity在離開前會save, 返回時如果因為被系統殺死需要重建, 則會從onCreate()重新開始生命周期, 調用onRestoreInstanceState(); 如果沒有重建, 則不會調用onCreate(), 也不會調用onRestoreInstanceState(), 生命周期從onRestart()開始, 接著onStart()和onResume().
  3. 按Home鍵的情形和啟動另一個activity一樣, 當前activity在離開前會save, 用戶再次點擊應用圖標返回時, 如果重建發生, 則會調用onCreate()和onRestoreInstanceState(); 如果activity不需要重建, 只是onRestart(), 則不會調用onRestoreInstanceState().

Activity恢復方法的調用時機

activity的onSaveInstanceState()onRestoreInstanceState()方法在如下情形下不會調用:

  1. 用戶主動finish()掉的activity不會調用onSaveInstanceState(), 包括主動按back退出的情況.
  2. 新建的activity, 從onCreate()開始, 不會調用onRestoreInstanceState().

Activity中還需要手動恢復什麼

如上, 系統已經為我們恢復了activity中的各種view和fragment, 那麼我們自己需要保存和恢復一些什麼呢?
答案是成員變數值.

因為系統並不知道你的各種成員變數有什麼用, 哪些值需要保存, 所以需要你自己覆寫上面兩個方法, 然後把自己需要保存的值加進bundle裡面去. 具體例子, 這裡Activity的重新創建有, 我就不重覆了.
重要的是不要忘記調用super的方法, 那裡有系統幫我們恢復的工作.

工具類Icepick介紹

在介紹下麵的內容之前, 先介紹一個小工具: Icepick
這個工具的作用是, 在你想保存和重建自己的成員變數數據時, 幫你省去那些put和get方法的調用, 你也不用為每一個欄位起一個常量key.
你需要做的就是簡單地在你想要保存狀態的欄位上面加上一個@State 註解.
然後在保存和恢復的時候分別加上一句話:

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }

然後你的成員變數就有了它應該有的值了, DONE!

Fragment的狀態保存和恢復

Fragment的狀態比Activity的要複雜一些, 因為它的生命周期狀態比較多.

Fragment狀態保存和恢復的相關方法

按照上面的思路, 我先去查找Fragment中保存和恢復的回調方法了.
Fragment的狀態保存回調是這個方法:

    public void onSaveInstanceState(Bundle outState) {
        // may be called any time before onDestroy()
    }

這個方法和之前activity的情況大體是類似的, 它不是生命周期的回調, 所以只在有需要的時候會調到.
onSaveInstanceState()在activity調用onSaveInstanceState()的時候發生, 用於保存實例狀態.(看它的方法名: instance state).
onSaveInstanceState()方法保存的bundle會返回給幾個生命周期回調: onCreate(), onCreateView(), onViewCreated()onActivityCreated().

Fragment並沒有對應的onRestoreInstanceState()方法.
也即沒有實例狀態的恢復回調.

Fragment只有一個onViewStateRestored()的回調方法:

    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        // 在onActivityCreated()和onStart()之間調用
        mCalled = true;
    }

onViewStateRestored()每次新建Fragment都會發生.
它並不是實例狀態恢復的方法, 只是一個View狀態恢復的回調.

這裡需要註意, Fragment的狀態分兩個類型: 實例狀態和View狀態.
這裡有個最佳實踐: The Real Best Practices to Save/Restore Activity's and Fragment's state
不要把Fragment的實例狀態和View狀態混在一起處理.

在這裡我先上個結論, 把查看源碼中Fragment狀態保存和恢復的相關方法列出來:

Fragment狀態保存入口:
Fragment狀態保存

Fragment的狀態保存入口有三個:

  1. Activity的狀態保存, 在Activity的onSaveInstanceState()里, 調用了FragmentManger的saveAllState()方法, 其中會對mActive中各個Fragment的實例狀態和View狀態分別進行保存.
  2. FragmentManager還提供了public方法: saveFragmentInstanceState(), 可以對單個Fragment進行狀態保存, 這是提供給我們用的, 後面會有例子介紹這個. 其中調用的saveFragmentBasicState()方法即為情況一中所用, 圖中已畫出標記.
  3. FragmentManager的moveToState()方法中, 當狀態回退到ACTIVITY_CREATED, 會調用saveFragmentViewState()方法, 保存View的狀態.

moveToState()方法中有很長的switch case, 中間不帶break, 基本是根據新狀態和當前狀態的比較, 分為正向創建和反向銷毀兩個方向, 一路沿著多個case走下去.

Fragment狀態恢復入口:
Fragment狀態恢復

三個恢復的入口和三個保存的入口剛好對應.

  1. 在Activity重新創建的時候, 恢復所有的Fragment狀態.
  2. 如果調用了FragmentManager的方法: saveFragmentInstanceState(), 返回值得到的狀態可以用Fragment的setInitialSavedState()方法設置給新的Fragment實例, 作為初始狀態.
  3. FragmentManager的moveToState()方法中, 當狀態正向創建到CREATED時, Fragment自己會恢復View的狀態.

這三個入口分別對應的情況是:
入口1對應系統銷毀和重建新實例.
入口2對應用戶自定義銷毀和創建新Fragment實例的狀態傳遞.
入口3對應同一Fragment實例自身的View狀態重建.

Fragment狀態保存恢復和Activity的聯繫

這裡對應的是入口1的情況.
當Activity在做狀態保存和恢復的時候, 在它其中的fragment自然也需要做狀態保存和恢復.
所以Fragment的onSaveInstanceState()在activity調用onSaveInstanceState()的時候一定會發生.
同樣的, 如果Fragment中有一些成員變數的值在此時需要保存, 也可以用@State標記, 處理方法和上面一樣.
也即, 在Activity需要保存狀態的時候, 其中的Fragments的實例狀態自動被處理保存.

Fragment同一實例的View狀態恢復

這裡對應的是入口3的情況.
前面介紹過, activity在保存狀態的時候, 會將所有View和Fragment的狀態都保存起來等待重建的時候使用.
但是如果是單個Activity對應多個Fragments的架構, Activity永遠是resume狀態, 多個Fragments在切換的過程中, 沒有activity的幫助, 如何保存自己的狀態?

首先, 取決於你的多個Fragments是如何初始化的.
我做了一個實驗, 在activity的onCreate()裡面初始化兩個Fragment:

private void initFragments() {
    tab1Fragment = getFragmentManager().findFragmentByTag(Tab1Fragment.TAG);
    if (tab1Fragment == null) {
        tab1Fragment = new Tab1Fragment();
    }
    tab2Fragment = getFragmentManager().findFragmentByTag(Tab2Fragment.TAG);
    if (tab2Fragment == null) {
        tab2Fragment = new Tab2Fragment();
    }
}

然後點擊兩個按鈕來切換它們, replace(), 並且不加入到back stack中:

@OnClick(R.id.tab1)
void onTab1Clicked() {
    getFragmentManager().beginTransaction()
            .replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG)
            .commit();
}

@OnClick(R.id.tab2)
void onTab2Clicked() {
    getFragmentManager().beginTransaction()
            .replace(R.id.content_container, tab2Fragment, Tab2Fragment.TAG)
            .commit();

}

可以看到, 每一次的切換, 都是一個Fragment的完全destroy, detach和另一個fragment的attach, create,
但是當我在這兩個fragment中各自加上EditText, 發現只要EditText有id, 切換過程中EditText的內容是被保存的.
這是誰在什麼時候保存並恢復的呢?
我在TextChange的回調里打了斷點, 發現調用棧如下:
Fragment restore view
FragmentManagerImpl中, moveToState()方法的case Fragment.CREATED中:
調用了: f.restoreViewState(f.mSavedFragmentState);
此時我沒有做任何保存狀態的處理, 但是斷點中可以看出:
Fragment states
雖然mSavedFragmentState是null, 但是mSavedViewState卻有值.
所以這個View狀態保存和恢復對應的入口即是上面兩個圖中的入口三.

這是因為我的兩個fragment只new了一次, 然後保存了成員變數, 即便是Fragment重新onCreate(), 但是對應的實例仍然是同一個.
這和Activity是不同的, 因為你是無法new一個Activity的.

在上面的例子中, 如果不保存Fragment的引用, 每次都new Fragment, 那麼View的狀態是不會被保存的, 因為不同實例間的狀態傳遞只有在系統銷毀恢復的情況下才會發生(入口一).
如果我們需要在不同的實例間傳遞狀態, 就需要用到下麵的方法.

不同Fragment實例間的狀態保存和恢復

這裡對應的是入口2, 不同於入口1和3, 它們是自動的, 入口2是用戶主動保存和恢復的情形.
自己主動保存Fragment的狀態, 可以調用FragmentManager的這個方法:

public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);

它的實現是這樣的:

@Override
public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {
    if (fragment.mIndex < 0) {
        throwException(new IllegalStateException("Fragment " + fragment
                + " is not currently in the FragmentManager"));
    }
    if (fragment.mState > Fragment.INITIALIZING) {
        Bundle result = saveFragmentBasicState(fragment);
        return result != null ? new Fragment.SavedState(result) : null;
    }
    return null;
}

返回的數據類型是: Fragment.SavedState, 這個state可以通過Fragment的這個方法設置給自己:

public void setInitialSavedState(SavedState state) {
    if (mIndex >= 0) {
        throw new IllegalStateException("Fragment already active");
    }
    mSavedFragmentState = state != null && state.mState != null
            ? state.mState : null;
}

但是註意只能在Fragment被加入之前設置, 這是一個初始狀態.
利用這兩個方法可以更加自由地保存和恢復狀態, 而不依賴於Activity.
這樣處理以後, 不必保存Fragment的引用, 每次切換的時候雖然都new了新的實例, 但是舊的實例的狀態可以設置給新實例.

例子代碼:

@State
SparseArray<Fragment.SavedState> savedStateSparseArray = new SparseArray<>();

void onTab1Clicked() {
    // save current tab
    Fragment tab2Fragment = getSupportFragmentManager().findFragmentByTag(Tab2Fragment.TAG);
    if (tab2Fragment != null) {
        saveFragmentState(1, tab2Fragment);
    }

    // restore last state
    Tab1Fragment tab1Fragment = new Tab1Fragment();
    restoreFragmentState(0, tab1Fragment);

    // show new tab
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG)
            .commit();
}

private void saveFragmentState(int index, Fragment fragment) {
    Fragment.SavedState savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
    savedStateSparseArray.put(index, savedState);
}

private void restoreFragmentState(int index, Fragment fragment) {
    Fragment.SavedState savedState = savedStateSparseArray.get(index);
    fragment.setInitialSavedState(savedState);
}

註意這裡用了SparseArray來存儲Fragment的狀態, 並且加上了@State, 這樣在Activity重建的時候其中的內容也能夠被恢復.

Back stack中的fragment

有一點很特殊的是, 當Fragment從back stack中返回, 實際上是經歷了一次View的銷毀和重建, 但是它本身並沒有被重建.
即View狀態需要重建, 實例狀態不需要重建.

舉個例子說明這種情形: Fragment被另一個Fragment replace(), 並且壓入back stack中, 此時它的View是被銷毀的, 但是它本身並沒有被銷毀.
也即, 它走到了onDestroyView(), 卻沒有走onDestroy()onDetact().
等back回來的時候, 它的view會被重建, 重新從onCreateView()開始走生命周期.
在這整個過程中, 該Fragment中的成員變數是保持不變的, 只有View會被重新創建.
在這個過程中, instance state的saving並沒有發生.

所以, 很多時候Fragment還需要考慮的是在沒有Activity幫助的情形下(Activity並沒有可能重建的情形), 自身View狀態的保存.
此時要註意一些不容易發現的錯誤, 比如List的新實例需要重新setAdapter等.

Fragment setRetainInstance

Fragment有一個相關方法:
setRetainInstance
這個方法設置為true的時候表示, 即便activity重建了, 但是fragment的實例並不被重建.
註意此方法只對沒有放在back stack中的fragment生效.
什麼時候要用這個方法呢? 處理configuration change的時候:
Handling Configuration Changes with Fragments
這樣, 當屏幕旋轉, Activity重建, 但是其中的fragment和fragment正在執行的任務不必重建.
更多解釋可以參見:
http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean
http://stackoverflow.com/questions/11160412/why-use-fragmentsetretaininstanceboolean

註意這個方法只是針對configuration change, 並不影響用戶主動關閉和系統銷毀的情況:
當activity被用戶主動finish, 其中的所有fragments仍然會被銷毀.
當activity不在最頂端, memory不夠了, 系統仍然可能會銷毀activity和其中的fragments.

View的狀態保存和恢復

View的狀態保存和恢復主要是依賴於下麵幾個方法:
保存: saveHierarchyState() -> dispatchSaveInstanceState() -> onSaveInstanceState()
恢復: restoreHierarchyState() -> dispatchRestoreInstanceState() -> onRestoreInstanceState()
還有兩個重要的前提條件是View要有id, 並且setSavedEnabled()為true.(這個值預設為true).
在系統的widget里(比如TextView, EditText, Checkbox等), 這些都是已經被處理好的, 我們只需要給View賦予id, Activity和Fragment重建的時候會自動恢復其中的狀態. (這裡的Fragment恢復對應入口一和入口三, 入口二屬於跨實例新建的情況).

但是如果你要使用第三方的自定義View, 就需要確認一下它們內部是否有狀態保存和恢復的代碼.
如果不行你就需要繼承該自定義View, 然後實現這兩個方法:

//
// Assumes that SomeSmartButton is a 3rd Party view that
// View State Saving/Restoring are not implemented internally
//
public class SomeBetterSmartButton extends SomeSmartButton {

    ...

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        // Save current View's state here
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        // Restore View's state here
    }

    ...

}

WebView的狀態保存和恢復

WebView的狀態保存和恢復不像其他原生View一樣是自動完成的.
WebView不是繼承自View的.
如果我們把WebView放在佈局里, 不加處理, 那麼Activity或Fragment重建的過程中, WebView的狀態就會丟失, 變成初始狀態.

在Fragment的onSaveInstanceState()裡面可以加入如下代碼來保存WebView的狀態:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    webView.saveState(outState);
}

然後在初始化的時候, 增加判斷, 不必每次都打開初始鏈接:

if (savedInstanceState != null) {
    webView.restoreState(savedInstanceState);
} else {
    webView.loadUrl(TEST_URL);
}

這樣處理以後, 在重新建立的時候, WebView的狀態就能恢復到離開前的頁面.
不論WebView是放在Activity里還是Fragment里, 這個方法都適用.

但是Fragment還有另一種情況, 即Fragment被壓入back stack, 此時它沒有被destroy(), 所以沒有調用onSavedInstanceState()這個方法.
這種情況返回的時候, 會從onCreateView()開始, 並且savedInstanceState為null, 於是其中WebView之前的狀態在此時丟失了.
解決這種情況可以利用Fragment實例並未銷毀的條件, 增加一個成員變數bundle, 保存WebView的狀態, 最終解決如下:

private Bundle webViewState;

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ButterKnife.bind(this, view);

    initWebView();
    if (webViewState != null) {
        //Fragment實例並未被銷毀, 重新create view
        webView.restoreState(webViewState);
    } else if (savedInstanceState != null) {
        //Fragment實例被銷毀重建
        webView.restoreState(savedInstanceState);
    } else {
        //全新Fragment
        webView.loadUrl(TEST_URL);
    }
}

@Override
public void onPause() {
    super.onPause();
    webView.onPause();

    //Fragment不被銷毀(Fragment被加入back stack)的情況下, 依靠Fragment中的成員變數保存WebView狀態
    webViewState = new Bundle();
    webView.saveState(webViewState);
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //Fragment被銷毀的情況, 依靠outState保存WebView狀態
    if (webView != null) {
        webView.saveState(outState);
    }
}

本文完整例子相關實驗代碼可見:
HelloActivityAndFragment
中的State Restore Demo.

本文地址: Android Fragment使用(三) Activity, Fragment, WebView的狀態保存和恢復

參考資料

Developer Android:
Android Fragment Reference
Android FragmentManager Reference

Posts:
Recreating an Activity
Activity的重新創建
從源碼角度剖析Fragment核心知識點
Fragment源碼閱讀筆記
The Real Best Practices to Save/Restore Activity's and Fragment's state
Android中保存和恢復Fragment狀態的最好方法

Handling Configuration Changes with Fragments
Saving Android View state correctly

Tools:
icepick


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

-Advertisement-
Play Games
更多相關文章
  • ...
  • js斷點調試心得 雖然網上已經有多的數不清的調試教程了,但仍然沒有發現哪篇文章寫的通俗易懂,索性自己嘗試寫寫自己的一些使用習慣或者說是心得,希望對那些還不是很懂得使用斷點調試的孩子有一些幫助(大神請無視~)。 1.斷點調試是啥?難不難? 斷點調試其實並不是多麼複雜的一件事,簡單的理解無外呼就是打開瀏 ...
  • 在有全屏側滑的情況下,頁面上有個slider需要左右滑動的時候,經常在滑動slider的時候頁面也跟著滑動 解決辦法一:關閉當前頁面的全屏側滑,開啟系統側滑 self.fd_interactivePopDisabled = YES; //關閉全屏側滑 self.navigationControlle ...
  • 代碼: RootViewController.h RootViewController.m ...
  • IOS之網路數據下載和JSON解析 簡介 在本文中筆者將要給大家介紹IOS中如何利用URLConnection從網路上下載數據,如何解析下載下來的JSON數據格式,以及如何顯示數據和圖片的一部下載顯示 涉及到的知識點: 1.URLConnection非同步下載和封裝 2.JSON格式和JSON格式解析 ...
  • Android開發的過程中,我想要通過代碼來實現數據連接的開啟和關閉,最初我將目標鎖定為ConnectivityManager類,但是在翻閱了Android官方API後並沒有找到相關的方法,如圖1。 圖1 但是據說Android的一些類的某些方法的API是非公開的,所以我又做瞭如下嘗試:獲得Conn ...
  • 效果 ...
  • 關於網路安全的數據加密部分,本來打算總結一篇博客搞定,沒想到東西太多,這已是第三篇了,而且這篇寫了多次,熬了多次夜,真是again and again。起個名字: 數據加密三部曲 ,前兩部鏈接如下: 1. 整體介紹: "網路安全——數據的加密與簽名,RSA介紹" 2. 編碼與哈希實現: "網路安全— ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...