Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android四大組件之Content Provider

Android四大組件之Content Provider

編輯:關於Android編程

 Android四大組件之Content Provider


一、概念
         Content Provider 作為Android應用程序四大組件之一,為存儲和查詢數據提供統一的接口,實現程序間數據的共享。Android系統內一些常見的數據如音樂、視頻、圖像等都內置了一系列的Content Provider。
     

應用程序間共享數據有兩種方式:

       一是創建子類繼承於Content Provider,重寫該類用於數據存儲和查詢的方法。

       二是直接使用已經存在的Content Provider,如聯系人等。
 

       在Content Provider中數據的是以表的形式存儲,在數據表中每一行為一條記錄,每一列為類型和數據。每一條記錄都包括一個唯一的名叫_ID的數值字段,這個字段唯一標識一條數據記錄,在創建表的時候用 INTEGER PRIMARY KEY AUTOINCREMENT來標識此字段。
       


二、相關類介紹


類URI:
         每一個Content Provider為其管理的多個數據集,分配一個URI,這個URI對外提供了一個能夠唯一標識自己數據集的字符串。這樣別的應用程序就可以通過這個URI來訪問這個數據集。Android中所有的Content Provider的URI都有固定格式:content://****開頭,一般可分為4個部分:

      標准前綴:用來標識一個Content Provider,固定Content://

      URI標識:定義了是哪個Content Provider提供這些數據。一般是定義該ContentProvider的包類的名字。和AndroidManifest.xml中定義的authorities屬性相同。

      路徑:標識URI下的某一個Item。

      記錄的ID:如果URI中包含表示某個記錄的ID,則返貨該id對應的數據。否則表示返回全部。

注意:"content://com.android.people.provider/contacts/#" 這裡#表示匹配任意數字"content://com.android.people.provider/contacts/*"表示匹配任意文本

 

UriMatcher:
         Uri標識了要操作的數據,而UriMatcher即使Android提供給我們用於操作Uri這個數據的工具類。
常用方法:
         public void addURI(String authority, String path, int code) 往UriMatcher對象裡面添加Uri。
         public int match(Uri uri) 與UriMatcher對象的Uri進行匹配,如果成功返回上面傳入的code值,否則返回-1.


類ContentUris:
          類似於UriMatcher,也是一個操作Uri數據的工具類,用於在Uri後面追加一個ID或解析出傳入的Uri對象的ID值。
常用方法:
         public static Uri withAppendId(Uri contentUri, long id)  為前面contentUri加上ID部分
         public static long parseId(Uri contentUri) 從contentUri中獲取ID部分

 

類ContentProvider: 
常用方法:

[java]

public abstract boolean onCreate(); 
public abstract Uri insert(Uri uri, ContentValues values) 
public abstract int delete(Uri uri, String selection, String[] selectionArsg); 
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs); 
public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder) 
public abstract String getType(Uri uri) 

public abstract boolean onCreate();
public abstract Uri insert(Uri uri, ContentValues values)
public abstract int delete(Uri uri, String selection, String[] selectionArsg);
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder)
public abstract String getType(Uri uri)
這些都是抽象方法需要子類去實現。類 ContentValues:

        android.content.ContentValues 這個用於存儲ContentResolver能處理的數據的集合。

構造函數:

        ContentValues()         // 創建一個空的ContentValues對象,初始化默認大小

        ContentValues(int size)

        ContentValues(ContentValues from)

常用方法:       

[java]

void clear() 
boolean containKey(String key) 
Object get(String key) 
void put(String key, Type value)     // Type: Byte Integer Float Short byte[] String Double Long Boolean  
int size() 
Type getAsTypeArray(String key)      // Type: Object Boolean Byte byte[] Double Float Integer Long Short String<SPAN style="FONT-SIZE: 14px">  
</SPAN> 

void clear()
boolean containKey(String key)
Object get(String key)
void put(String key, Type value)     // Type: Byte Integer Float Short byte[] String Double Long Boolean
int size()
Type getAsTypeArray(String key)      // Type: Object Boolean Byte byte[] Double Float Integer Long Short String
類android.content.ContentResolver:
       

         一個程序可以通過實現一個ContentProvider的抽象接口將自己的數據以類似數據庫中表的方式完全暴露出去,那麼外界其他應用程序怎麼從數據庫中獲取數據呢,這就需要ContentResolver了,通過URI表示外界訪問需要的數據庫。           這個類為我們定義了一系列的方法包括:插入、刪除、修改、查詢等,與ContentProvider基本類似,主要根據傳入的參數Uri找到對應的Content Provider,調用其相應的方法。

構造函數:

         public ContentResolver(Context context)

一般在代碼中我們直接通過: MainActivity.this.getContentResolver()獲得當前應用程序的一個ContentResolver實例。     

         這裡我們需要考慮一個問題,就是如果多個程序同時通過ContentResolver共享訪問一個ContentProvider,會不會不同步,尤其是數據寫入的時候這就需要在AndroidManifest.xml中定義ContentProvider的時候加上:<provider>元素的multiprocess屬性。同時Android在ContentResolver中為我們提供了

notifyChange()接口,在數據發生改變時通知其他的ContentObserver。

[java]

final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer) 
final void unregisterContentObserver(ContentObserver observer) 
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) 
void notifyChange(Uri uri, ContentObserver observer) 

final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
final void unregisterContentObserver(ContentObserver observer)
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)
void notifyChange(Uri uri, ContentObserver observer)
三、使用已經存在的ContentProvider
        使用已經存在的ContentProvider包括兩種情況:一是從已經存在的ContentProvider裡面讀取數據,如獲得
手機裡面聯系人的信息。二是將自己的數據加入到ContentProvider裡面,讓其他程序能共享次數據,這就需要獲得對這個
ContentProvider的寫權限了。

       Android中電話簿就是通過ContentProvider實現數據共享的,系統中有很多已經存在的共享URI,我們可以使用ContentResolver通過Uri來操作不同的標的數據。如Contacts.People.CONTENT_URI


        在Android中為我們提供了兩種方法來查詢Content Provider:一是使用ContentResolver的query()方法,二是使用Activity對象的manageQuery()方法,他們的參數都相同,而且都返回Cursor對象。但是使用manageQuery()方法返回的Cursor對象的生命周期自動被Activity來管理,被管理的Cursor對象在Activity進入暫停狀態的時候調用自己的deactivate()方法卸載,在Activity回到運行狀態的時候調用自己的requery()方法重新查詢生成的Cursor。而使用ContentResolver的query()方法返回的Cursor對象需要手動加入Activity來管理,這是通過Activity的startManagingCursor()方法來實現的。
 

 

四、創建自己的ContentProvider

(1) 創建一個繼承於ContentProvider的類MyContentProvider
(2) 定義一個public static final Uri 類型變量CONTENT_URI
 如:public static final Uri CONTENT_URI = Uri.parse("content://com.android.MyContentProvider")
(3) 定義需要返回給客戶端的數據列名,如果使用到數據庫SQLite,必須定義一個_id,表示記錄的唯一性。
(4) 創建數據存儲系統,如文件系統或數據庫SQLite系統。
(5) 如果存儲字節型數據,如位圖文件等,數據列其實是一個表示實際保存文件的URI字符串,用來讀取對應的實際文件數據。 處理這種數據類型的Content Provider需要實現一個名為_data的字段,該字段列出了該文件在Android系統的實際路 徑,客戶端可以通過調用方法ContentResolver.

openOutputStream()來處理該URI指向的文件資源。
(6) 查詢返回一個Cursor類型對象,所有執行寫操作的方法如insert()、update()及delete()都將被監聽,可以通過Content
Resolver().notifyChange()來通過監聽器關於數據更新的消息。
(7) 在AndroidManifest.xml中使用<provider>標簽來設置Content Provider信息,如:android:authorities、android:name
android:permission等。

Demo:
數據庫類DatabaseHelper用來存儲個人信息,名字(name)對應年齡(age)

[java]

public class DatabaseHelper extends SQLiteOpenHelper { 
    private static final String DB_NAME = "personInfo.db"; 
    private static final String TB_NAME = "person";  
    private static final int VERSION = 1; 
    private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)"; 
    private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)"; 
     
    public DatabaseHelper(Context context) { 
        super(context, DB_NAME, null, VERSION); 
        // TODO Auto-generated constructor stub  
    } 
 
    @Override 
    public void onCreate(SQLiteDatabase db) { 
        // TODO Auto-generated method stub  
        db.execSQL(creat_cmd); 
    } 
    @Override 
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
        // TODO Auto-generated method stub  
        db.execSQL(upgrade_cmd); 
    } 

public class DatabaseHelper extends SQLiteOpenHelper {
 private static final String DB_NAME = "personInfo.db";
 private static final String TB_NAME = "person";
 private static final int VERSION = 1;
 private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)";
 private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)";
 
 public DatabaseHelper(Context context) {
  super(context, DB_NAME, null, VERSION);
  // TODO Auto-generated constructor stub
 }

 @Override
 public void onCreate(SQLiteDatabase db) {
  // TODO Auto-generated method stub
  db.execSQL(creat_cmd);
 }
 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  // TODO Auto-generated method stub
  db.execSQL(upgrade_cmd);
 }
}

創建自己的ContentProvider

[java]

// 創建一個MyProvider繼承於ContentProvider  
public class MyProvider extends ContentProvider { 
    private DatabaseHelper dbHelper;    // 數據庫類  
    private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 
    private static final int ALL_PERSON = 1; 
    private static final int PERSON = 2; 
    private static final String TAG = "MyProvider"; 
    private static final String TABLE_NAME = "person"; 
    // 定義自己的URI  
    private static final String AUTHORITY = "com.myAndroid.myProvider"; 
    public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME); 
     
    static { 
        URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON); 
        URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON); 
    } 
     
    // ContentProvider的入口,初始化  
    @Override 
    public boolean onCreate() { 
        // TODO Auto-generated method stub  
        Log.i(TAG, "----onCreate----"); 
        dbHelper = new DatabaseHelper(this.getContext()); 
        return false; 
    } 
     
    @Override 
    public int delete(Uri arg0, String arg1, String[] arg2) { 
        // TODO Auto-generated method stub  
        Log.i(TAG, "----delete----"); 
        SQLiteDatabase db = dbHelper.getWritableDatabase(); 
        int count = 0; 
        switch (URI_MATCHER.match(arg0)) { 
        case ALL_PERSON: 
            count = db.delete(TABLE_NAME, arg1, arg2); 
        case PERSON: 
            long id = ContentUris.parseId(arg0); 
            String where = "_id=" + id; 
            if(arg1 != null && !"".equals(arg1)) { 
                where += " and " + arg1; 
            } 
            count = db.delete(TABLE_NAME, where, arg2); 
        default: 
            throw new IllegalArgumentException("Unknown Uri:" + arg0.toString()); 
        } 
        getContext().getContentResolver().notifyChange(arg0, null); // 通知注冊客戶端數據發生改變  
        return count; 
    } 
 
    @Override 
    public String getType(Uri arg0) { 
        // TODO Auto-generated method stub  
        Log.i(TAG, "----getType----"); 
        switch (URI_MATCHER.match(arg0)) { 
        case ALL_PERSON: 
                return "com.android.cursor.dir/person"; 
        case PERSON: 
                return "com.android.cursor.item/person"; 
        default: 
                throw new IllegalArgumentException("Unknow Uri" + arg0.toString()); 
        } 
    } 
 
    @Override 
    public Uri insert(Uri uri, ContentValues arg1) { 
        // TODO Auto-generated method stub  
        Log.i(TAG, "----insert----"); 
        SQLiteDatabase db = dbHelper.getWritableDatabase(); 
        Uri insertUri = null; 
        switch (URI_MATCHER.match(uri)) { 
        case ALL_PERSON: 
            long rowId = db.insert(TABLE_NAME, "name", arg1); 
            Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId); 
            insertUri = ContentUris.withAppendedId(uri, rowId); 
//          this.getContext().getContentResolver().notifyChange(insertUri, null);  
            break; 
        default: 
            throw new IllegalArgumentException("Unknown Uri:" + uri.toString()); 
        } 
        getContext().getContentResolver().notifyChange(insertUri, null); 
        return insertUri; 
    } 
 
    // 處理查詢,返回Cursor  
    @Override 
    public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, 
            String arg4) { 
        // TODO Auto-generated method stub  
        Log.i(TAG, "----query----"); 
        SQLiteDatabase db = dbHelper.getReadableDatabase(); 
        Cursir cursor = null; 
        switch (URI_MATCHER.match(arg0)) { 
        case ALL_PERSON:                    // query all the person info  
            cursor =  db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4); 
        case PERSON:                        // query only one person info from a given ID  
            long id = ContentUris.parseId(arg0); 
            String where = " _id=" + id; 
            if( (!"".equals(arg2)) && (arg2 != null)) { 
                where += " and " + arg2 ; 
            } 
            cursor =  db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4); 
        default: 
            throw new IllegalArgumentException("unknow uri" + arg0.toString()); 
        } 
        if(cursor !=  null) { 
        // 注冊該Uri對應的數據發生改變時,向客戶端發送通知  
            cursor.setNotificationUri(getContext().getContentResolver(), uri);           
        } 
        return cursor; 
    } 
 
    @Override 
    public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { 
        // TODO Auto-generated method stub  
        Log.i(TAG, "----update----"); 
        SQLiteDatabase db = dbHelper.getWritableDatabase(); 
        int count = 0; 
        switch (URI_MATCHER.match(arg0)) { 
        case ALL_PERSON: 
            count = db.update(TABLE_NAME, arg1, arg2, arg3); 
            break; 
        case PERSON: 
            long id = ContentUris.parseId(arg0); 
            String where = "_id=" + id; 
            if( (arg2 != null) && (!"".equals(arg2))) { 
                where += " and " + arg2; 
            } 
            count = db.update(TABLE_NAME, arg1, where, arg3); 
            break; 
        default: 
            throw new IllegalArgumentException("Unknow Uri:"+arg0.toString()); 
        } 
        getContext().getContentResolver().notifyChange(arg0, null); 
        return count; 
    } 

// 創建一個MyProvider繼承於ContentProvider
public class MyProvider extends ContentProvider {
 private DatabaseHelper dbHelper; // 數據庫類
 private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 private static final int ALL_PERSON = 1;
 private static final int PERSON = 2;
 private static final String TAG = "MyProvider";
 private static final String TABLE_NAME = "person";
 // 定義自己的URI
 private static final String AUTHORITY = "com.myAndroid.myProvider";
 public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
 
 static {
  URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON);
  URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON);
 }
 
 // ContentProvider的入口,初始化
 @Override
 public boolean onCreate() {
  // TODO Auto-generated method stub
  Log.i(TAG, "----onCreate----");
  dbHelper = new DatabaseHelper(this.getContext());
  return false;
 }
 
 @Override
 public int delete(Uri arg0, String arg1, String[] arg2) {
  // TODO Auto-generated method stub
  Log.i(TAG, "----delete----");
  SQLiteDatabase db = dbHelper.getWritableDatabase();
  int count = 0;
  switch (URI_MATCHER.match(arg0)) {
  case ALL_PERSON:
   count = db.delete(TABLE_NAME, arg1, arg2);
  case PERSON:
   long id = ContentUris.parseId(arg0);
   String where = "_id=" + id;
   if(arg1 != null && !"".equals(arg1)) {
    where += " and " + arg1;
   }
   count = db.delete(TABLE_NAME, where, arg2);
  default:
   throw new IllegalArgumentException("Unknown Uri:" + arg0.toString());
  }
  getContext().getContentResolver().notifyChange(arg0, null); // 通知注冊客戶端數據發生改變
  return count;
 }

 @Override
 public String getType(Uri arg0) {
  // TODO Auto-generated method stub
  Log.i(TAG, "----getType----");
  switch (URI_MATCHER.match(arg0)) {
  case ALL_PERSON:
    return "com.android.cursor.dir/person";
  case PERSON:
    return "com.android.cursor.item/person";
  default:
    throw new IllegalArgumentException("Unknow Uri" + arg0.toString());
  }
 }

 @Override
 public Uri insert(Uri uri, ContentValues arg1) {
  // TODO Auto-generated method stub
  Log.i(TAG, "----insert----");
  SQLiteDatabase db = dbHelper.getWritableDatabase();
  Uri insertUri = null;
  switch (URI_MATCHER.match(uri)) {
  case ALL_PERSON:
   long rowId = db.insert(TABLE_NAME, "name", arg1);
   Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId);
   insertUri = ContentUris.withAppendedId(uri, rowId);
//   this.getContext().getContentResolver().notifyChange(insertUri, null);
   break;
  default:
   throw new IllegalArgumentException("Unknown Uri:" + uri.toString());
  }
  getContext().getContentResolver().notifyChange(insertUri, null);
  return insertUri;
 }

 // 處理查詢,返回Cursor
 @Override
 public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
   String arg4) {
  // TODO Auto-generated method stub
  Log.i(TAG, "----query----");
  SQLiteDatabase db = dbHelper.getReadableDatabase();
  Cursir cursor = null;
  switch (URI_MATCHER.match(arg0)) {
  case ALL_PERSON:     // query all the person info
   cursor =  db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4);
  case PERSON:      // query only one person info from a given ID
   long id = ContentUris.parseId(arg0);
   String where = " _id=" + id;
   if( (!"".equals(arg2)) && (arg2 != null)) {
    where += " and " + arg2 ;
   }
   cursor =  db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4);
  default:
   throw new IllegalArgumentException("unknow uri" + arg0.toString());
  }
  if(cursor !=  null) {
  // 注冊該Uri對應的數據發生改變時,向客戶端發送通知
   cursor.setNotificationUri(getContext().getContentResolver(), uri);   
  }
  return cursor;
 }

 @Override
 public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
  // TODO Auto-generated method stub
  Log.i(TAG, "----update----");
  SQLiteDatabase db = dbHelper.getWritableDatabase();
  int count = 0;
  switch (URI_MATCHER.match(arg0)) {
  case ALL_PERSON:
   count = db.update(TABLE_NAME, arg1, arg2, arg3);
   break;
  case PERSON:
   long id = ContentUris.parseId(arg0);
   String where = "_id=" + id;
   if( (arg2 != null) && (!"".equals(arg2))) {
    where += " and " + arg2;
   }
   count = db.update(TABLE_NAME, arg1, where, arg3);
   break;
  default:
   throw new IllegalArgumentException("Unknow Uri:"+arg0.toString());
  }
  getContext().getContentResolver().notifyChange(arg0, null);
  return count;
 }
}

客戶端使用Content Provider:

[java]

// 定義自己的URI  
private static final String TABLE_NAME = "person"; 
private static final String AUTHORITY = "com.myAndroid.myProvider"; 
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME); 
 
// 插入  
ContentResolver contenResolver = MainActivity.this.getContentResolver(); 
ContentValues values = new ContentValues(); 
values.put("name", "hello"); 
values.put("age", 25); 
Uri resultUri = contentResolver.insert(CONTENT_URI, values); 
if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); } 
 
// 查詢  
String columns[] = new String[] ("_id", "name", "age"); 
Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id"); 
if(cursor.moveToFirst()) { 
    do { 
        Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id"))); 
        Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name"))); 
        Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age"))); 
    } while(cursor.moveToNext()); 
    cursor.close(); 

 
// 刪除 ID為1的記錄  
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null); 
 
// 修改ID為 1 的記錄  
ContentValues values = new ContentValues(); 
values.put("name", "world"); 
values.put("age",  32); 
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null); 

// 定義自己的URI
private static final String TABLE_NAME = "person";
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);

// 插入
ContentResolver contenResolver = MainActivity.this.getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "hello");
values.put("age", 25);
Uri resultUri = contentResolver.insert(CONTENT_URI, values);
if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); }

// 查詢
String columns[] = new String[] ("_id", "name", "age");
Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id");
if(cursor.moveToFirst()) {
 do {
  Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id")));
  Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name")));
  Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age")));
 } while(cursor.moveToNext());
 cursor.close();
}

// 刪除 ID為1的記錄
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null);

// 修改ID為 1 的記錄
ContentValues values = new ContentValues();
values.put("name", "world");
values.put("age",  32);
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null);
 

最後我們還需要在AndroidManifest.xml中定義我們的provider屬性:

[html]

<provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" /> 

<provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" />

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved