安卓四大組件之內容提供者

来源:http://www.cnblogs.com/huangjie123/archive/2016/11/19/6081344.html
-Advertisement-
Play Games

內容提供者ContentProvider,是Android 的四大組件之一。 ...


       內容提供者ContentProvider,是Android 的四大組件之一。內容提供者是應用程式之間共用數據的介面。應用程式創建的資料庫,預設情況下是私有的,別的應用程式訪問不到數據,如果想把數據對外提供,就要用到內容提供。ContentProvider屏蔽了數據存儲的細節,內部實現對用戶完全透明, 用戶只需要關心操作數據的uri就可以了,ContentProvider可以實現不同app之間共用。 Sql也有增刪改查的方法,但是sql只能查詢本應用下的資料庫。 而ContentProvider 還可以去增刪改查本地文件/xml文件的讀取等。Android 系統將這種機制應用到方方面面,比如:聯繫人(通訊錄應用程式)Provider 專為不同應用程式提供聯繫人數據;簡訊(簡訊應用程式)Provider 專為不同應用程式提供系統簡訊信息。當應用繼承ContentProvider 類,並重寫該類用於提供數據和存儲數據的方法,就可以向其他應用共用其數據。雖然使用其他方法也可以對外共用數據,但數據訪問方式會因數據存儲的方式而不同,如:採用文件方式對外共用數據,需要進行文件操作讀寫數據;採用SharedPreferences 共用數據,需要使用SharedPreferences API 讀寫數據。而使用ContentProvider 共用數據的好處是統一了數據訪問方式。總之,內容提供者管理了對結構化數據最常見的就是資料庫中數據的訪問,操作內容提供者是不同進程之間以資料庫數據形式交互數據的標準方式。 

     自定義的內容提供者包括內容提供者和訪問者兩個部分。

     內容提供者,擁有自己的資料庫,將資料庫暴露出來供訪問者修改。ContenProvider的編寫基本步驟:
        1. 寫一個類繼承 ContentProvider;
        2. 重寫一系列的方法,包括資料庫操作的空實現;

        3.   在內容提供者代碼內部定義UriMatcher -用於判斷uri是否匹配

static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 
static {
   mUriMatcher.addURI("在清單文件裡面定義的authorities", "自定義匹配字元串", 成功返回的標識); 
}

        4.   在增刪改查執行的時候判斷uri是否合法,在內容提供者內部實現對資料庫的增刪改查;

        5.   清單文件的下麵聲明provider,這裡需要指定主機名,也就是對外提供的Uri,當訪問者在內容解析者中傳入同一個uri時,才可以訪問到資料庫; 

<provider 
android:name="com.itheima.db.BankDBBackdoor"
android:authorities="自定義主機名" >
</provider>

      

        訪問者,存在於另外一個工程中,可以對內提供者的資料庫進行操作。

         1.  創建內容提供者解析器
              ContentResolver resolver = 上下文.getContentResolver();

         2. 定義要訪問的Uri路徑
              Uri uri = Uri.parse("content://自定義主機名/自定義匹配字元串")    // “content://”是標準寫法

         3. 利用內容提供者解析器進行增刪改查,實現對資料庫的操作

     

        內容提供者Uri 的書寫模板:   content:// 主機名authority/path/id。具體的書寫規範如下所示:

          1. "content://" 這是個固定寫法,用來說明一個ContentProvider 控制這些數據。
          2. 主機名或授權Authority:它定義了是哪個ContentProvider 提供這些數據。
          3. path:路徑,URI 下的某一個Item。
          4. ID:通常定義Uri 時使用”#”號占位符代替, 使用時替換成對應的數字。#表示數據id,#代表任意數字,*用來來匹配任意文本

      使用內容提供者操作系統簡訊和操作系統聯繫人是我們開發中經常遇到的需求,而自定義內容提供者對外提供數據反而使用的場景並不多,除非我們開發的簡訊或者聯繫人應用。一個小細節是,由於讀取和插入系統簡訊資料庫都涉及到可能侵犯用戶隱私,因此創建的工程必須添加相關的許可權。下麵就分別講解一下使用內容提供者操作系統資料庫和自定義內容提供者。

      用內容提供者操作系統簡訊只需要關註的到系統簡訊資料庫的一張表,最長用的數據有body,date,type,address,各自的含義也較為直觀。body表示簡訊的內容,date表示發送簡訊或收到簡訊的時間,type表示是受到簡訊還是發送簡訊,address表示收到的簡訊來自於哪個手機號。讀取和插入系統簡訊資料庫需要添加如下許可權:
             <uses-permission android:name="android.permission.READ_SMS"/>
             <uses-permission android:name="android.permission.WRITE_SMS"/>
      使用內容觀察者操作系統聯繫人時需要關註三張張數據表,rawcontact表 ,data表和 mimetype表。其中raw_contacts 表存放的是聯繫人id 信息,一個聯繫人就是表中的一行記錄。ontact_id在表單里應該是唯一的存在, 所以在插入的時候需要先查詢最後一條id是多少,然後在此基礎上加一。data 表中存放的是raw_contacts中的每一條id 對應的具體信息,一個聯繫人可能有電話、郵件、姓名等多條信息,每一條信息在該表中都是一行記錄。為了區分不同信息的類型,因此還有一個mimetypes 表,該表存儲的是常量數據,不同類型的信息由mimetype_id 來標識。現在很多App 都可以對系統聯繫人進行操作,這樣就可以直接將號碼添加到系統聯繫人中,可以關聯/備份/恢復系統聯繫人。讀取聯繫人信息的基本步驟是,首先查詢rawcontact表,獲取聯繫人的contactid,在rawcontact表中並不是每一個contact_id對應一條信息,而是一個contact_id對應多條信息,這樣可以存儲更多的信息。其次查詢根據contact_id查詢data表,獲取聯繫人的數據 data1、mimetype,前者存儲相關數據,後者存儲該數據對應得數據。最後根據mimetype類型確定數據類型。修改聯繫人的操作是往raw_contacts 表中插入一個id,值為n+1,作為一條新的記錄,然後往data1 表中插入具體的數據,其中id 必須為n+1。讀取和插入系統聯繫人資料庫需要添加如下許可權:
            <uses-permission android:readPermission="android.permission.READ_CONTACTS"
            <uses-permission android:writePermission="android.permission.WRITE_CONTACTS"

        那麼怎麼才能獲取簡訊和聯繫人的Uri呢。這時就需要看源碼了。打開Android 系統源碼,其中TelephonyProvider 就是簡訊的內容提供者文件。打開TelephonyProvider 下的src 文件,查看java 文件,其中的SmsProvider.java 即簡訊息內容提供者邏輯代碼。通過查找系統源碼,可以確定簡訊息內容提供者的Uri 應該為:”content://sms”。用同樣的方法,可以查到聯繫人內容提供者的Uri路徑。打開Android 源碼,查看packages\providers\路徑下的文件,其中ContactsProvider 就是聯繫人的內容提供者。打開ContactsProvider2.java文件,查看此內容提供者的uri 路徑。根據源碼,確定內容提供者的Uri 信息為:操作raw_contacts 表的Uri:content://com.android.contacts/raw_contacts。操作data 表的Uri:content://com.android.contacts/data。其實,平常寫代碼時不必要這麼複雜,直接把Uri路徑拿來就可以用了。另外要註意的是,由於聯繫人資料庫使用了視圖,所以操作資料庫表時,看到的表欄位名稱和真實操作的有所不同。比如:data 表在查詢的時候沒有mimetype_id 欄位,取代的是mimetype 欄位。

       再來講一講內容觀察者。內容提供者相當於一個監聽。觀察資料庫內容是否發生改變,如果改變,通知觀察者。內容觀察者ContentObserver,目的是觀察(捕捉)特定Uri 引起的資料庫的變化,繼而做一些相應的處理,它類似於資料庫技術中的觸發器(Trigger),當ContentObserver 所觀察的Uri 發生變化時,便會觸發它。觸發器分為表觸發器、行觸發器,相應的ContentObserver 也分為“表ContentObserver”、“行ContentObserver”,當然這是與它所監聽的Uri MIME Type 有關的。

       內容觀察者的使用步驟:
           1. 在內容提供者類增加通知方法
                             getContext().getContentResolver().notifyChange(uri, null);
          2. 在觀察者類註冊觀察
                           //定義觀察的uri ,和內容提供者的Uri一直
                          Uri uri = Uri.parse("content://...");
                          //註冊觀察者
                          getContentResolver().registerContentObserver(uri, true, ContentObserver);

 

          最後通過案例來演示今天的知識點。

          案例一: 內容提供者操作簡訊

         java代碼

/*
 * 添加許可權
 *   <uses-permission android:name="android.permission.READ_SMS"/>
 *   <uses-permission android:name="android.permission.WRITE_SMS"/>
 */
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    /**利用記憶體提供者添加簡訊*/
    public void add(View view) {
        Uri uri = Uri.parse("content://sms");
        ContentResolver resolver = getContentResolver();
        ContentValues values = new ContentValues();
        values.put("address", "00000");
        values.put("date", System.currentTimeMillis());
        values.put("type", 1); // 表示收簡訊還是發簡訊
        values.put("body", "看到簡訊表示利用內容提供者添加簡訊成功");
        resolver.insert(uri, values);
    }
    /**利用記憶體提供者刪除簡訊*/
    public void delete(View view) {
        Uri uri = Uri.parse("content://sms");
        ContentResolver resolver = getContentResolver();
        resolver.delete(uri, "address = ?", new String[] {"00000"});
    }
}

        佈局文件中只定義了兩個按鈕

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="add"
        android:text="添加簡訊" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="delete"
        android:text="刪除簡訊" />

</LinearLayout>

        效果展示:

          添加短息:

                        

            刪除簡訊:

                         

 

 

            案例二: 內容提供者操作聯繫人

            JAVA代碼,主程式: 

import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.example.contact.domain.ContactInfo;
import com.example.contact.utils.ContactUtils;
//添加許可權<uses-permission android:name="android.permission.READ_CONTACTS"/>
public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";
    private ListView listview;
    private List<ContactInfo> contactlist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listview = (ListView) findViewById(R.id.listview);
        contactlist = ContactUtils.getContact(this);
        Log.i(TAG, "info" + contactlist.size());
        for (ContactInfo info : contactlist) {
            Log.i(TAG, "info" + info.toString());
        }
        listview.setAdapter(new ContactAdapter());

    }

    public class ContactAdapter extends BaseAdapter {

        private ContactInfo contactInfo;

        @Override
        public int getCount() {
            return contactlist.size();
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            viewHolder holder;
            if (convertView == null) {
                holder = new viewHolder();
                convertView = View.inflate(MainActivity.this,
                        R.layout.item_contact, null);
                holder.tv_text = (TextView) convertView
                        .findViewById(R.id.tv_text);
                convertView.setTag(holder);
            } else {
                holder = (viewHolder) convertView.getTag();
            }
            contactInfo = contactlist.get(position);
            holder.tv_text.setText(contactInfo.toString());
            return convertView;
        }
    }

    public class viewHolder {
        TextView tv_text;
    }
}

 

         聯繫人工具類:

public class ContactInfo {
    private String name;
    private String phone;
    private String email;
    private String qq;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getQq() {
        return qq;
    }
    public void setQq(String qq) {
        this.qq = qq;
    }
    @Override
    public String toString() {
        return "ContactInfo [name=" + name + ", phone=" + phone + ", email="
                + email + ", qq=" + qq + "]";
    }
}

      獲取系統聯繫人:

import java.util.ArrayList;
import java.util.List;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;

import com.example.contact.domain.ContactInfo;

public class ContactUtils {
    public static List<ContactInfo> getContact(Context context) {        
        List<ContactInfo> list = new ArrayList<ContactInfo>();
        ContentResolver resolver = context.getContentResolver();
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        Uri datauri = Uri.parse("content://com.android.contacts/data");
        Cursor cursor = resolver.query(uri, new String[] { "contact_id" },
                null, null, null);
        while (cursor.moveToNext()) {
            
            String id = cursor.getString(0);
            System.out.println("00"+id);
            if (id != null) {
                ContactInfo info = new ContactInfo();
                Cursor datacursor = resolver.query(datauri, new String[] {
                        "data1", "mimetype" }, "raw_contact_id = ?",
                        new String[] { id }, null);
                while (datacursor.moveToNext()) {
                    String data1 = datacursor.getString(0);
                    String mimetype = datacursor.getString(1);
                    if ("vnd.android.cursor.item/name".equals(mimetype)) {
                        info.setName(data1);
                    } else if ("vnd.android.cursor.item/im".equals(mimetype)) {
                        info.setQq(data1);
                    } else if ("vnd.android.cursor.item/email_v2"
                            .equals(mimetype)) {
                        info.setEmail(data1);
                    } else if ("vnd.android.cursor.item/phone_v2"
                            .equals(mimetype)) {
                        info.setPhone(data1);
                    }
                }
                datacursor.close();
                list.add(info);
            }
        }
        cursor.close();
        return list;
    }
}

 

           佈局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

        成果展示:

  

        案例三: 內容提供者修改資料庫。通過上述知識我們知道當需要對另外一個應用程式的資料庫操作的時候可以用到內容提供者,那到底內容提供者內部是如何工作的呢?在這裡給出一個案例來說明內容提供者的工作機制。首先,創建一個工程,在這個工程中新建一個資料庫,單獨寫一個類繼承ContentProvider,我們稱之為後門程式,重寫其中一系列的方法。其中包括對數據增刪改查。但此處的資料庫操作方法是空實現,當我們在另一工程中為內容解析者指定同一Uri路徑時。調用內容解析者的增刪改查方法時,會自動對該數據哭庫操作。同時,還可以在後門程式中設置內容監聽者,這樣可以隨時觀察到數據的變化。

        內容提供者的主程式,不用操作任何邏輯:

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

        內容提供者中創建資料庫:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDBOpenhelper extends SQLiteOpenHelper {

    public MyDBOpenhelper(Context context) {
        super(context, "test.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
       db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),number varchar(20))");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

       寫一個類ContentProvider:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class BackDoor extends ContentProvider {

    private static final int SUCCESS = 1;

    /** 判斷Uri規則 */
    static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        mUriMatcher.addURI("com.exmple.text", "account", SUCCESS);    //uri規則可自己定義,但一定和清單文件一直
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    /** 增刪改查為空實現 */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        int code = mUriMatcher.match(uri); // 判斷Uri是否合法
        if (code == SUCCESS) {
            System.out.println("查詢數據");
            MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
            SQLiteDatabase db = helper.getReadableDatabase();
            return db.query("account", projection, selection, selectionArgs,
                    null, null, sortOrder);
        } else {
            throw new IllegalArgumentException("路徑不正確");
        }
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            System.out.println("添加數據");
            MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
            SQLiteDatabase db = helper.getWritableDatabase();
            db.insert("account", null, values);
            getContext().getContentResolver().notifyChange(uri, null); // 內容觀察者檢測資料庫是否更改
        } else {
            throw new IllegalArgumentException("路徑不正確");
        }
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            System.out.println("刪除數據");
            MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
            SQLiteDatabase db = helper.getWritableDatabase();
            db.delete("account", selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);     // 內容觀察者檢測資料庫是否更改
        } else {
            throw new IllegalArgumentException("路徑不正確");
        }
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            System.out.println("更新數據");
            MyDBOpenhelper helper = new MyDBOpenhelper(getContext());
            SQLiteDatabase db = helper.getWritableDatabase();
            db.update("account", values, selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);    // 內容觀察者檢測資料庫是否更改
        } else {
            throw new IllegalArgumentException("路徑不正確");
        }
        return 0;

    }
}

 

       配置文件中添加provider節點:

        <!-- 註冊內容提供者數據 -->
        <provider
            android:name="com.example.provider.BackDoor"
            android:authorities="com.exmple.text" >
        </provider>

       調用者的主程式:

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /**
     * 利用後門程式 添加一條數據
     */
    public void insert(View view) {
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://com.exmple.text/account");
        ContentValues values = new ContentValues();
        values.put("name", "zhangsan");
        values.put("number", 10000);
        resolver.insert(uri, values);
    }

    /**
     * 利用後門程式 刪除一條數據
     */
    public void delete(View view) {
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://com.exmple.text/account");
        resolver.delete(uri, "name=?", new String[] { "zhangsan" });
    }

    /**
     * 利用後門程式 修改數據
     */
    public void update(View view) {
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://com.exmple.text/account");
        ContentValues values = new ContentValues();
        values.put("number", 20000);
        resolver.update(uri, values, "name=?", new String[] { "zhangsan" });
    }

    /**
     * 利用後門程式 查詢數據
     */
    public void query(View view) {
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://com.exmple.text/account");
        Cursor cursor = resolver.query(uri, new String[] { "name", "number" },
                null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(0);
            float number = cursor.getFloat(1);
            System.out.println("name:" + name + "----" + "number:" + number);
        }
        cursor.close();
    }
}

 

         調用者的佈局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="insert"
        android:text="增" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="delete"
        android:text="刪" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="update"
        android:text="改" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="query"
        android:text="查" />

</LinearLayout>

        運行結果:


        

 


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

-Advertisement-
Play Games
更多相關文章
  • 左右定寬,中間自適應,實現三列佈局 今天想說的是一個左右定寬,中間自適應,實現三列佈局,我也總結了以下,主要有以下幾種: 廢話不多說,直接上代碼: 第一種:float 第二種:BFC 第三種:雙飛翼佈局 第四種:flex ...
  • 好久沒有更新博客了,自從大學畢業整個人好像剛從監獄放出來似的,被外面的花花世界迷暈了,整天都是上班,再也沒有熬夜敲代碼,好久沒有對自己的學習進行總結了,趁著周末的時光總結一下這5個月來自己在前端路上裸奔的這段日子。原本大學畢業打算找個JAVA方向的或Android開發的工作,真是歲月弄人,最後來到了 ...
  • html部分 <html> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" type="text/css" href="css/reset.css"/> <link rel="stylesheet" href= ...
  • 今天解決了iframe高度自適應的問題,不過這隻是同域下的頁面嵌入,以下是代碼: function SetCwinHeight(){ var iframeid = document.getElementById("frame"); //frame是iframe的id if (document.get ...
  •   本文主要講如何自定義NSOperation,以及自定義NSOperation的一些註意事項,以下載圖片為例。 新建一個類,繼承於NSOperation。 CustomOperation.h 代碼 CustomOperation.m 分析 :1.自定義NSOperation都要 ...
  • 介紹Apk去掉簽名的方法以及用JDK自帶jarsigner重新簽名的方法。 ...
  • 1、配置URLSchemes 被跳轉至的應用要配置URLSchemes,其他應用根據此應用的URLSchemes跳轉至此應用。 一個APP可以配置多個URLSchemes,這樣其他APP無論寫哪個URLSchemes,都能跳轉至此應用。 方式1:配置Info信息,其實本質修改的還是Info.plis ...
  • 生命周期對程式員很重要,特別當我們瞭解,就可以寫出更流暢的程式,更好的來避規性能瓶頸,讓我們的APP擁有更好的用戶體驗。我們繼續來分享“活動狀態及生存周期”,活動狀態:運行狀態,暫停狀態,停止狀態,銷毀狀態。活動的生成周期分別為:onCreate()、onSart()、onResme()、onPau... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...