一起學Android之ContentProvider

来源:https://www.cnblogs.com/hsiang/archive/2019/06/18/11048492.html
-Advertisement-
Play Games

本文主要講解在Android開發中ContentProvider的常規用法,僅供學習分享使用,如有不足之處,還請指正。 ...


本文主要講解在Android開發中ContentProvider的常規用法,僅供學習分享使用,如有不足之處,還請指正。

訪問一個ContentProvider

Android開發中,應用程式通過ContentResolver(內容解析器)ContentProvider(內容提供者)中獲取數據,ContentResolver提供訪問ContentProvider中同名方法,ContentProvider包括ContentProvider和它的子類,ContentResolverContentProvider的持久層存儲提供了基本的CRUDCreate,Retrieve,Update,Delete)方法進行訪問。客戶端AppContentResolver對象自動處理和ContentProviderApp之間的進程間通信。ContentProvider還充當資料庫和外部數據視圖表現之間的抽象層。

備註:如果要訪問一個ContentProviderApp需要在清單文件中請求對應的許可權。

例如:從User Dictionary Provider中獲取單詞和區域的列表,可以調用ContentResolver.query()方法,如下圖所示:

1 // 查詢用戶定義字典並返回結果
2 mCursor = getContentResolver().query(
3     UserDictionary.Words.CONTENT_URI,   // 單詞表的內容URI
4     mProjection,                        // 查詢的數據列名數組
5     mSelectionClause                    //查詢條件,可以為null
6     mSelectionArgs,                     // 查詢參數,可以為null
7     mSortOrder);                        // 返回數據對象的排序條件

下表顯示了query(Uri,projection,selection,selectionArgs,sortOrder) 如何與SQL語句進行匹配:

Content URIs

Content URIProvider中標識數據的URI,包括整個Provider(其許可權)的符號名和指向表(或路徑)的名稱,Content URI是訪問ContentProvider的參數之一。

在前面的代碼行中,常量_uri包含了用戶詞典“Word”表的Content URIContentResolver對象通過將許可權與已知提供者的系統表進行比較,將查詢參數發送到正確的Provider 

ContentProvider使用URI的路徑部分來選擇要訪問的表,通常為公開的每個表設置路徑。

在前面的代碼行中,Word”表的全稱為:

1 content://user_dictionary/words

其中user_dictionary 字元串是Provider的許可權,words是表的路徑。content:// (the scheme)始終存在,並將其標識為Content URI

許多Provider允許將id值附加到URI的末尾來訪問表中的單個行。例如,要從User Dictionary中檢索_id4的行,可以使用Content URI

1 Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

當要修改或刪除其中一行時,經常使用ID值。

備註:Uri Uri.Builder類包含了用字元串構造形式良好的uri對象的方法。ContentUris包含了將ID附加到uri的方法。前面片段使用withAppendedId() ID附加到UserDictionary.Words.CONTENT_URI

Provider獲取數據

本節介紹如何使用User Dictionary Provider作為示例,從中檢索數據。

為了清晰起見,本節中的代碼段調用UI線程”上的ContentResolver.query()。在實際代碼中,應該在非UI線程上非同步地進行查詢。

要從Provider獲取數據,請遵循以下基本步驟:

  1. 請求讀取Provider的訪問許可權。
  2. 定義查詢Provider的代碼。

訪問許可權

要從Provider中檢索數據,應用程式需要Provider的“讀取訪問許可權”。不能在運行時請求此許可權;必須在您的清單中指定需要此許可權,使用<uses-permission>元素和由Provider定義的許可權名稱。當在清單中指定此元素時,實際上是在為App“請求”此許可權。當用戶安裝App時,會隱式地批准這個請求。

User Dictionary Provider在清單文件中定義的許可權名稱為android.permission.READ_USER_DICTIONARY,所以App中想要從Provider中獲取數據,需要請求這個許可權。

構造查詢

查詢數據的下一步是構造查詢。以下片段定義了訪問User Dictionary Provider的一些變數:

 1 //  "projection" 定義每行返回的列名數組
 2 String[] mProjection =
 3 {
 4     UserDictionary.Words._ID,    //  _ID column name
 5     UserDictionary.Words.WORD,   //  word column name
 6     UserDictionary.Words.LOCALE  // locale column name
 7 };
 8 
 9 // 定義查詢條件
10 String mSelectionClause = null;
11 
12 // 定義查詢條件參數
13 String[] mSelectionArgs = {""};

下一個片段顯示如何使用ContentResolver.query(),以User Dictionary Provider 為例,查詢類似於sql查詢,它包含要返回的列名、查詢條件和排序。

查詢返回的列集合稱為投影(變數投影)。

查詢條件表達式被拆分為選擇子句和選擇參數。選擇子句是邏輯表達式和布爾表達式、列名稱和值的組合。如果指定可替換參數?”,查詢條件不再是一個值,而是從條件參數數組(mSelectionArgs)中查詢該值。

如果用戶沒有輸入一個單詞,則選擇子句設置為空,查詢返回Provider中的所有單詞。

如果用戶輸入了一個單詞,查詢條件將設置UserDictionary.Words.WORD + " = ?"。參數數組的第一個元素設置為用戶輸入的單詞。

 1 /*
 2  * 定義查詢參數
 3  */
 4 String[] mSelectionArgs = {""};
 5 
 6 // 獲取界面輸入的查詢條件
 7 mSearchString = mSearchWord.getText().toString();
 8 
 9 //此處插入代碼校驗數據是否有效
10 //如果條件為空,則查詢所有
11 if (TextUtils.isEmpty(mSearchString)) {
12     // 如果查詢條件為空,則返回所有內容
13     mSelectionClause = null;
14     mSelectionArgs[0] = "";
15 } else {
16     // 構造查詢條件,匹配用戶輸入的數據.
17     mSelectionClause = UserDictionary.Words.WORD + " = ?";
18     // 查詢參數.
19     mSelectionArgs[0] = mSearchString;
20 }
21 
22 // 對錶進行查詢並返回Cursor對象
23 mCursor = getContentResolver().query(
24     UserDictionary.Words.CONTENT_URI,  // URI
25     mProjection,                       // 查詢數據列
26     mSelectionClause                   // 查詢條件
27     mSelectionArgs,                    // 查詢參數
28     mSortOrder);                       // 返回結果排序行
29 
30 // 如果出現查詢異常,則返回空
31 if (null == mCursor) {
32     /*
33      * 插入代碼捕獲異常
34      */
35     // 如果返回為空,則沒有匹配的內容
36 } else if (mCursor.getCount() < 1) {
37     /*
38      * 通知用戶查詢不成功. 但這不是必須的*/
39 } else {
40     // 插入代碼處理結果
41 }

類似於sql語句:

1 SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

在這個sql語句中,使用的是實際的列名稱,而不是Contract類常量。

防止惡意輸入

如果content provider管理的數據在sql資料庫中,外部不受信任的數據輸入到原始sql語句中,就會導致sql註入。

考慮這個查詢條件:

1 //通過拼接用戶輸入和列名的方式構造查詢條件
2 String mSelectionClause =  "var = " + mUserInput;

如果這樣做,用戶可能將惡意sql連接到您的sql語句中。例如,用戶可以輸入"nothing; DROP TABLE *;"用於mUserInput,這將導致選擇子句var = nothing; DROP TABLE *;。由於選擇條件被視為sql語句,這可能會導致Provider刪除sqlite資料庫中的所有表。

為了避免此問題,請使用可替換的參數和單獨的選擇參數數組的查詢條件。採用這種方式,用戶輸入將直接綁定到查詢,而不是被解釋為sql語句的一部分,用戶無法註入惡意sql。如下所示:

1 // 用一個可替換參數來包含用戶輸入
2 String mSelectionClause =  "var = ?";

如下設置查詢參數數組:

1 // 定義一個查詢條件的數組
2 String[] selectionArgs = {""};

在查詢參數數組中進行賦值:

1 // 將用戶數據作為參數數據
2 selectionArgs[0] = mUserInput;

顯示查詢結果

ContentResolver.query()客戶端方法總是返回一個CursorCursor對象提供對其包含的行和列的讀取訪問權。使用Cursor中的方法可以迭代行數據,確定每列的數據類型,將數據從列中取出,並檢查結果的其他屬性。有些Cursor實現會在提供者的數據變更時自動更新,或在Cursor變更時觸發對應的事件,或兩者兼而有之。

如果沒有行符合查詢條件,provider將返回一個Cursor, 其Cursor.getCount()0(空游標)。

如果發生內部錯誤,查詢的結果取決於特定的Provider。它可以返回null,也可以拋出異常。

由於Cursor是行的“列表”,顯示Cursor內容的一個好方法是通過SimpleCursorAdapter綁定到ListView

如下代碼所示:它創建一個SimpleCursorAdapter對象,包含查詢到的Cursor,並將此對象設置到ListView的適配器

 1 // 定義從Cursor中檢索並載入到輸出行的列名
 2 String[] mWordListColumns =
 3 {
 4     UserDictionary.Words.WORD,   // word column name
 5     UserDictionary.Words.LOCALE  // locale column name
 6 };
 7 
 8 //定義一個視圖ID列表,該列表將接收每行的Cursor列
 9 int[] mWordListItems = { R.id.dictWord, R.id.locale};
10 
11 // 創建一個SimpleCursorAdapter對象
12 mCursorAdapter = new SimpleCursorAdapter(
13     getApplicationContext(),               // 應用程式上下文對象
14     R.layout.wordlistrow,                  // ListView單行配置文件 
15     mCursor,                               // query函數返回的結果
16     mWordListColumns,                      // Cursor中的列名數組
17     mWordListItems,                        // ListView中Item項的佈局文件
18     0);                                    // Flags (usually none are needed)
19 
20 // 設置 adapter到ListView
21 mWordList.setAdapter(mCursorAdapter);

備註:要使用Cursor支持ListViewCursor必須包含一個名為_id的列。這個限制也解釋了為什麼大多數Provider的每個表都有一個_id列。

從查詢結果中獲取數據

您可以將查詢結果用於其他任務,而不是簡單地顯示查詢結果。要做到這一點,需要迭代Cursor中的行:

 1 // 定義"word"列的索引
 2 int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
 3 
 4 /*
 5  * 當cursor有效的時候才執行.  User Dictionary Provider如果發生內部錯誤,將返回null,其他provider可能會拋出異常
 6  */
 7 
 8 if (mCursor != null) {
 9     /*
10      * 移動到cursor的下一行.在第一行移動之前, 行指向是-1,如果試圖去查詢此位置上的內容,將會拋出一個異常
11      */
12     while (mCursor.moveToNext()) {
13         //獲取對應的列的值.
14         newWord = mCursor.getString(index);
15         // 插入代碼處理獲取的值.
16         ...
17         // while 迴圈結束
18     }
19 } else {
20     // 展示錯誤和異常信息
21 }

Cursor實現包含檢索不同類型數據的幾種“get”方法。例如,上一個片段使用getString()。同時也有一個gettype()方法,該方法返回列的數據類型。

Content Provider許可權

訪問Provider中的數據,調用方必須具有相應的許可權,這些許可權確保用戶知道應用程式試圖訪問哪些數據,用戶在安裝App時會看到請求的許可權。

如前所述,User Dictionary Provider要求使用android.permission.READ_USER_DICTIONARY許可權獲取數據。Provider需要android.permission.WRITE_USER_DICTIONARY許可權來插入、更新或刪除數據。

為了獲得訪問provider所需的許可權,App在其清單文件中以<uses-permission>元素請求它們。當安裝App時,用戶必須允許應用程式請求的所有許可權。如果用戶全部允許,將繼續安裝;如果用戶不允許,Package Manager將中止安裝。

以下<uses-permission>元素請求讀取 User Dictionary Provider的訪問許可權:

 

1 <uses-permission android:name="android.permission.READ_USER_DICTIONARY">

Inserting, Updating, and Deleting Data

與從provider獲取數據的方式相同,還可以使用provider客戶端與provider's 提供方之間的交互來修改數據。provider provider客戶端自動處理安全以及進程間通信。

插入數據(Inserting data

將數據插入到provider中,請調用ContentResolver.insert()。此方法將新行插入到provider中,並返回新增行的 content URI。此片段顯示如何將新詞插入到User Dictionary Provider中:

 1 // 定義一個新的 Uri對象,接收插入新行放回的內容
 2 Uri mNewUri;
 3 
 4 // 要插入的新值
 5 ContentValues mNewValues = new ContentValues();
 6 
 7 /*
 8  * 設置每列對應的值
 9  */
10 mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
11 mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
12 mNewValues.put(UserDictionary.Words.WORD, "insert");
13 mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
14 
15 mNewUri = getContentResolver().insert(
16     UserDictionary.Word.CONTENT_URI,   // 內容 URI
17     mNewValues                          // 插入的值
18 );

新行的數據對應單個ContentValues對象,該對象在形式上類似於單行cursor。此對象中的列不需要具有相同的數據類型,如果不想指定值,則可以使用ContentValues.putNull()設置列為空。

代碼段不會添加_id列,因為此列是自動維護的。provider為添加的每一行指定一個唯一_id,通常使用_id作為表的主鍵。

返回的新行的newUri,格式如下:

1 content://user_dictionary/words/<id_value>

<id_value>是新行的_id。大多數provider可以自動檢測到這種形式的內容,然後在該特定行上執行請求的操作。

若要從返回的Uri中得到_id值,請調用ContentUris.parseId()

更新數據(Updating data

要更新行,將使用帶有更新值的ContentValues對象,就像使用插入時一樣,選擇條件也與使用查詢時一樣。調用方法是ContentResolver.update()。您只需要為需要更新的列向ContentValues對象添加值。如果要清除列的內容,請將值設置為null

下麵的片段將locale設置有語言"en"的所有行更改為locale為空。返回值是更新的行數:

 1 // 包含更新的內容的對象
 2 ContentValues mUpdateValues = new ContentValues();
 3 
 4 // 定義需要更新的查詢條件
 5 String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
 6 String[] mSelectionArgs = {"en_%"};
 7 
 8 // 定義更新行得到的行數
 9 int mRowsUpdated = 0;
10 
11 /*
12  * 設置更新的內容.
13  */
14 mUpdateValues.putNull(UserDictionary.Words.LOCALE);
15 
16 mRowsUpdated = getContentResolver().update(
17     UserDictionary.Words.CONTENT_URI,   // URI
18     mUpdateValues                       // 更新的內容
19     mSelectionClause                    //查詢條件
20     mSelectionArgs                      // 查詢內容參數
21 );

在調用ContentResolver.update()時,對用戶輸入進行處理。

刪除數據(Deleting data

刪除行類似於查詢行數據:為要刪除的行指定選擇條件,而客戶端方法返回已刪除行的數目如下所示:

 1 // 定義需要刪除的條件
 2 String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
 3 String[] mSelectionArgs = {"user"};
 4 
 5 //定義刪除掉行數
 6 int mRowsDeleted = 0;
 7 
 8 // 刪除匹配條件的內容
 9 mRowsDeleted = getContentResolver().delete(
10     UserDictionary.Words.CONTENT_URI,   //  URI
11     mSelectionClause                    // 刪除條件
12     mSelectionArgs                      // 刪除參數
13 );

 

在調用 ContentResolver.delete()方法時,對用戶輸入進行處理。

Provider數據類型

Content providers可以提供許多不同的數據類型。User Dictionary Provider只提供文本,但也可以提供以下格式:

  •  integer
  •  long integer (long)
  •  floating point
  •  long floating point (double)

providers經常使用的另一種數據類型是Binary Large OBject (BLOB),它是64kb位元組數組。通過查看Cursor類“get”方法,可以看到可用的數據類型。

provider中每一列的數據類型通常在其文檔中列出。User Dictionary Provider 的數據類型在其contractUserDictionary.Words的參考文檔中列出。也可以通過Cursor.getType()來確定數據類型。

Provider訪問的替代形式

在應用程式開發中,三種可供選擇的Provider訪問形式非常重要:

  1. 批量訪問:可以在ContentProviderOperation中使用方法創建批量處理訪問調用,然後用ContentResolver.applyBatch()應用它們。
  2. 非同步查詢:應該在單獨的線程中進行查詢,其中一種方法是使用CursorLoader對象。
  3. 通過intents訪問數據:雖然不能直接向提供者發送intent,但可以向provider's application發送intent,而provider's application序通常是最適合修改provider數據的應用程式。

批量訪問(Batch access

provider的批量訪問用於插入多行,或在同一方法中在多個表中插入行,或通常用於作為事務(原子操作)執行一組跨進程的操作。

要以batch mode”訪問provider,您可以創建一組 ContentProviderOperation 對象,然後通過ContentResolver.applyBatch()方法將對象分發到provider。將provider的許可權傳遞給此方法,而不是特定的內容。這允許數組中的每個ContentProviderOperation對象對不同的表操作。ContentResolver.applyBatch() 返回結果數組。

通過Intent進行數據訪問(Data access via intents

Intents可以提供對 content provider的間接訪問。允許用戶訪問provider中的數據,即使您的App沒有訪問許可權,也可以從有許可權的App獲得結果Intent,或者通過激活有許可權的App併在其中工作。

合同類別(Contract Classes

contract類定義了幫助App處理content URIs、列名稱、意圖操作和 content provider的其他特性的常量。Contract類不自動包含在provider中;provider的開發人員必須定義它們,然後將其提供給其他開發人員。android平臺中的許多提供商在android.provider中都有相應的contract類。

例如,User Dictionary Provider有一個包含內容URI和列名常量的contract類用戶詞典。“單詞”表的內容以“常量”為定義。UserDictionary.Words.CONTENT_URI,在以下示例片段中使用。例如,查詢投影可以定義為:

1 String[] mProjection =
2 {
3     UserDictionary.Words._ID,
4     UserDictionary.Words.WORD,
5     UserDictionary.Words.LOCALE
6 };

 ContentProvider示例

讀取通話記錄

 1 //通訊記錄URI
 2     private String call_uri = "content://call_log/calls";
 3 
 4     //內容解析器
 5     private ContentResolver mResolver;
 6 
 7     //列表
 8     private ListView lvCall;
 9 
10     //獲取的通訊記錄的列名
11     private String[] columns = new String[]{
12             CallLog.Calls._ID, CallLog.Calls.CACHED_NAME, CallLog.Calls.NUMBER, CallLog.Calls.TYPE, CallLog.Calls.DATE,CallLog.Calls.DURATION
13     };
14 
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19         //初始化內容解析器
20         mResolver = getContentResolver();
21         lvCall = (ListView) this.findViewById(R.id.lv_call);
22     }
23 
24     /**
25      * 獲取通訊記錄事件
26      * @param v
27      */
28     public void bn_call(View v) {
29         List<Map<String, String>> list = new ArrayList<>();
30         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
31         Cursor cursor = mResolver.query(Uri.parse(call_uri), columns, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
32         //以下是為了轉換數據格式
33         if(cursor!=null){
34             while (cursor.moveToNext()){
35                 long dt=cursor.getLong(cursor.getColumnIndex("date"));
36                 Date callDate = new Date(dt);
37                 String callDateStr = sdf.format(callDate);
38                 String name=cursor.getString(cursor.getColumnIndex("name"));
39                 String number=cursor.getString(cursor.getColumnIndex("number"));
40                 String duration =cursor.getString(cursor.getColumnIndex("duration"))+"s";
41                 Map<String, String> map=new HashMap<String, String>() ;
42                 map.put("name",name);
43                 map.put("number",number);
44                 map.put("date",callDateStr);
45                 map.put("duration",duration);
46                 list.add(map);
47             }
48         }
49         //將數據填充到Adapter
50         SimpleAdapter adapter=new SimpleAdapter(this,list,R.layout.list_item,
51                 new String[]{"name", "number", "date","duration"},
52                 new int[]{R.id.tv_name, R.id.tv_number, R.id.tv_time,R.id.tv_duration});
53 
54         /*SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.list_item, cursor,
55                 new String[]{"name", "number", "date"},
56                 new int[]{R.id.tv_name, R.id.tv_number, R.id.tv_time},
57                 CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);*/
58         //綁定Adapter到ListView
59         lvCall.setAdapter(adapter);
60     }

讀取簡訊記錄

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

-Advertisement-
Play Games
更多相關文章
  • 更改oracle RAC public ip,vip,scan ip和private ip oifcfg - Oracle 介面配置工具 用法: oifcfg iflist [-p [-n]] oifcfg setif {-node | -global} {/:}... oifcfg getif [ ...
  • 資料庫設計 1. 說在前面 項目開發的流程包括哪些環節 (1) 根據市場公司需求分析公司是否需要開發軟體來輔助日常工作 (2) 公司高層市場考察,市場分析,決定做什麼軟體。 (3) 不懂技術的人想象軟體應該有什麼功能,長什麼樣子 (1) 根據領導的需求設計出產品的原型(圖紙) ① 有具體的功能,功能 ...
  • 版權聲明:本文為xing_star原創文章,轉載請註明出處! 本文同步自http://javaexception.com/archives/130 微信好友檢測助手App 最近幾周,寫了個微信好友檢測助手App,寫這個的初衷是為了低成本的讓用戶檢測自己微信上刪除或拉黑自己的好友,不考慮用xposed ...
  • 例如需求,我有一個WebView 載入一個url, 該url對應的網頁本身自帶下拉刷新 ,但是網頁本身會有出現400 500 等異常請求錯誤碼 這時候網頁載入失敗,頁面本身的下拉是無法使用的,要求重新載入頁面的話就需要在webview外層套一個android下拉控制項(SwipeRefreshLayo ...
  • Hi3120是海思的一款高性能視頻掃描格式轉換晶元。該晶元集成了降噪、去隔行、平滑縮放、視頻增強、微控制器、圖形界面菜單等多項功能,能從PAL、NTSC、高清等彩色電視信號中去除雜訊以提取清晰的圖像信號,並兼480P/576P/720P/10801/1080PNGA/SVGA/XGA/UXGA等多種 ...
  • 通過VOLUME_UP,VOLUME_DOWN調節atv的聲音,不變化,只有調到靜音的時候,才沒有聲音,界面上的聲音大小是顯示對的.[Solution]這類問題常見的原因是音頻參數沒有進行設置,使用原始release預設的參數,而預設的參數在各版本上0-6均為255,導致調節音量等級,聲音大小無變化 ...
  • Objective-C語言的動態性主要體現在以下3個方面 (1)動態類型:運行時確定對象的類型。 (2)動態綁定:運行時確定對象的方法。 (3)動態載入:運行時載入需要的資源或者或代碼模塊。 一、動態類型 動態類型指對象指針類型的動態性,具體地說就是使用id類型將對象的類型推遲到運行時才確定,由賦給 ...
  • 本篇博文記錄MBProgressHUD源碼學習過程,從官方提供的 "Demo" 項目入手,一步步瞭解其代碼結構,學習它使用的技術,體會作者的編程思想。 一、結構 我們先來看下MBProgressHUD的結構,查看其類的定義。 1.MBProgressHUD是UIView的子類。 2.屬性: 3.其他 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...