实例介绍
【实例截图】

【核心代码】
安卓中的Junit
在安卓项目中写Main方法,并不能运行,想要测试某些方法,只能通过Junit测试。
使用方法可参考:
方法一,直接在项目中添加测试类
1. 在AndroidManifest.xml清单文件中添加配置
在根节点中加入:<instrumentation android:targetPackage="要测试的包名" android:name="android.test.InstrumentationTestRunner" />
在application节点中加入:<uses-library android:name="android.test.runner" />
2. 定义一个类继承AndroidTestCase,定义测试方法
AndroidTestCase类中的两个方法
/**
* 在测试用例执行之前 如果我们想进行一些初始化的操作
*/
@Override
protected void setUp() throws Exception {
super.setUp();
//测试前的操作
}
/**
* 在测试用例执行之后调用的方法 ,
* 扫尾,擦屁股的操作
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
//测试后的操作
}
方法二,创建测试项目
通常使用这种方式来测试,因为这样可以不污染要测试的项目。
1. 创建Android Test Project
2. 输入项目名,选择一个已存在的工程,Eclipse可以自动配置Junit环境
完成后,系统会在AndroidManifest.xml清单文件中添加配置。只需要写一个类继承AndroidTestCast即可。
文件操作方式(File、SharedPreferences 、XML、数据库)
File方式
思路:
1、 编写界面,要求,把指定的文件内容,保存到指定的文件中。
2、 添加写文件到SD卡里的权限:WRITE_EXTERNAL_STORAGE
注:写到ROM不需要权限,播放MP3和读SD卡不需要权限
3、 编写逻辑代码:
a) 获取文件名与文件内容
b) 获取按钮,并添加单击事件,在事件方法中判断按了哪个按钮就保存到哪里。
如果是保存到SD卡用: Environment.getExternalStorageDirectory()// 获取SD卡目录, 兼容所有版本,一般情况获取到的目录为:mnt/sdcard
如果是保存到ROM用:context.openFileOutput(name, Context.MODE_WORLD_READABLE Context.MODE_WORLD_WRITEABLE);每一个参数为文件名,第二个参数为读写权限指定。这里的返回值是一个输出流。这里打开的输出流中的文件保存在:/data/data/com.itheima.file/files/上面指定的变量文件名(如abc.txt),这里的其他权限说明:
Context.MODE_PRIVATE;//以私有方式创建abc.txt文件,这个文件只能被这个应用所读写,其他应用程序将读取不到,要想被其他程序读取到则可以用Context.MODE_WORLD_READABLE方式创建
保存文件的两种方式:
1、保存文件到/data/data/cn.itcast.file_2/files/目录下:
①FileOutputStream in = new FileOutputStream("/data/data/cn.itcast.file_2/files/abc.txt");
②OutputStream out = context.openFileOutput(name, Context.MODE_WORLD_READABLE Context.MODE_WORLD_WRITEABLE);
2、保存文件到SD卡(mnt/sdcard/)目录下:
①FileOutputStream out = new FileOutputStream("mnt/sdcard/abc.txt");
②FileOutputStream out = new FileOutputStream(“Environment.getExternalStorageDirectory()” abc.txt");
注意:保存到ROM中的第①种方式和保存到SD卡中的两种方式都没有指定权限,一般写文件时如果没有指定文件的权限,那么这个文件的权限默认继承它的父文件夹的权限
读写权限的总结如下:
使用Context.openFileOutput()方法中的权限有:
l Context.MODE_PRIVATE (私有读写)
l Context.MODE_WORLD_READABLE (全局可读)
l Context.MODE_WORLD_WRITEABLE(全局可写)
l Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE (全局可读写)
这里用 | 或 都可以,因为MODE_WORLD_READABLE = 0x0001
MODE_WORLD_WRITEABLE = 0x0002
二进制为 0000 0000 0000 0001
0000 0000 0000 0010
不管是用或(|)还是用加( ),它们的结果都:0000 0000 0000 0011
l context.MODE_APPEND(私有,以追加的方式写)
通过append的模式打开的文件 ,以后操作这个文件会以追加的方式把新的数据添加到文件的末尾,权限是私有的权限.
如果在别的应用中想以追加的方式写,则可以这样:
FileOutputStream fos = new FileOutputStream("/data/data/com.itheima.file/files/private.txt",true);
注意:在写到SD卡时,有可能SD卡没插或不可用,这时要判断SD卡的状态:Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
查看DDMS透视图中的Permissions(权限)
-rw-rw----
-rw-rw-rw-
-rw-rw-r--
-rw-rw--w-
-rw-rw----
第一个参数:“-”代表是文件,“d”代表是文件夹
后面的内容是linux操作系统的文件访问权限,后面的9个参数 可以把它分为3组
l 前三个参数 当前用户的权限,这三个中的前两个代表读写,第三个如果是x代表可执行的
l 中间三个参数 当前用户所在的组的权限
l 最后面三个参数 所有人的权限
l
x 代表的是可执行
命令:abd shell 进入模拟器手机的根目录
ls –l 显示目录中的内容
chmod是“change mode”的缩写,意为更改文件的读写权限,如:
chmod 777 abc.txt 更改abc.txt 文件的读写权限为全局可读可写
这里的777是怎么来的呢?在Linux系统中,如果权限是rwx,则用111表示,即10进制中的7,如果权限是rw-,则用110表示,即10进制中的6
如果创建的文件夹或目录没有指定权限,那么默认继承父目录的权限
读取文件的两种方式:
1、读取/data/data/cn.itcast.file_2/files/下的文件,可以用两种方式:
FileInputStream in = new FileInputStream("/data/data/cn.itcast.file_2/files/abc.txt");
FileInputStream in = activity.openFileInput(“abc.txt”);//默认读取:"/data/data/cn.itcast.file_2/files/”目录下的文件
注:ContextWrapper.getFilesDir()可获取/data/data/cn.itcast.file_2/files/路径,Activity是ContextWrapper的子类
2、读取SD卡(mnt/sdcard/)目录下的文件:
FileInputStream in = new FileInputStream("mnt/sdcard/abc.txt");
FileInputStream in = new FileInputStream(“Environment.getExternalStorageDirectory()” abc.txt");
具体代码在07.File 项目中。
SharedPreferences方式
SharedPreferences类,它的功能与Properties类似,是保存键值对的,Properties是把键值对保存在.properties文件中的,而SharedPreferences是把键值对保存到xml文件中的,通过这个类操作的xml文件的读写不需要设置权限
通过SharedPreferences方式保存的xml文件默认保存在:/data/data/com.itheima.sp/ 目录下。
实例:如上图,通过点保存后,下次再启动这个程序时自动读取上次保存的数据并显示出来。
核心代码:
1、 获取SharedPreferences对象:Context. getSharedPreferences("config", MODE_PRIVATE);
2、 获取Editor对象,用于存放数据到SharedPreferences对象中:SharedPreferences.edit();
得到Editor之后,通过Editor的putString()和commit()方法向SharedPreferences存放数据和提交数据。
3、 SharedPreferences对象中有数据之后,通过该SharedPreferences对象的getString()取出数据
在Activity中可以调用getPreferences(int mode)方法获得一个SharedPreferences,文件名和Activity名一致
具体代码在:08.SharedPreferences 项目中
XML方式
Ø Pull简介
Ÿ 常见的XML解析方式有三种,DOM、SAX、Pull,Android系统中推荐使用Pull
Ÿ Pull解析器是一个开源的Java项目,Android系统内部解析XML文件均为此种方式,也可用于JavaEE项目
Ÿ Android SDK中已经集成了Pull解析器,无需添加任何jar文件
Ÿ Pull解析器运行方式与SAX类似,提供各种事件的判断
Ÿ 官方网站:http://xmlpull.org/
Ø 使用Pull解析器解析XML文件
Ÿ Xml.newPullParser() 获得解析器
Ÿ parser.setInput(in, "UTF-8") 设置输入流以及编码
Ÿ parser.next() 获取下一个解析事件,得到一个事件代码
Ÿ XmlPullParser中定义了常量来标识各种解析事件
START_DOCUMENT、END_DOCUMENT 、START_TAG 、END_TAG 、TEXT
Ø 使用XmlSerializer写出XML
Ÿ 使用以下方法生成XML,和XML文档顺序类似
startDocument
startTag
attribute
text
endTag
endDocument
Pull方式解析XML
有如下person.xml文件
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<persons>
<person id="1">
<name>范冰冰</name>
<age>31</age>
</person>
<person id="2">
<name>林志玲</name>
<age>38</age>
</person>
<person id="3">
<name>杨幂</name>
<age>26</age>
</person>
</persons>
这里相当于描述了一个List<Person> (persons标签),在这个集体里存放了3个Person对象(person标签),
思路:
1、 根据上页的xml文件写一个Person类。
2、 获取xml文件:通过类加载器加载为一个输入流
3、 获取XmlPullParse解析器:XmlPullParser parser = Xml.newPullParser(); 并为这个解析器设置要解析的xml文件(以流的方式传入参数),并且指定流的解析编码:parser.setInput(in, "UTF-8");
4、 开始解析并生成Person对象
核心代码如下:
List<Person> persons = new ArrayList<Person>();
Person p = null;
XmlPullParser parser = Xml.newPullParser(); // 获取解析器
parser.setInput(in, "UTF-8"); // 设置输入流, 指定码表
for (int type = parser.getEventType(); type != XmlPullParser.END_DOCUMENT; type = parser.next()) { // 定义循环, 开始解析
if (type == XmlPullParser.START_TAG) { // 如果遇到开始标签的事件
if (parser.getName().equals("person")) { // 如果标签名为person
p = new Person(); // 创建Person对象
p.setId(Integer.valueOf(parser.getAttributeValue(0))); // 获取0号属性的值, 赋值给id属性
persons.add(p); // 将对象装入集合
} else if(parser.getName().equals("name")) { // 如果标签名为name
p.setName(parser.nextText()); // 获取下一个文本, 设置给name属性
} else if(parser.getName().equals("age")) { // 如果标签名为age
p.setAge(Integer.valueOf(parser.nextText())); // 获取下一个文本, 赋值给age属性
}
}
}
具体代码在:09.XML 项目中PersonService.java中的loadPersons()方法
通过Person对象生成XML
思路:
1、 获取一个保存了Person对象的List集合
2、 写一个输出流,这个输出流用于把Person写到哪个xml文件中。
3、 获取XmlSerializer 对象:XmlSerializer s = Xml.newSerializer(); 这个对象用于把java对象序列化成XML文件
4、 为这个XmlSerializer对象设置要序列化到哪个xml文件(以输出流的方式传入),并设置输出的编码格式:s.setOutput(out, "UTF-8");
5、 通过XmlSerializer对象把Person序列化为XML
核心代码如下:
XmlSerializer s = Xml.newSerializer();
s.setOutput(out, "UTF-8");
s.startDocument("UTF-8", true); // 开始文档
s.startTag(null, "persons"); // 开始persons标签
for (Person p : persons) {
s.startTag(null, "person"); // 开始person标签
s.attribute(null, "id", p.getId().toString()); // 设置id属性
s.startTag(null, "name"); // 开始name标签
s.text(p.getName()); // 设置文本
s.endTag(null, "name"); // 结束name标签
s.startTag(null, "age"); // 开始age标签
s.text(p.getAge().toString()); // 设置文本
s.endTag(null, "age"); // 结束age标签
s.endTag(null, "person"); // 结束person标签
}
s.endTag(null, "persons"); // 结束persons标签
s.endDocument(); // 结束文档
具体代码在:09.XML 项目中PersonService.java中的savePersons ()方法
Android中的数据库
安卓中使用的数据库都是SQLite数据库,SQLite中的一个数据库就是一个文件
安卓SDK中提供了一套操作SQLite的API。
SQLite特点
Ÿ Android平台中嵌入了一个关系型数据库SQLite,和其他数据库不同的是SQLite存储数据时不区分类型
例如一个字段声明为Integer类型,我们也可以将一个字符串存入,一个字段声明为布尔型,我们也可以存入浮点数。
除非是主键被定义为Integer,这时只能存储64位整数
Ÿ 创建数据库的表时可以不指定数据类型,例如:
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))
CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name)
Ÿ SQLite支持大部分标准SQL语句,增删改查语句都是通用的,分页查询语句和MySQL相同
SELECT * FROM person LIMIT 20 OFFSET 10
SELECT * FROM person LIMIT 10,20
Ÿ 和JDBC访问数据库不同,操作SQLite数据库无需加载驱动,不用获取连接,直接可以使用
获取SQLiteDatabase对象之后通过该对象直接可以执行SQL语句
SQLiteDatabase.execSQL()
SQLiteDatabase.rawQuery()
通过SQLiteOpenHelper.getWritableDatabase();方式创建的数据库默认保存在:/data/data/ 目录下
创建数据库与表的步骤
1、 创建一个类继承SQLiteOpenHelper,这会实现两个方法,且要显示写一个构造函数
构造函数,就是用来调用父类的构造函数来创建或打开数据库用的
2、 在onCreate()方法是数据库第一次创建的时候自动执行的,在这里写上创建表的语句
3、 onUpgrade()方法是数据库版本升级的时候自动调用的
4、 写一个测试类继承AndroidTestCast,调用刚刚创建的类的getWritableDatabase();方法即可以获取到一个可写的数据库对象(SQLiteDatabase)。SQLiteDatabase对象就有增、删、改、查的方法。通过查看getWritableDatabases()方法的源代码可以了解到获取数据库的原理。
具体代码在:10.SQLite项目中的
com.itheima.sqlite包:DBOpenHelper文件、BTest文件。
SQLite的增删改查方式一
---------------使用SQLiteDatabase的execSQL()方法和rawQuery( )方法
具体代码(包括分页查询)在:10.SQLite项目中的:
com.itheima.sqlite包:BOpenHelper文件
com.itheima.sqlite.domain包:Person文件
com.itheima.sqlite.dao包:PersonDaoClassic文件
com.itheima.sqlite.test包:DBTest文件。
关于getWritableDatabase()和getReadableDatabase()的区别
1、 getWritableDatabase()获取到的数据库并不是只能写,它也能读
2、 getReadableDatabase()这也不是说获取只能读的数据库,执行方法时,方法内部会先调用:getWritableDatabase(),如果获取不到可写的数据库才去获取一个只能读的数据库。
SQLite的增删改查方式二
---------------使用SQLiteDatabase的insert( )、update( )、delete( )方法和query( )方法
使用几个增删改查的方法好处是省略了写SQL关键字,只需指定要操作的表名,字段名,值,条件等即可。
具体代码(包括分页查询)在:10.SQLite项目中的:
com.itheima.sqlite包:BOpenHelper文件
com.itheima.sqlite.domain包:Person文件
com.itheima.sqlite.dao包:PersonDao文件
com.itheima.sqlite.test包:DBTest文件。
关于数据库的总结:
-------------总结所用到的类的功能
SQLiteOpenHelper:要想创建或者得到一个数据库,必须要创建一个类继承它,通过创建的个类可以得到一个数据库(即SQLiteDatabase对象),并且可以在这个类的onCreate()生命周期方法里创建数据库表;
SQLiteDatabase: 这个类提供了增删改查的方法。其中查的方法返回的是一个Cursor对象
Cursor:这个类用来保存查数据库时返回的一条一条的记录,该对象提供了获取这些记录的方法
ContentValues:相当于Map,是用于保存键值对,用于在SQLite的增删改查方式二中需要指定的值。
事务管理
Ÿ 在使用SQLite数据库时可以用SQLiteDatabase类中定义的相关方法控制事务
beginTransaction() 开启事务
setTransactionSuccessful() 设置事务成功标记
endTransaction() 结束事务
Ÿ endTransaction()需要放在finally中执行,否则事务只有到超时的时候才自动结束,会降低数据库并发效率
ListView组件
要求,显示数据库中的记录,然后点某条记录的时候显示详细信息。
步骤:
1、 先写主界面,代码在:10.SQLite/res/layout/main.xml ,这个布局用来显示ListView组件
2、 根据数据库中的表(如Person)的记录的字段建立一个10.SQLite/res/layout/item.xml
3、 在Activity中完成逻辑代码,步骤如下:
核心代码:
主要在ListAdpter的:
View view = View.inflate(getApplicationContext(), R.layout.item, null);通过个语句可能把item.xml文件创建为一个View对象,这个View就是所需要的item项。只要把这个View设置上Person对象的数据然后挂到ListView中即完成了任务。即view.findViewByID( ).setText,先找到这个View对象的组件,再把组件上设置好显示的内容即可。
往ListView中添加item项的方式
通过ListView.setAdapter(ListAdapter adapter)方法添加适配器来完成添加item项的操作。
ListAdapter是一个接口,它有常用的三个子类:BaseAdpter、SimpleAdapter、SimpleCursorAdapter
方式一:BaseAdpter
l 在上图的getCount( )法中,想知道一共有多少个Count,用persons.size( )返回,这里的Count有多少个,那么getView方法就会执行多少次,调用一次getView方法就会创建一个View(item项),也就是说你有多少个Person对象就会调用多少次getView()方法,来创建多个View(item项),每创建一个View这个BaseAdapter对象会自动把这个View对象挂到ListView对象当中。每调用一次getView都会传一个Position参数,这个参数从0开始一直到小于getCount。也就是说position记录了每个View(item项)在ListView中的索引(位置),0代表第一个item项,1代表第二个item项。
注意:这个返回的View对象是根据item.xml创建的,也就是说这个View对象包括了item.xm文件中的布局属性和item.xml文件中的各个TextView,EditView,Button等标签节点,在把View返回之前,也就是挂到ListView之前,我们要通过findViewById查找到各个TextView,然后把TextView的文本改成Person对象里的数据,注意这个findViewByid方法必须使用的是View view = View.inflate(getApplicationContext(), R.layout.item, null); 这里的这个view.findViewById()方法。
再注意:当调用TextView.setText( )时,如果我们要显示的是一个Integer对象值,不可以直接把Integer放进来,为如果这样的就不会直接显示这个Integer字符,而是去R文件中找有没有这个值为Integer的ID,找不到就会报错。
l 当点击ListView中的某个item项时,会自动调用getItem( )方法,这时我们可以通过persons.get(position)方法来返回一个Person对象。
l getItemId( )一般就返回posion即可,即返回ListView中item项的索引
具体代码在10.SQLite项目中的:
res/layout/main.xml
res/layout/item.xml
com.itheima.sqlite包:MainActivityByBaseAdapter
方式二:SimpleAdpter
new SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
l 把persons里的每个Person对象里的属性封装成一个Map,即map.put(“name”,person.getName)的形式转换,一个Person变成一个Map然后把所有的Map存到一个List中,这个List就是上面所需要的data参数。
l Resource为item.xml的引用(即R.layout.item),from,为Map里的各个key的值
l to指定要resource参数中对应key的值保存到对应的R.id.tv_name中,to是一个数组,就是用来指定所有的R.id…
具体代码在10.SQLite项目中的:com.itheima.sqlite包:MainActivityBySimpleAdapter
方式三:SimpleCursorAdpter
new SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to)
l 这里所需要的参数跟方式二中的类似,Cursor是一个保存了所有Person记录集的对象,也就是说相当于一条记录就是一个Person对象,在这条记录里有name、id、balance等字段。from就是Cursor对象里记录中的字段名
注意:这里要求的Cursor对象必须要有一个名为“ _id ”的字段,要不然就会报错。注意,只是说这个Cursor对象的记录中必须要有而已,并没有说数据库中一定要有这个“_id”字段,因为在执行查询语句时可以给id起一个别名为“_id”,但是在SQLite中有一个不成名的规定:数据库中的id字段一般名为“_id”,所以最好把数据库里的id直接起名为“_id”。
l layout是一个item.xml文件的引用:R.layout.item
具体代码在10.SQLite项目中的:com.itheima.sqlite包:MainActivityBySimpleCursorAdapter
总结:
l 用第三种方式最方便,因为从数据库里查询数据调用SQLite的rawQuery( )或query( )方法返回的就是一个Cursor。
l 如果数据不是从数据库查询来的,用第二种方式会比较方便一些。
往ListView中的item项增加监听器
增加监听器: ListView. setOnItemClickListener(OnItemClickListener listener)
这里需要一个OnItemClickListener接口的子类
如果使用的是SimpleCursorAdpter:
private class MyOnItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor c = (Cursor) parent.getItemAtPosition(position); // 得到选中的条目上的Cursor, 这个Cursor已经被移动到选中的位置了
Person p = new Person(c.getInt(0), c.getString(1), c.getInt(2)); // 获取Cursor中的数据, 组装成对象
Toast.makeText(getApplicationContext(), p.toString(), 0).show(); // 弹出
}
}
在代码中选择AdapterView然后按Ctrl T可以查看AdapterView的类继承结果,可以看到的父类是View,它还有一个子类ListView,其实这个prarent参数就是Item项的容器,即ListView对象。
因为SimpleCursorAdpter生成的item项是通过Cursor对象生成的,所以parent.getItemAtPosition(position)返回的当然也就是一个Cursor对象。
而view即所点的LinearLayout(Item项)
position为所点击的是第几个
id 一般这个值和position是一样的
具体代码在10.SQLite项目中的:com.itheima.sqlite包:MainActivityBySimpleCursorAdapter
如果使用的是SimpleAdpter:
private class MyOnItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Map<String, Object> map = (Map) parent.getItemAtPosition(position); // 得到选中的条目上的Map
Person p = new Person((Integer)map.get("id"), (String)map.get("name"), (Integer)map.get("balance")); // 获取Map中的数据, 组装成对象
Toast.makeText(getApplicationContext(), p.getName(), 0).show(); // 弹出
}
}
因为SimpleAdpter生成的item项是通过Map对象生成的,所以parent.getItemAtPosition(position)返回的当然也就是一个Map对象。
具体代码在10.SQLite项目中的:com.itheima.sqlite包:MainActivityBySimpleAdapter
如果使用的是BaseAdpter:
private class MyOnItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Person p = (Person) parent.getItemAtPosition(position); // 得到选中的条目上的Person
Toast.makeText(getApplicationContext(), p.toString(), 0).show(); // 弹出
}
}
parent.getItemAtPosition(position)的返回值是什么呢?Adpter接口中的getItem( )方法返回的对象,因为之前写的时候返回的是Person所以这里的返回值就是Person,其实使用SimpleAdpter时,内部的getItem()方法返回的就是Map,而使用SimpleCursorAdpter是getItem()返回的是Cursor对象。
具体代码在10.SQLite项目中的:com.itheima.sqlite包:MainActivityByBaseAdapter
ArrayAdapater
ListView.setAdapter(new ArrayAdapter<String>(this, R.layout.me_item, R.id.fav_title, String[ ]{}));
这种方式适合只填充Item里的一个值,如上面只能给item项中的R.id.fav_title的文本框赋值。
内容提供者(ContentProvider)
数据库文件默认是只能在本项目(应用)中被读写,在其他应用中是不能读写的,如果想给外面的应用读写,这时就可以用内容提供者。
作用就是共享数据,手机中的各种应用,基本上都是通过内容提供者把数据共享出来的。比如安装了一个QQ通讯录,能读到手机里的短信,那是因为手机的短信程序里有内容提供者,提供了短信的获取。
编写ContentProvider
在AndroidManifest.xml文件的Application节点中配置:
在清单文件的<application>节点下进行配置,<provider>标签中需要指定name和authorities属性
name为类名,包名从程序Package开始,以“.”开始
authorities:是访问Provider时的路径,要唯一
URI代表要操作的数据,由scheme、authorites、path三部分组成
content://cn.itcast.sqlite.provider/person
scheme:固定为content,代表访问内容提供者
authorites:<provider>节点中的authorites属性
path:程序定义的路径,可根据业务逻辑定义
编写一个类继承ContentProvider,必然实现以下几个方法
至此一个简单的内容提供者就写好,在其他的应用中怎么访问这个内容提供者呢?
通过ContentResolver对象访问内容提供者
在其他应用中写一个测试类,然后:
public class TestProvider extends AndroidTestCase {
public void invokeProvider(){
ContentResolver resolver = getContext().getContentResolver();
Uri uri = Uri.parse("content://daizhenliang");
ContentValues values = new ContentValues();
resolver.insert(uri, values);
resolver.delete(uri, null, null);
resolver.update(uri, null, null, null);
resolver.query(uri, null, null, null, null);
}
}
当执行resolver的增、删、改、查,就会自动调用相应"content://daizhenliang"这个提供者中的增删改查方法,并且这里的参数会原封不动的传过去
通过"content://daizhenliang/person"指定了要访问person表,如这时调用resolver.insert(uri, values)方法,则会自动调用ContentProvider中的public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 方法,这里的Uri就是resolver传过来的Uri,要进行查询操作,肯定要知道操作的是哪个表,这时就要对Uri进行解析,这时可以用UriMatcher工具类,代码如下:
UriMatche matcher = new UriMatcher(UriMatcher.NO_MATCH); // 匹配器
matcher.addURI("com.itheima.sqlite.provider", "person", 1); // 添加可以匹配的URI
matcher.addURI("com.itheima.sqlite.provider", "student", 2);
这样就设置好了一个匹配器,当调用matcher.match(uri)时,就会和刚刚配置的Uri进行匹配,如果匹配到了person,则此方法返回1,如果匹配到了student,则返回2,通过这个返回值我们就直到要操作的是哪个表了。
matcher.addURI("com.itheima.sqlite.provider", "person/#", 2);//这里的#代表要匹配一个数字,但是这个数字还不知道是多少,就可以用这种试匹配。通过这种匹配,我们就可以在查询的时候在URI中指定要查的条件(Id),URI在ContentResolver中传到ContentProvicer中时就要解析出这个ID,这时可以用:long id = ContentUris.parseId(uri); 这个工具可以方便地解析出URI中的id。 具体使用在:10.SQLite项目中:com.itheima.sqlite.provider包:SQLiteProvider文件中
关于ContentProvider中的getType()方法的使用:
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI("com.itheima.sqlite.provider", "person", 1); 不带id说明操作的是多条数据
matcher.addURI("com.itheima.sqlite.provider", "person/#", 2); 带id说明操作的是单条记录
public String getType(Uri uri) {
switch (matcher.match(uri)) {
case 2:
return "vnd.android.cursor.item/person"; item代表是单条记录
case 1:
return "vnd.android.cursor.dir/person"; dir代表是多条记录
default:
throw new IllegalArgumentException("无法匹配的URI: " uri);
}
}
其实这个getType方法主要就是用来测试Uri有没有写错,就是说在进行增删改查之前,先测一下Uri对不对。
关于ContentProvider与ContentResolver进行增、删、改、查的使用具体代码在:
10.SQLite项目中:com.itheima.sqlite.provider包:SQLiteProvider
06.Test项目中:com.itheima.junit.test包:ProviderTest
总结:
使用ContentResolver进行数据库的增删改查的调用过程:
ContentObserver内容观察者
用于观察ContentProvider的变化。
而ContentProvider的增、删、改、查是通过ContentResolver对象进行调用的,所以内容观察者是注册在ContentResolver对象中的,代码如下:
ContentResolver.registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
uri:要观察的ContentProvider的uri,如:content://com.itheima.sqlite.provider
notifyForDescendents:如果为true则uri中的后代也观察,如:content://com.itheima.sqlite.provider/person
content://com.itheima.sqlite.provider/person/#
content://com.itheima.sqlite.provider/student
observer:当观察者观察的Uri发生改变时就会自动执行observer对象的onChange()方法。
注:在ContentProvider的增、删、改、查操作后要加上:getContext().getContentResolver().notifyChange(uri, null);// 通知监听这个ContentProvider的ContentObserver,作用就是通知观察者说ContentProvider发生改变了。
观察者的项目没有修改过时不要发布多次,比如发布了3次,那么在Uri发生改变时,就会通知观察者3次,也就是说会执行3次onChange()方法。如果观察者项目修改过然后发布1次就不会出现监听多次的情况。
练习使用观察者:
1、 写一个内容提供者项目
2、 写一个观察者项目,注册一个观察者观察1中的内容提供者,在观察者的onChange中输出一个语句。
3、 写一个测试项目,在这里用ContentResolver操作内容提供者,查看观察者的输出语句有没有执行
总结:
观察者是注册在ContentResovler对象上的
Content有一个getContentResolver()的方法获取ContentResovler对象。
所以只要在能拿到Content对象的类里就能注册内容观察者。
能获取Context的类:
Activity、Application、Service就是Context的子类
AndroidTestCase. mContext 、AndroidTestCase. Context()
ContentProvider. getContext( )
实例:监听手机中的短信
手机中的各种应用,基本上都是通过内容提供者把数据共享出来的。比如安装了一个QQ通讯录,能读到手机里的短信,那是因为手机的短信程序里有内容提供者,提供了短信的获取。所以就可以通过一个观察者来监听短信。
要想使用观察者监听手机中的短信,必须要知道authorites。
查看短信程序的项目:
短信的发送相关界面是这个项目:data/data/ ,而短信的数据是在:data/data/
打开mmssms.db数据库:
怎么知道短信程序的authorites呢?通过查它的源代码就可以知道,但是在SDK里是没有发短信的源代码的,在这个网站:https://github.com/android里有所有的安卓手机内置程序的源代码,而要下载这些源代码需要使用Git工具,这个工具的下载网站:http://code.google.com/p/msysgit/
注意:
GIT1.7.7安装后不能卸载,可以用其他版本覆盖后再卸载。
使用GIT时不要使用中文目录,否则GIT GUI会报错无法启动。删除C盘中.gitconfig文件可以解决。
1、 首先安装Git工具
2、
3、
这个界面就是要把网上的代码克隆下来的,这个代码的克隆地址是什么呢?打开 :https://github.com/android,搜telphonyprovider如下图:
下载到之后:
然后找到这个:
然后查看提供者的源码:
在源代码中查找:
使用观察者查数据的时候,
由于具体的查询代码是写在ContentProvider中的,所以我们在查的时候可以用查询结果Cursor.getColumnNames()查看都查到了哪些字段,然后再获取我们所需字段的值。
需要的字段如下:
注:有时候发一短信能收到多条通知,是因为我们监听了authorites的后代。
代码如下:
具体代码在:11.ContentObserver项目:MainActivity类中
读取手机联系人:
电话联系人的保存位置
可以看到这个联系人数据库在外部的应用是不能访问的,所以只能通过内容提供者来访问了
要使用内容提供者,首先要知道内容提供者的Uri,通过查看源代码就能知道:
先看清单文件,再看src源代码:
查看清单后就能知道主机地址:content://com.android.contacts/xxx ,下一步就是要查xxx有哪些,查我们需要的,这时就要看源代码了(ContactsProvider2.java)
查看.java中配置的uri信息,可以直接搜索 static { 这样就比较快:
1、 找到:matcher.addURI(ContactsContract.AUTHORITY, "raw_contacts", RAW_CONTACTS); 那么要获取raw_contact表就可以使用:content://com.android.contacts/raw_contact 这个URI
2、 找到:matcher.addURI(ContactsContract.AUTHORITY, "data", DATA); data表
data表中的mimetype_id字段与mimetypes表中的_id进行了关联,所以在查到data表的Cursor后,这个Cursor里就已经保存有mimetype这个字段了,可以直接获取这个字段的值
获取联系人步骤:
1、 增加权限
小贴士
感谢您为本站写下的评论,您的评论对其它用户来说具有重要的参考价值,所以请认真填写。
- 类似“顶”、“沙发”之类没有营养的文字,对勤劳贡献的楼主来说是令人沮丧的反馈信息。
- 相信您也不想看到一排文字/表情墙,所以请不要反馈意义不大的重复字符,也请尽量不要纯表情的回复。
- 提问之前请再仔细看一遍楼主的说明,或许是您遗漏了。
- 请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。
关于好例子网
本站旨在为广大IT学习爱好者提供一个非营利性互相学习交流分享平台。本站所有资源都可以被免费获取学习研究。本站资源来自网友分享,对搜索内容的合法性不具有预见性、识别性、控制性,仅供学习研究,请务必在下载后24小时内给予删除,不得用于其他任何用途,否则后果自负。基于互联网的特殊性,平台无法对用户传输的作品、信息、内容的权属或合法性、安全性、合规性、真实性、科学性、完整权、有效性等进行实质审查;无论平台是否已进行审查,用户均应自行承担因其传输的作品、信息、内容而可能或已经产生的侵权或权属纠纷等法律责任。本站所有资源不代表本站的观点或立场,基于网友分享,根据中国法律《信息网络传播权保护条例》第二十二与二十三条之规定,若资源存在侵权或相关问题请联系本站客服人员,点此联系我们。关于更多版权及免责申明参见 版权及免责申明
网友评论
我要评论