zoukankan      html  css  js  c++  java
  • Android组件系列----ContentProvider内容提供者

    【声明】 

    欢迎转载,但请保留文章原始出处→_→ 

    生命壹号:http://www.cnblogs.com/smyhvae/

    文章来源:http://www.cnblogs.com/smyhvae/p/4108017.html

     

    【正文】


    一、ContentProvider简介:

    ContentProvider内容提供者(四大组件之一)主要用于在不同的应用程序之间实现数据共享的功能

    ContentProvider可以理解为一个Android应用对外开放的接口,只要是符合它所定义的Uri格式的请求,均可以正常访问执行操作。其他的Android应用可以使用ContentResolver对象通过与ContentProvider同名的方请求执行,被执行的就是ContentProvider中的同名方法。所以ContentProvider有很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的,来看 下面这一张图:

    12154217-a98af1e70c7e46cca16299e42ee30fb3

    Android附带了许多有用的ContentProvider,但是本文暂时不涉及到这么多(本文将学习如何创建自己的ContentProvider)。Android中自带的ContentProvider包括:

    • Browser:存储如浏览器的信息。
    • CallLog:存储通话记录等信息。
    • Contacts Provider:存储联系人(通讯录)等信息。
    • MediaStore:存储媒体文件的信息。
    • Settings:存储设备的设置和首选项信息。

    此外,还有日历、

    ContentProvider的方法:

    如果要创建自己的内容提供者,需要新建一个类继承抽象类ContentProvider,并重写其中的抽象方法。抽象方法如下:

    boolean onCreate()   
    初始化提供者 Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 查询数据,返回一个数据Cursor对象。其中参数selection和selectionArgs是外部程序提供的查询条件 Uri insert(Uri uri, ContentValues values)
    插入一条数据。参数values是需要插入的值
    int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    根据条件更新数据
    int delete(Uri uri, String selection, String[] selectionArgs)
    根据条件删除数据 String getType(Uri uri)
    返回MIME类型对应内容的URI

    除了onCreate()和getType()方法外,其他的均为CRUD操作,这些方法中,Uri参数为与ContentProvider匹配的请求Uri,剩下的参数可以参见SQLite的CRUD操作,基本一致。 

    备注:还有两个非常有意思的方法,必须要提一下,call()和bulkInsert()方法,使用call,理论上可以在ContentResolver中执行ContentProvider暴露出来的任何方法,而bulkInsert()方法用于插入多条数据。

    Uri:

    在Android中,Uri是一种比较常见的资源访问方式。而对于ContentProvider而言,Uri也是有固定格式的:<srandard_prefix>://<authority>/<data_path>/<id>

    • <srandard_prefix>:ContentProvider的srandard_prefix始终是content://。
    • <authority>:ContentProvider的名称。
    • <data_path>:请求的数据类型。
    • <id>:指定请求的特定数据。

    在ContentProvider的CRUD操作,均会传递一个Uri对象,通过这个对象来匹配对应的请求。那么如何确定一个Uri执行哪项操作呢?需要用到一个UriMatcher对象,这个对象用来帮助内容提供者匹配Uri。它所提供的方法非常简单,仅有两个:

    • void addURI(String authority,String path,int code):添加一个Uri匹配项,authority为AndroidManifest.xml中注册的ContentProvider中的authority属性;path为一个路径,可以设置通配符,#表示任意数字,*表示任意字符code为自定义的一个Uri代码。
    • int match(Uri uri):匹配传递的Uri,返回addURI()传递的code参数。

    二、代码举例:

    最终所有工程文件的目录结构如下:

    5b1e38fc-929c-4587-a2b2-87f060663db5

    PersonDao是增删改查数据库的工具类,并在PersonContentProvider中得到调用。DBHelper用于初始化SQLite数据库。

    PersonContentProvider用于向外提供增删改查的接口。并最终在ContentResolverTest的MyTest.java中进行单元测试,实现CRUD。

    本文的核心类是:PersonContentProvider和MyTest

    下面来看一下具体的实现步骤。

    新建工程文件ContetProviderTest01。

    (1)新建类PersonDao:用于进行对SQLite的CRUD操作。代码如下:

    PersonDao.java:

     1 package com.example.contentprovidertest01.dao;
     2 
     3 import android.content.ContentValues;
     4 import android.content.Context;
     5 import android.database.Cursor;
     6 import android.database.sqlite.SQLiteDatabase;
     7 
     8 import com.example.contentprovidertest01.db.DBHelper;
     9 
    10 public class PersonDao {
    11     private DBHelper helper = null;
    12 
    13     public PersonDao(Context context) {
    14         helper = new DBHelper(context);
    15     }
    16 
    17     //方法:插入操作,返回的long类型为:插入当前行的行号
    18     public long insertPerson(ContentValues values) {
    19         long id = -1;
    20         SQLiteDatabase database = null;
    21         try {
    22             database = helper.getWritableDatabase();
    23             id = database.insert("person", null, values);
    24         } catch (Exception e) {
    25             e.printStackTrace();
    26         } finally {
    27             if (database != null) {
    28                 database.close();
    29             }
    30         }
    31         return id;
    32     }
    33 
    34     public int deletePerson(String whereClause, String[] whereArgs) {
    35         int count = -1;
    36         SQLiteDatabase database = null;
    37         try {
    38             database = helper.getWritableDatabase();
    39             count = database.delete("person", whereClause, whereArgs);
    40         } catch (Exception e) {
    41             e.printStackTrace();
    42         } finally {
    43             if (database != null) {
    44                 database.close();
    45             }
    46         }
    47         return count;
    48     }
    49 
    50     public int updatePerson(ContentValues values, String whereClause,
    51             String[] whereArgs) {
    52         SQLiteDatabase database = null;
    53         int count = -1;
    54         try {
    55             database = helper.getWritableDatabase();
    56             count = database.update("person", values, whereClause, whereArgs);
    57         } catch (Exception e) {
    58             e.printStackTrace();
    59         } finally {
    60             if (null != database) {
    61                 database.close();
    62             }
    63         }
    64         return count;
    65     }
    66 
    67     public Cursor queryPersons(String selection, String[] selectionArgs) {
    68         SQLiteDatabase database = null;
    69         Cursor cursor = null;
    70         try {
    71             database = helper.getReadableDatabase();
    72             cursor = database.query(true, "person", null, selection,
    73                     selectionArgs, null, null, null, null);
    74         } catch (Exception e) {
    75             e.printStackTrace();
    76         } finally {
    77             if (null != database) {
    78                 // database.close();
    79             }
    80         }
    81         return cursor;
    82     }
    83 
    84 }

    (2)新建类DBHelper:用于初始化SQLiate数据库

    DBHelper.java:

     1 package com.example.contentprovidertest01.db;
     2 
     3 import android.content.Context;
     4 import android.database.sqlite.SQLiteDatabase;
     5 import android.database.sqlite.SQLiteOpenHelper;
     6 
     7 public class DBHelper extends SQLiteOpenHelper {
     8 
     9     private static String name = "mydb.db"; // 数据库的名字
    10     private static int version = 1; // 数据库的版本
    11 
    12     public DBHelper(Context context) {
    13         super(context, name, null, version);
    14     }
    15 
    16     @Override
    17     public void onCreate(SQLiteDatabase db) {
    18         // 只能支持基本数据类型:varchar int long float boolean text blob clob
    19         // 建表语句执行
    20         String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))";
    21         db.execSQL(sql);
    22     }
    23 
    24     @Override
    25     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    26         // TODO Auto-generated method stub
    27         String sql = "alter table person add sex varchar(8)";
    28         db.execSQL(sql);
    29     }
    30 
    31 }

    (3)【核心】新建类PersonContentProvider,继承ContetProvider

    PersonContentProvider.java:

      1 package com.example.contentprovidertest01;
      2 
      3 import com.example.contentprovidertest01.dao.PersonDao;
      4 
      5 import android.content.ContentProvider;
      6 import android.content.ContentUris;
      7 import android.content.ContentValues;
      8 import android.content.UriMatcher;
      9 import android.database.Cursor;
     10 import android.net.Uri;
     11 import android.os.Bundle;
     12 import android.util.Log;
     13 
     14 public class PersonContentProvider extends ContentProvider {
     15 
     16     private final String TAG = "PersonContentProvider";
     17     private PersonDao personDao = null;
     18     private static final UriMatcher URI_MATCHER = new UriMatcher(
     19             UriMatcher.NO_MATCH);// 默认的规则是不匹配的
     20     private static final int PERSON = 1; // 操作单行记录
     21     private static final int PERSONS = 2; // 操作多行记录
     22     // 往UriMatcher中添加匹配规则。注意,这里面的url不要写错了,我就是因为写错了,半天没调试出来。哎···
     23     static {
     24         // 添加两个URI筛选
     25         URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider",
     26                 "person", PERSONS);
     27         // 使用通配符#,匹配任意数字
     28         URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider",
     29                 "person/#", PERSON);
     30     }
     31 
     32     public PersonContentProvider() {
     33 
     34     }
     35 
     36     @Override
     37     public boolean onCreate() {
     38         // 初始化一个数据持久层
     39         personDao = new PersonDao(getContext());
     40         //Log.i(TAG, "--->>onCreate()被调用");
     41         return true;
     42     }
     43 
     44     @Override
     45     public Uri insert(Uri uri, ContentValues values) {
     46         Uri resultUri = null;
     47         // 解析Uri,返回Code
     48         int flag = URI_MATCHER.match(uri);
     49         switch (flag) {
     50         case PERSONS:
     51             //调用数据库的访问方法   
     52             long id = personDao.insertPerson(values); //执行插入操作的方法,返回插入当前行的行号
     53             resultUri = ContentUris.withAppendedId(uri, id);
     54             Log.i(TAG,"--->>插入成功, id=" + id);
     55             Log.i(TAG,"--->>插入成功, resultUri=" + resultUri.toString());
     56             System.out.println("insert success");
     57             break;
     58         }
     59         return resultUri;
     60     }
     61 
     62     //方法:删除记录。注:参数:selection和selectionArgs是查询的条件,是由外部(另一个应用程序)传进来的
     63     @Override
     64     public int delete(Uri uri, String selection, String[] selectionArgs) {
     65         int count = -1; //影响数据库的行数
     66         try {
     67             int flag = URI_MATCHER.match(uri);
     68             switch (flag) {
     69             case PERSON:
     70                 // delete from student where id=?
     71                 // 单条数据,使用ContentUris工具类解析出结尾的Id
     72                 long id = ContentUris.parseId(uri);
     73                 String where_value = "id = ?";
     74                 String[] args = { String.valueOf(id) };
     75                 count = personDao.deletePerson(where_value, args);
     76                 break;
     77             case PERSONS:
     78                 count = personDao.deletePerson(selection, selectionArgs);
     79                 break;
     80             }
     81         } catch (Exception e) {
     82             e.printStackTrace();
     83         }
     84         Log.i(TAG, "--->>删除成功,count=" + count);
     85         return count;
     86     }
     87 
     88     @Override
     89     public int update(Uri uri, ContentValues values, String selection,
     90             String[] selectionArgs) {
     91         int count = -1;
     92         try {
     93             int flag = URI_MATCHER.match(uri);
     94             switch (flag) {
     95             case PERSON:
     96                 long id = ContentUris.parseId(uri);
     97                 String where_value = " id = ?";
     98                 String[] args = { String.valueOf(id) };
     99                 count = personDao.updatePerson(values, where_value, args);
    100                 break;
    101             case PERSONS:
    102                 count = personDao
    103                         .updatePerson(values, selection, selectionArgs);
    104                 break;
    105             }
    106         } catch (Exception e) {
    107             e.printStackTrace();
    108         }
    109         Log.i(TAG, "--->>更新成功,count=" + count);
    110         return count;
    111     }
    112 
    113     @Override
    114     public Cursor query(Uri uri, String[] projection, String selection,
    115             String[] selectionArgs, String sortOrder) {
    116         Cursor cursor = null;
    117         try {
    118             int flag = URI_MATCHER.match(uri);
    119             switch (flag) {
    120             case PERSON:
    121                 long id = ContentUris.parseId(uri);
    122                 String where_value = " id = ?";
    123                 String[] args = { String.valueOf(id) };
    124                 cursor = personDao.queryPersons(where_value, args);
    125                 break;
    126             case PERSONS:
    127                 cursor = personDao.queryPersons(selection, selectionArgs);
    128                 break;
    129             }
    130         } catch (Exception e) {
    131             e.printStackTrace();
    132         }
    133         Log.i(TAG, "--->>查询成功,Count=" + cursor.getCount());
    134         return cursor;
    135     }
    136 
    137     @Override
    138     public String getType(Uri uri) {
    139         int flag = URI_MATCHER.match(uri);
    140         switch (flag) {
    141         case PERSON:
    142             return "vnd.android.cursor.item/person"; // 如果是单条记录,则为vnd.android.cursor.item/
    143                                                         // + path
    144 
    145         case PERSONS:
    146             return "vnd.android.cursor.dir/persons"; // 如果是多条记录,则为vnd.android.cursor.dir/
    147                                                         // + path
    148         }
    149         return null;
    150     }
    151 
    152     @Override
    153     public Bundle call(String method, String arg, Bundle extras) {
    154         Log.i(TAG, "--->>" + method);
    155         Bundle bundle = new Bundle();
    156         bundle.putString("returnCall", "call被执行了");
    157         return bundle;
    158     }
    159 }

    18行的UriMatcher类的作用是:匹配内容uri,默认的规则是不匹配的。UriMatcher提供了一个addURI方法:

    • void android.content.UriMatcher.addURI(String authority, String path, int code)

    这三个参数分别代表:权限、路径、和一个自定义代码。一般第一个参数是uri(包名.内容提供者的类名),第二个参数一般是数据库的表名。

    27行:匹配规则的解释:*表示匹配任意字符,#表示匹配任意数字注:如果内部的匹配规则越多,越容易访问。

    138行的getType(Uri uri)方法:所有的内容提供者都必须提供的一个方法。用于获取uri对象所对应的MIME类型。

    然后,每编写一个内容提供者,都必须在清单文件中进行声明。在AndroidManifest.xml中<application>节点中增加,格式如下:

    <provider
        android:name=".内容提供者的类名"
        android:authorities="包名.内容提供者的类名" >
    </provider>

    第3行表示的是uri路径,毕竟Contet Provider是通过路径来访问的。

    所以在本程序中,在AndroidManifest.xml的<application>节点中增加如下代码:

    <provider
      android:name=".PersonContentProvider"
      android:authorities="com.example.contentprovidertest01.PersonContentProvider" >
    </provider>

    (4)单元测试类:

    这里需要涉及到另外一个知识:ContentResolver内容访问者

    要想访问ContentProvider,则必须使用ContentResolver。可以通过ContentResolver来操作ContentProvider所暴露处理的接口。一般使用Content.getContentResolver()方法获取ContentResolver对象。第一段中已经提到:ContentProvider有很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的。所以它也存在insert、query、update、delete等方法。于是单元测试类可以这样写:(注:单元测试如果不清楚,可以参考另外一篇文章: JUnit单元测试的使用

    MyTest.java:

      1 package com.example.contentresolvertest;
      2 
      3 import android.content.ContentResolver;
      4 import android.content.ContentValues;
      5 import android.database.Cursor;
      6 import android.net.Uri;
      7 import android.os.Bundle;
      8 import android.test.AndroidTestCase;
      9 import android.util.Log;
     10 
     11 public class MyTest extends AndroidTestCase {
     12 
     13     public MyTest() {
     14         // TODO Auto-generated constructor stub
     15 
     16     }
     17 
     18     public void calltest() {
     19         ContentResolver contentResolver = getContext().getContentResolver();
     20         Uri uri = Uri
     21                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
     22         Bundle bundle = contentResolver.call(uri, "method", null, null);
     23         String returnCall = bundle.getString("returnCall");
     24         Log.i("main", "-------------->" + returnCall);
     25     }
     26 
     27     //测试方法:向数据库中添加记录。如果之前没有数据库,则会自动创建
     28     public void insert() {
     29         // 使用内容解析者ContentResolver访问内容提供者ContentProvider
     30         ContentResolver contentResolver = getContext().getContentResolver();
     31         ContentValues values = new ContentValues();
     32         values.put("name", "生命贰号");
     33         values.put("address", "湖北");
     34         // content://authorities/person
     35         // http://
     36         Uri uri = Uri
     37                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
     38         contentResolver.insert(uri, values);
     39     }
     40 
     41     //测试方法:删除单条记录。如果要删除所有记录:content://com.example.contentprovidertest01.PersonContentProvider/person
     42     public void delete() {
     43         ContentResolver contentResolver = getContext().getContentResolver();
     44         Uri uri = Uri
     45                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2");//删除id为1的记录
     46         contentResolver.delete(uri, null, null);
     47     }
     48 
     49     //测试方法:根据条件删除记录。
     50     public void deletes() {
     51         ContentResolver contentResolver = getContext().getContentResolver();
     52         Uri uri = Uri
     53                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
     54         String where = "address=?"; 
     55         String[] where_args = { "HK" };
     56         contentResolver.delete(uri, where, where_args);  //第二个参数表示查询的条件"address=?",第三个参数表示占位符中的具体内容
     57     }
     58 
     59     //方法:根据id修改记录。注:很少有批量修改的情况。
     60     public void update() {
     61         ContentResolver contentResolver = getContext().getContentResolver();
     62         Uri uri = Uri
     63                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2");
     64         ContentValues values = new ContentValues();
     65         values.put("name", "李四");
     66         values.put("address", "上海");
     67         contentResolver.update(uri, values, null, null);
     68     }
     69 
     70     //方法:根据条件来修改记录。
     71     public void updates() {
     72         ContentResolver contentResolver = getContext().getContentResolver();
     73         Uri uri = Uri
     74                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/student");
     75         ContentValues values = new ContentValues();
     76         values.put("name", "王五");
     77         values.put("address", "深圳");
     78         String where = "address=?";
     79         String[] where_args = { "beijing" };
     80         contentResolver.update(uri, values, where, where_args);
     81     }
     82 
     83     //测试方法:查询所有记录。如果要查询单条记录:content://com.example.contentprovidertest01.PersonContentProvider/person/1
     84     public void query() {
     85         ContentResolver contentResolver = getContext().getContentResolver();
     86         Uri uri = Uri
     87                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
     88         Cursor cursor = contentResolver.query(uri, null, null, null, null);
     89         while (cursor.moveToNext()) {
     90             Log.i("MyTest",
     91                     "--->>"
     92                             + cursor.getString(cursor.getColumnIndex("name")));
     93         }
     94     }
     95 
     96     //测试方法:根据条件查询所有记录。
     97     public void querys() {
     98         ContentResolver contentResolver = getContext().getContentResolver();
     99         Uri uri = Uri
    100                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
    101         String where = "address=?";
    102         String[] where_args = { "深圳" };
    103         Cursor cursor = contentResolver.query(uri, null, where, where_args,
    104                 null);
    105         while (cursor.moveToNext()) {
    106             Log.i("main",
    107                     "-------------->"
    108                             + cursor.getString(cursor.getColumnIndex("name")));
    109         }
    110     }
    111 
    112 }

    既然ContetProvider实现的是跨应用访问数据,那这个测试类Test.java就应该写在另一个应用程序中才行。于是,我们新建另外一个工程文件ContentResolverTest,在里面添加单元测试,里面的代码其实和上方的Test.java的代码是一模一样的。运行单元测试,依然能在ContentResolverTest中实现对ContentProviderTest01中的CRUD.核心在于:使用应用1中的内容解析者ContentResolver访问应用2中的内容提供者ContentProvider

    现在运行ContentProviderTest01中的单元测试类:

    1、运行insert()方法,实现插入操作。后台打印如下:

    7a532114-aa85-466f-8237-203e2b109eb2

    上图中红框部分表明,这个uri就是代表内容提供者中,person表中,id为1的数据。

    此时,打开file Explorer,进行查看,发现确实多了个文件:

    bb395fd7-353b-4240-86fe-a484de71fa26

    注意:如果SQLite中之前没有mydb.db这个数据库,当实现插入操作时,会自动创建mydb.db这个数据库,并自动创建person表(因为在PersonDao类中执行了getWritableDatabase()方法)。

    现在将上图中的mydb.db导出,然后用SQLiteExpert软件打开,输入sql查询语句,就可以看到person表中的数据了:

    a4b85f6c-ddf9-49a9-8d53-f6e0e32e93f9

    如果再执行insert()方法,又会继续添加一条记录(id是自动增长的)。

    2、运行query()方法,查询所有记录(目前一共两条记录)。后台输出效果如下:

    c5324531-b69a-46f1-be87-295d5e23806b

    经测试,其他方法也都是可以执行的。

    事实证明,新建的另外一个工程文件ContentResolverTest中,在里面运行单元测试,也是可以执行的(单元测试的代码不变,实现的CRUD功能也一模一样),也就是说,能够对ContentProviderTest01中的SQLite进行CRUD操作。例如,运行query()方法,后台输出如下:

    4180318a-9f6a-42ea-95de-cdc3548c7bd2

    这样,我们的目的也就达到了。

    【特别注意】

    需要特别注意的是,代码中uri不要写错了,这些错误一旦发生,很难被发现具体表现在:

    1、清单文件中:

    <provider
        android:name=".内容提供者的类名" 
        android:authorities="包名.内容提供者的类名" >
    </provider>

    如:

            <provider
                android:name=".PersonContentProvider"
                android:authorities="com.example.contentprovidertest01.PersonContentProvider" >
            </provider>

    2、ContentProvider类中的UriMatcher中的uri:

     1     private static final UriMatcher URI_MATCHER = new UriMatcher(
     2             UriMatcher.NO_MATCH);// 默认的规则是不匹配的
     3     private static final int PERSON = 1; // 操作单行记录
     4     private static final int PERSONS = 2; // 操作多行记录
     5     // 往UriMatcher中添加匹配规则。注意,这里面的url不要写错了,我就是因为写错了,半天没调试出来。哎···
     6     static {
     7         // 添加两个URI筛选
     8         URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider",
     9                 "person", PERSONS);
    10         // 使用通配符#,匹配任意数字
    11         URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider",
    12                 "person/#", PERSON);
    13     }

    3、ContentProvider类中的getType()方法里面的代码:

     1     @Override
     2     public String getType(Uri uri) {
     3         int flag = URI_MATCHER.match(uri);
     4         switch (flag) {
     5         case PERSON:
     6             return "vnd.android.cursor.item/person"; // 如果是单条记录,则为vnd.android.cursor.item/
     7                                                         // + path
     8         case PERSONS:
     9             return "vnd.android.cursor.dir/persons"; // 如果是多条记录,则为vnd.android.cursor.dir/
    10                                                         // + path
    11         }
    12         return null;
    13     }

    4、ContentResolver类中的uri:(以insert()方法为例)

     1     //测试方法:向数据库中添加记录。如果之前没有数据库,则会自动创建
     2     public void insert() {
     3         // 使用内容解析者ContentResolver访问内容提供者ContentProvider
     4         ContentResolver contentResolver = getContext().getContentResolver();
     5         ContentValues values = new ContentValues();
     6         values.put("name", "生命贰号");
     7         values.put("address", "湖北");
     8         // content://authorities/person
     9         // http://
    10         Uri uri = Uri
    11                 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person");
    12         contentResolver.insert(uri, values);
    13     }

    【工程文件】

    链接:http://pan.baidu.com/s/1hq7VO12 

    密码:0a49

  • 相关阅读:
    服务端配置scan ip
    父表、子表 主外键关系
    Linux下使用NMON监控、分析系统性能
    Spot light工具集
    linux设置中文环境
    【Android Developers Training】 20. 创建一个Fragment
    【Android Developers Training】 19. 序言:通过Fragments构建动态UI
    【Android Developers Training】 18. 重新创建一个Activity
    【Android Developers Training】 17. 停止和重启一个Activity
    【Android Developers Training】 16. 暂停和恢复一个Activity
  • 原文地址:https://www.cnblogs.com/qianguyihao/p/4108017.html
Copyright © 2011-2022 走看看