contentprovider

j#1.ContentProvider简介
当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。

        

anthorities是MyProvider的唯一标识,通过此authorities能找到个内容提供者

一.为什么要使用contentprovider

1.ContentProvider提供了对底层数据存储方式的抽象,比如,底层使用了SQLite数据库,在用了ContentProvider封装后,即使你把数据库换成MongoDB,也不会对上层数据使用层代码产生影响。
2.Android框架中的一些类需要ContentProvider类型数据。如果想让数据可以使用在如SyncAdapter, Loader, CursorAdapter等类上,那么就需要为数据做一层ContentProvider封装。
3.第三个原因也是最主要的原因,是ContentProvider为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查,而不用担心直接开放数据库权限而带来的安全问题

Binder进程间通信机制可以突破了以应用程序为边界的权限控制来实现在不同应用程序之间传输数据,而Content Provider组件在不同应用程序之间共享数据正是基于Binder进程间通信机制来实现的。虽然Binder进程间通信机制突破了以应用程序为边界的权限控制,但是它是安全可控的,因为数据的访问接口是由数据的所有者来提供的,换句话来说,就是数据提供方可以在接口层来实现安全控制,决定哪些数据是可以读,哪些数据可以写

Binder进程间通信机制虽然打通了应用程序之间共享数据的通道,但是还有一个问题需要解决,那就是数据要以什么来作来媒介来传输。我们知道,应用程序采用Binder进程间通信机制进行通信时,要传输的数据都是采用函数参数的形式进行的,对于一般的进程间调来来说,这是没有问题的,然而,对于应用程序之间的共享数据来说,它们的数据量可能是非常大的,如果还是简单的用函数参数的形式来传递,效率就会比较低下。我们知道,在应用程序进程之间以匿名共享内存的方式来传输数据效率是非常高的,因为它们之间只需要传递一个文件描述符就可以了。因此,Content Provider组件在不同应用程序之间传输数据正是基于匿名共享内存机制来实现的

二.ContentProvider的MIME类型

对于单条记录,MIME类型类似于:
vnd.android.cursor.item/vnd.your-company.content-type
而对于记录的集合,MIME类型类似于:
vnd.android.cursor.dir/vnd.your-company.comtent-type

content-type可以根据ContentProvider的功能来定,比如日记的ContentProvider可以为note,日程安排的ContentProvider可以为schedule

contacts2.db的mimetypes类型

_id    mimetype
1   vnd.android.cursor.item/email_v2
9   vnd.android.cursor.item/identity
2   vnd.android.cursor.item/im
7   vnd.android.cursor.item/name
3   vnd.android.cursor.item/nickname
4   vnd.android.cursor.item/organization
5   vnd.android.cursor.item/phone_v2
10  vnd.android.cursor.item/photo
8   vnd.android.cursor.item/postal-address_v2
6   vnd.android.cursor.item/sip_address

2.Uri

Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作
content://authority/path/id

   1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。     
   2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。       
   3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

• 要操作contact表中id为10的记录,可以构建这样的路径:/contact/10
• 要操作contact表中id为10的记录的name字段, contact/10/name
• 要操作contact表中的所有记录,可以构建这样的路径:/contact

**
//操作contact表中所有的记录
content://authorities/contact
//操作contact表中id为10的记录
content://authorities/contact/10
//操作contact表中id为10的记录的name字段
content://authorities/contact/10/name
**

3.UriMatcher、ContentUrist和ContentResolver简介

因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。

UriMatcher:用于匹配Uri,它的用法如下:

1.首先把你需要匹配Uri路径全部给注册上,如下:
常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回匹配码为1 uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配 content://com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2 uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);//#号为通配符
2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回的匹配码为1。

ContentUris:用于获取Uri路径后面的ID部分

它有两个比较实用的方法:
• withAppendedId(uri, id)用于为路径加上ID部分
• parseId(uri)方法用于从路径中获取ID部分

ContentResolver:

当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据

有些人可能会疑惑,为什么我们不直接访问Provider,而是又在上面加了一层ContentResolver来进行对其的操作,这样岂不是更复杂了吗?其实不然,大家要知道一台手机中可不是只有一个Provider内容,它可能安装了很多含有Provider的应用,比如联系人应用,日历应用,字典应用等等。有如此多的Provider,如果你开发一款应用要使用其中多个,如果让你去了解每个ContentProvider的不同实现,岂不是要头都大了。所以Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作

那ContentResolver是如何来区别不同的ContentProvider的呢?这就涉及到URI(Uniform Resource Identifier)

4.ContentProvider的创建和使用

4.1自定义ContentProvider

public class RuiXin {

    public static final String DBNAME = "ruixinonlinedb";
    public static final String TNAME = "ruixinonline";
    public static final int VERSION = 3;

    public static String TID = "tid";
    public static final String EMAIL = "email";
    public static final String USERNAME = "username";
    public static final String DATE = "date";
    public static final String SEX = "sex";

    public static final String AUTOHORITY = "com.ruixin.login";
    public static final int ITEM = 1;
    public static final int ITEM_ID = 2;

    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.ruixin.login";
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.ruixin.login";

    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY
            + "/ruixinonline");
}
public class DBlite extends SQLiteOpenHelper {
    public DBlite(Context context) {
        super(context, RuiXin.DBNAME, null, RuiXin.VERSION);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        db.execSQL("create table " + RuiXin.TNAME + "(" + RuiXin.TID
                + " integer primary key autoincrement not null," + RuiXin.EMAIL
                + " text not null," + RuiXin.USERNAME + " text not null,"
                + RuiXin.DATE + " interger not null," + RuiXin.SEX
                + " text not null);");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub
    }

    public void add(String email, String username, String date, String sex) {
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(RuiXin.EMAIL, email);
        values.put(RuiXin.USERNAME, username);
        values.put(RuiXin.DATE, date);
        values.put(RuiXin.SEX, sex);
        db.insert(RuiXin.TNAME, "", values);
    }
}

public class MyProvider extends ContentProvider {

    DBlite dBlite;
    SQLiteDatabase db;

    private static final UriMatcher sMatcher;
    static {
        sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sMatcher.addURI(RuiXin.AUTOHORITY, RuiXin.TNAME, RuiXin.ITEM);
        sMatcher.addURI(RuiXin.AUTOHORITY, RuiXin.TNAME + "/#", RuiXin.ITEM_ID);

    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        db = dBlite.getWritableDatabase();
        int count = 0;
        switch (sMatcher.match(uri)) {
        case RuiXin.ITEM:
            count = db.delete(RuiXin.TNAME, selection, selectionArgs);
            break;
        case RuiXin.ITEM_ID:
            String id = uri.getPathSegments().get(1);
            count = db.delete(
                    RuiXin.TID,
                    RuiXin.TID
                            + "="
                            + id
                            + (!TextUtils.isEmpty(RuiXin.TID = "?") ? "AND("
                                    + selection + ')' : ""), selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        switch (sMatcher.match(uri)) {
        case RuiXin.ITEM:
            return RuiXin.CONTENT_TYPE;
        case RuiXin.ITEM_ID:
            return RuiXin.CONTENT_ITEM_TYPE;
        default:
            throw new IllegalArgumentException("Unknown URI" + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub

        db = dBlite.getWritableDatabase();
        long rowId;
        if (sMatcher.match(uri) != RuiXin.ITEM) {
            throw new IllegalArgumentException("Unknown URI" + uri);
        }
        rowId = db.insert(RuiXin.TNAME, RuiXin.TID, values);
        if (rowId > 0) {
            Uri noteUri = ContentUris.withAppendedId(RuiXin.CONTENT_URI, rowId);
            getContext().getContentResolver().notifyChange(noteUri, null);
            return noteUri;
        }
        throw new IllegalArgumentException("Unknown URI" + uri);
    }

    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        this.dBlite = new DBlite(this.getContext());
        // db = dBlite.getWritableDatabase();
        // return (db == null)?false:true;
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        // TODO Auto-generated method stub
        db = dBlite.getWritableDatabase();
        Cursor c;
        Log.d("-------", String.valueOf(sMatcher.match(uri)));
        switch (sMatcher.match(uri)) {
        case RuiXin.ITEM:
            c = db.query(RuiXin.TNAME, projection, selection, selectionArgs,
                    null, null, null);

            break;
        case RuiXin.ITEM_ID:
            String id = uri.getPathSegments().get(1);
            c = db.query(RuiXin.TNAME, projection, RuiXin.TID
                    + "="
                    + id
                    + (!TextUtils.isEmpty(selection) ? "AND(" + selection + ')'
                            : ""), selectionArgs, null, null, sortOrder);
            break;
        default:
            Log.d("!!!!!!", "Unknown URI" + uri);
            throw new IllegalArgumentException("Unknown URI" + uri);
        }
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }
}




    
        
            
                

                
            


            
                
            
            
                
            

        
        
    


4.2使用ContentObserver

ContentObserver是观察特定Uri相关的数据的变换,然后做出相应的操作
可以通过UriMatcher类注册不同类型的Uri,我们可以通过这些不同的Uri来查询不同的结果。根据Uri返回的结果,Uri Type可以分为:返回多条数据的Uri、返回单条数据的Uri。

注册和注销ContentObserver

public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
 功能:为指定的Uri注册一个ContentObserver数据观察者对象,当给定的Uri发生改变时,回调该实例对象去处理。
 参数:uri:需要观察的Uri(需要在UriMatcher里注册,否则该Uri也没有意义了)
      notifyForDescendents:为false 表示精确匹配,即只匹配该Uri,为true 表示可以同时匹配其派生的Uri

public final void  unregisterContentObserver(contentobserver)

ContentObserver类的简介

public ContentObserver(Handler handler) 
所有ContentObserver的派生类都需要调用该构造方法
@Overridepublic void onChange(boolean selfChange) {
//数据该表时回调该方法
}

使用ContentObserver的步骤

1.创建我们特定的ContentObserver派生类,必须重载父类构造方法,必须重载onChange()方法去处理回调后的功能实现
2.利用context.getContentResolover()获得ContentResolove对象,接着调用registerContentObserver()方法去注册内容观察者
3.由于ContentObserver的生命周期不同步于Activity和Service等,因此,在不需要时,需要手动的调用unregisterContentObserver()去取消注册。

使用ContentObserver的两种情况

1.需要频繁检测数据是否发生改变,如果用线程去操作很耗时不经济
2.在用户不知晓的情况下对数据库做一些事件

让contentProvider的改变能被ContentObserver接收到的原因是

每个ContentProvider数据源发生改变后,如果想通知其监听对象, 例如ContentObserver时,必须在其对应方法 update /
insert / delete时,显示的调用this.getContentReslover().notifychange(uri , null)方法,回调监听处理逻辑。否则,我们的ContentObserver是不会监听到数据发生改变的

4.3结合使用案例


public class Person {
    public int _id;
    public String name;
    public int age;
    public String info;

    public Person() {
    }

    public Person(String name, int age, String info) {
        this.name = name;
        this.age = age;
        this.info = info;
    }
}

public class DBHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "provider.db";
    private static final int DATABASE_VERSION = 1;

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS person"
                + "(_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, age INTEGER, info TEXT)";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS person");
        onCreate(db);
    }
}
public class PersonProvider extends ContentProvider {

    private static final UriMatcher matcher;
    private DBHelper helper;
    private SQLiteDatabase db;

    private static final String AUTHORITY = "com.scott.provider.PersonProvider";
    private static final int PERSON_ALL = 0;
    private static final int PERSON_ONE = 1;

    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.scott.person";
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.scott.person";

    // 数据改变后立即重新查询
    private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY
            + "/persons");

    static {
        matcher = new UriMatcher(UriMatcher.NO_MATCH);

        matcher.addURI(AUTHORITY, "persons", PERSON_ALL); // 匹配记录集合
        matcher.addURI(AUTHORITY, "persons/#", PERSON_ONE); // 匹配单条记录
    }

    @Override
    public boolean onCreate() {
        helper = new DBHelper(getContext());
        return true;
    }

    @Override
    public String getType(Uri uri) {
        int match = matcher.match(uri);
        switch (match) {
        case PERSON_ALL:
            return CONTENT_TYPE;
        case PERSON_ONE:
            return CONTENT_ITEM_TYPE;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        db = helper.getReadableDatabase();
        int match = matcher.match(uri);
        switch (match) {
        case PERSON_ALL:
            // doesn't need any code in my provider.
            break;
        case PERSON_ONE:
            long _id = ContentUris.parseId(uri);
            selection = "_id = ?";
            selectionArgs = new String[] { String.valueOf(_id) };
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        return db.query("person", projection, selection, selectionArgs, null,
                null, sortOrder);
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int match = matcher.match(uri);
        if (match != PERSON_ALL) {
            throw new IllegalArgumentException("Wrong URI: " + uri);
        }
        db = helper.getWritableDatabase();
        if (values == null) {
            values = new ContentValues();
            values.put("name", "no name");
            values.put("age", "1");
            values.put("info", "no info.");
        }
        long rowId = db.insert("person", null, values);
        if (rowId > 0) {
            notifyDataChanged();
            return ContentUris.withAppendedId(uri, rowId);
        }
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        db = helper.getWritableDatabase();
        int match = matcher.match(uri);
        switch (match) {
        case PERSON_ALL:
            // doesn't need any code in my provider.
            break;
        case PERSON_ONE:
            long _id = ContentUris.parseId(uri);
            selection = "_id = ?";
            selectionArgs = new String[] { String.valueOf(_id) };
        }
        int count = db.delete("person", selection, selectionArgs);
        if (count > 0) {
            notifyDataChanged();
        }
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        db = helper.getWritableDatabase();
        int match = matcher.match(uri);
        switch (match) {
        case PERSON_ALL:
            // doesn't need any code in my provider.
            break;
        case PERSON_ONE:
            long _id = ContentUris.parseId(uri);
            selection = "_id = ?";
            selectionArgs = new String[] { String.valueOf(_id) };
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        int count = db.update("person", values, selection, selectionArgs);
        if (count > 0) {
            notifyDataChanged();
        }
        return count;
    }

    // 通知指定URI数据已改变
    private void notifyDataChanged() {
        getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
    }
}
public class PersonObserver extends ContentObserver {

    public static final String TAG = "PersonObserver";
    private Handler handler;

    public PersonObserver(Handler handler) {
        super(handler);
        this.handler = handler;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        Log.i(TAG, "data changed, try to requery.");
        // 向handler发送消息,更新查询记录
        Message msg = new Message();
        handler.sendMessage(msg);
    }
}

public class MainActivity extends Activity {

    private ContentResolver resolver;
    private ListView listView;

    private static final String AUTHORITY = "com.scott.provider.PersonProvider";
    private static final Uri PERSON_ALL_URI = Uri.parse("content://"
            + AUTHORITY + "/persons");

    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            // update records.
            requery();
        };
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        resolver = getContentResolver();
        listView = (ListView) findViewById(R.id.listView);

        // 为PERSON_ALL_URI注册变化通知
        getContentResolver().registerContentObserver(PERSON_ALL_URI, true,
                new PersonObserver(handler));
    }

    /**
     * 初始化
     * 
     * @param view
     */
    public void init(View view) {
        ArrayList persons = new ArrayList();

        Person person1 = new Person("Ella", 22, "lively girl");
        Person person2 = new Person("Jenny", 22, "beautiful girl");
        Person person3 = new Person("Jessica", 23, "sexy girl");
        Person person4 = new Person("Kelly", 23, "hot baby");
        Person person5 = new Person("Jane", 25, "pretty woman");

        persons.add(person1);
        persons.add(person2);
        persons.add(person3);
        persons.add(person4);
        persons.add(person5);

        for (Person person : persons) {
            ContentValues values = new ContentValues();
            values.put("name", person.name);
            values.put("age", person.age);
            values.put("info", person.info);
            resolver.insert(PERSON_ALL_URI, values);
        }
    }

    /**
     * 查询所有记录
     * 
     * @param view
     */
    public void query(View view) {
        // Uri personOneUri = ContentUris.withAppendedId(PERSON_ALL_URI,
        // 1);查询_id为1的记录
        Cursor c = resolver.query(PERSON_ALL_URI, null, null, null, null);

        CursorWrapper cursorWrapper = new CursorWrapper(c) {

            @Override
            public String getString(int columnIndex) {
                // 将简介前加上年龄
                if (getColumnName(columnIndex).equals("info")) {
                    int age = getInt(getColumnIndex("age"));
                    return age + " years old, " + super.getString(columnIndex);
                }
                return super.getString(columnIndex);
            }
        };

        // Cursor须含有"_id"字段
        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
                android.R.layout.simple_list_item_2, cursorWrapper,
                new String[] { "name", "info" }, new int[] {
                        android.R.id.text1, android.R.id.text2 });
        listView.setAdapter(adapter);

        startManagingCursor(cursorWrapper); // 管理Cursor
    }

    /**
     * 插入一条记录
     * 
     * @param view
     */
    public void insert(View view) {
        Person person = new Person("Alina", 26, "attractive lady");
        ContentValues values = new ContentValues();
        values.put("name", person.name);
        values.put("age", person.age);
        values.put("info", person.info);
        resolver.insert(PERSON_ALL_URI, values);
    }

    /**
     * 更新一条记录
     * 
     * @param view
     */
    public void update(View view) {
        Person person = new Person();
        person.name = "Jane";
        person.age = 30;
        // 将指定name的记录age字段更新为30
        ContentValues values = new ContentValues();
        values.put("age", person.age);
        resolver.update(PERSON_ALL_URI, values, "name = ?",
                new String[] { person.name });

        // 将_id为1的age更新为30
        // Uri updateUri = ContentUris.withAppendedId(PERSON_ALL_URI, 1);
        // resolver.update(updateUri, values, null, null);
    }

    /**
     * 删除一条记录
     * 
     * @param view
     */
    public void delete(View view) {
        // 删除_id为1的记录
        Uri delUri = ContentUris.withAppendedId(PERSON_ALL_URI, 1);
        resolver.delete(delUri, null, null);

        // 删除所有记录
        // resolver.delete(PERSON_ALL_URI, null, null);
    }

    /**
     * 重新查询
     */
    private void requery() {
        // 实际操作中可以查询集合信息后Adapter.notifyDataSetChanged();
        query(null);
    }
}



    
        
            
                

                
            

            
            
                
            
            
                
            

        

        
    


5.ContentProvider的安装原理

PackageManagerService:负责系统中Package的管理,应用程序的安装、卸载、信息查询等
第一步:扫描apk文件并准备解析和安装
第二步:判断是新安装还是覆盖安装
第三步:对apk的manifest文件进行解析
第四步:解析application标签
第五步:解析provider标签
第六步:将解析到的信息保存到PMS中,以后需要使用provider就从PMS中进行获取

6.ContentProvider的获取

第一步:每个Context类都会包含一个ApplicationContentResolver对象
第二步:ApplicationContentResolver调用acquireProvider对象,主要是交个ActivityThread来处理
第三步:ActivityThread会判断,如果本地保存有需要的CP对象,就会直接返回这个对象,如果没有保存,就从AMS对象中查询并获取,如果从AMS中找到了,就会把保存到本地方便后边的使用,如果AMS中没有找到,就会在PMS全部的CP对象中查找
第四步:查找到的CP对象会层层返回到调用的地方

7.ContentProvider的启动

安装apk的时候,并不会把CP加载到内存中,系统采用的是懒加载机制,等到第一次使用cp的时候,系统才会把它加载到内存,下次再要使用的时候就直接从内存中获取

下一步学习的内容

http://blog.csdn.net/luoshengyang/article/details/6967204

参考:
http://www.cnblogs.com/chenglong/articles/1892029.html
http://blog.csdn.net/liuhe688/article/details/7050868
http://www.jianshu.com/p/f5ec75a9cfea

你可能感兴趣的:(contentprovider)