1.SQLite
在Android平台上,集成了一个嵌入式关系型数据库—SQLite,SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。
SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么。例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。 但有一种情况例外:定义为INTEGER PRIMARY KEY的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误。 另外, SQLite 在解析CREATE TABLE 语句时,会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息,如下面语句会忽略 name字段的类型信息:
CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))
SQLite可以解析大部分标准SQL语句,如:
查询语句:select * from 表名 where 条件子句 group by 分组字句 having ... order by 排序子句
如:select * from person
select * from person order by id desc
select name from person group by name having count(*)>1
分页SQL与mysql类似,下面SQL语句获取5条记录,跳过前面3条记录
select * from Account limit 5 offset 3 或者 select * from Account limit 3,5
插入语句:insert into 表名(字段列表) values(值列表)。如: insert into person(name, age) values(‘传智’,3)
更新语句:update 表名 set 字段名=值 where 条件子句。如:update person set name=‘传智‘ where id=10
删除语句:delete from 表名 where 条件子句。如:delete from person where id=10
SQLiteOpenHelper抽象类:数库帮助类
要使用数据库,就要先创建出数据库,第二步就要创建表结构。使用SQLiteOperHelper类创建数据库,示例如下:
public class PersonDBOpenHelper extends SQLiteOpenHelper {
public PersonDBOpenHelper(Context context) {
//context 上下文
//name 数据库的名称
//factory 数据库查询结果的游标工厂,使用null会使用系统默认
//version 数据库的版本 >=1
super(context, "person.db", null, 3);
}
/**
* 数据库第一次被创建的时候调用的方法
* @param db 被创建的数据库
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person (id integer primary key autoincrement , name varchar(20), number varchar(20))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
注意:实例化PersonDBOpenHelper类是不会创建数据库的,要使用这个类的对象去调用getWritableDatabase()或者getReadableDatabase()方法之后,数据库才会创建。
数据库路径:data/data/包名/databases/数据库名称
使用SQLiteOpenHelper及系统Api对数据库的增删改查
public class PersonDao2 {
private PersonDBOpenHelper helper;
public PersonDao2(Context context) {
helper = new PersonDBOpenHelper(context);
}
//添加
public boolean add(String name, String phone) {
SQLiteDatabase db = helper.getWritableDatabase();
// db.execSQL("insert into person (name,phone) values (?,?)", new Object[]{name,phone});
ContentValues values = new ContentValues();
values.put("name", name);
values.put("phone", phone);
long id = db.insert("person", null, values);
db.close();
return id != -1;
}
//查找
public boolean find(String name) {
SQLiteDatabase db = helper.getReadableDatabase();
//Cursor cursor = db.rawQuery("select * from peron where name =?", new String[]{name});
Cursor cursor = db.query("person", null, "name=?",
new String[] { name }, null, null, null);
boolean result = cursor.moveToFirst();
cursor.close();
db.close();
return result;
}
/**
* 获取全部的person信息
* @return
*/
public List<Person> findAll(){
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.query("person", null, null, null, null, null, null);
List<Person> persons = new ArrayList<Person>();
while(cursor.moveToNext()){
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
String phone = cursor.getString(cursor.getColumnIndex("phone"));
Person person = new Person(name, phone, id);
persons.add(person);
}
cursor.close();
db.close();
return persons;
}
//更新
public boolean update(String name, String newphone) {
SQLiteDatabase db = helper.getReadableDatabase();
// db.execSQL("update person set phone=? where name=?", new Object[]{newphone,name});
ContentValues values = new ContentValues();
values.put("phone", newphone);
int result = db.update("person", values, "name=?",
new String[] { name });
db.close();
return result>0;
}
//删除
public boolean delete(String name) {
SQLiteDatabase db = helper.getWritableDatabase();
// db.execSQL("delete from person where name=?", new Object[]{ name});
int result = db.delete("person", "name=?", new String[]{name});
db.close();
return result>0;
}
}
使用系统Api的好处就是不用记住sql操作语句也能对数据库进行操作,而且使用系统Api可以更清楚的看到对数据库的各种操作的结果。
数据库的事务
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。使用例子如下(银行转账):
public void testTransection(){
PersonSQLiteOperHelper helper = new PersonSQLiteOpenHelper(getContext());
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try{
db.execSQL("update person set account=account-1000 where name =?",new Object[]{"zhangsan"});
db.execSQL("update person set account=account+1000 where name =?",new Object[]{"wangwu"});
//标记数据库事务执行成功
db.setTransactionSuccessful();调用此方法会在执行到endTransaction() 时提交当前事务,如果不调用此方法会回滚事务
}catch(Exception e){
}finally{
//由事务的标志决定是提交事务,还是回滚事务
db.endTransection();
db.close();
}
}
上面两条SQL语句在同一个事务中执行,如果其他一条没有执行成功或者没有执行,整个事务都不会执行,所以不会出现银行转账的时候出现A给B转钱,A扣了钱,B却没收到钱的尴尬。
2.ListView入门
通过ListView将Person数据库的数据呈现出来。
步骤:1.调用listview的setAdapter()的方法。
2.里面传入一个adatper的适配器,覆写getCount()方法,覆写getView(int postion ,View convertView , ViewGroup parent)方法
关键代码:
public class MainActivity extends Activity {
pirvate List<Person> persons;
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PersonDao dao = new PersonDao(this);
persons = dao.findAll();
lv = (ListView) findViewById(R.id.lv);
lv.setAdapter(new MyAdapter());
}
//默认实现类 simplexxx defaultxxx basexxx
private class MyAdapter extends BaseAdapter{
private static final String TAG = "MyAdapter";
//返回条目数量
public int getCount(){
return persons.size;//条目个数 == 集合的size
}
public Object getItem(int postion){
return null;
}
public long getItemId(int postion){
return 0;
}
public View getView(int position ,View convertView , ViewGroup parent){
Log.i(TAG,"返回view对象,位置:"+postion);
TextView tv = new TextView();
tv.setTextSize(20);
tv.setTextColor(Color.BLACK);
//得到某个位置对应的person对象
Person person = persons.get(postion);
tv.setText(person.toString());
return tv;
}
}
}
3.数据适配器
通过adapter格式化数据显示,原理就是将要显示数据的格式用布局文件设定好,然后通过adapter与ListView关联进行显示,下面将示例将person对象信息显示出来
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dip"
android:gravity="center_vertical"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="id"
android:layout_marginLeft="20dip"
android:textColor="#ff0000"
android:textSize="18sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="名字" />
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="电话" />
</LinearLayout>
</LinearLayout>
重写View getView(int postion ,View convertView , ViewGroup parent)方法
public View getView(int position ,View convertView , ViewGroup parent){
//得到某个位置的person对象
Person person = person.get(position);
View view = View.inflate(MainActivity.this,R.layout.list_item,null);
//一定要在view对象里面寻找孩子的id
TextView tv_id = (TextView) view.findViewById(R.id.tv_id);
tv_id.setText("id:"+person.getId());
TextView tv_name = (TextView) view.findViewById(R.id.tv_name);
tv_id.setText("id:"+person.getName());
TextView tv_number = (TextView) view.findViewById(R.id.tv_number);
tv_id.setText("id:"+person.getNumber());
return view;
}
ArrayAdapter
用于显示比较简单的文本public class MainActivity extends Activity {
pirvate static String[] names = {"功能1","功能2","功能3","功能4","功能5"}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView lv = (ListView) findViewById(R.id.lv);
//设置listview的数据适配器
lv.setAdapter(new ArrayAdapter<String>(this, R.layout.item, R.id.tv, names));
}
}
xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/tv"
android:layout_width="match_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="文本" />
</LinearLayout>
SimpleAdapter
可以显示比较复杂的列表,包括每行显示图片、文字等,但不能对列表进行后期加工(在java代码中加工),也是只是单纯的负责显示(当然可以设计复杂点的布局来显示复杂列表),例如,每行显示不同背景等。
public class MainActivity extends Activity {
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
// 定义一个list集合 集合里面存放的是 每一个条目 item item数据是一个map的类型
List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("icon", R.drawable.ic_menu_login);
map1.put("name", "我是第一个功能");
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("icon", R.drawable.ic_menu_manage);
map2.put("name", "我是第二个功能");
Map<String, Object> map3 = new HashMap<String, Object>();
map3.put("icon", R.drawable.ic_menu_myplaces);
map3.put("name", "我是第三个功能");
Map<String, Object> map4 = new HashMap<String, Object>();
map4.put("icon", R.drawable.ic_menu_notifications);
map4.put("name", "我是第四个功能");
Map<String, Object> map5 = new HashMap<String, Object>();
map5.put("icon", R.drawable.ic_menu_paste);
map5.put("name", "我是第五个功能");
data.add(map1);
data.add(map2);
data.add(map3);
data.add(map4);
data.add(map5);
lv.setAdapter(new SimpleAdapter(this, data, R.layout.layout_item,
new String[] { "icon", "name" }, new int[]{R.id.iv,R.id.tv}));
}
}
xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
显示效果:
4.ContendProvider(内容提供者)
ContentProvider 在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。使用ContentProvider对外共享数据的好处是统一了数据的访问方式。对外共享数据处理步骤:
第1步:需要继承ContentProvider类并重写相关方法
第2步:在AndroidManifest.xml使用<provider>对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
<manifest .... >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name=".PersonContentProvider" android:authorities="cn.itcast.providers.personprovider"/>
</application>
</manifest>
内容提供者的写法:(将自己的应用的数据提供一个访问方式让别的应该也能访问)
public class PersonDBProvider extends ContentProvider {
//定义一个uri的匹配器,用于匹配uri 如果路径不满足条件返回-1
private static UriMatcher mather = new UriMatcher(UriMatcher.NO_MATCH);//如果发现路径不正确 不匹配 返回-1
private static final int INSERT = 1;
private static final int DELETE = 2;
private static final int UPDATE = 3;
private static final int QUERY = 4;
private PersonDBOpenHelper helper;
//定义uri的匹配规则
static{
//添加一组匹配规则
mather.addURI("com.itheima.db.personprovider", "insert", INSERT);
mather.addURI("com.itheima.db.personprovider", "delete", DELETE);
mather.addURI("com.itheima.db.personprovider", "update", UPDATE);
mather.addURI("com.itheima.db.personprovider", "query", QUERY);
}
// content://com.itheima.db.personprovider/insert 添加
// content://com.itheima.db.personprovider/delete 删除
// content://com.itheima.db.personprovider/update 更新
// content://com.itheima.db.personprovider/query 查找
/**
* 当内容提供者被创建的时候调用适合数据的初始化
*/
@Override
public boolean onCreate() {
helper = new PersonDBOpenHelper(getContext());
return false;
}
//查询数据库
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int result = mather.match(uri);
if(result==QUERY){
//返回查询的结果集
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
}else if(result == ID){
int postion = uri.getPath().lastIndexOf("/");
String newid = uri.getPath().substring(postion+1);
System.out.println(uri.getPath());
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("person", projection, "id=?", new String[]{newid}, null, null, sortOrder);
return cursor;
}
else{
throw new IllegalArgumentException("路径不匹配,不能执行查询操作");
}
}
@Override
public String getType(Uri uri) {
int result = mather.match(uri);
if(result==OK){//口令正确
return "vnd.android.cursor.dir/person";
}else if(result == ID){
return "vnd.android.cursor.item/person";
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int result = mather.match(uri);
if(result==INSERT){
SQLiteDatabase db = helper.getWritableDatabase();
db.insert("person", null, values);
db.close();
}else{
throw new IllegalArgumentException("路径不匹配,不能执行插入操作");
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int result = mather.match(uri);
if(result==DELETE){
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("person", selection, selectionArgs);
db.close();
}else{
throw new IllegalArgumentException("路径不匹配,不能执行删除操作");
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int result = mather.match(uri);
if(result==UPDATE){//口令正确
SQLiteDatabase db = helper.getWritableDatabase();
db.update("person", values, selection, selectionArgs);
db.close();
}else{
throw new IllegalArgumentException("路径不匹配,不能执行更新操作");
}
return 0;
}
}
写内容提供者的时候,cursor所对应的数据库是不用关闭的通过内容提供者读取其他程序数据:
public void click(View view){
//得到手机中间人
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.itheima.db.personprovider/query");
Cursor cursor = resolver.query(uri,null,null,null,null);
while(cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex("name"));
String id = cursor.getString(cursor.getColumnIndex("id"));
System.out.println("name="+name+" : "+"id="+id);
}
cursor.close();
}
短信的备份
首先查看源代码,查看packages/providers/TelephoneProvider文件夹中的AndroidManifest.xml的清单文件,再查看源码文件,可以得到短信的内容提供者的Uripublic void readSms(View view) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms/"); // 全部短信的uri
Cursor cursor = resolver.query(uri, null, null, null, null);
while (cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndex("address"));
long date = cursor.getLong(cursor.getColumnIndex("date"));
String body = cursor.getString(cursor.getColumnIndex("body"));
String type = cursor.getString(cursor.getColumnIndex("type"));
System.out.println("地址:" + address + " 时间" + new Date(date)
+ " 内容:" + body);
if ("1".equals(type)) {
System.out.println("接收到的短信");
} else {
System.out.println("发送的短信");
}
System.out.println("------------------");
}
cursor.close();
}
记得添加android.permission.WRITE_SMS权限
向系统短信应用插入一条短信记录
public void click(View view){
new Thread(){
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//插入一条短信
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms/");
ContentValues values = new ContentValues();
values.put("address", "95533");
values.put("date", System.currentTimeMillis());
values.put("type", 1);
values.put("body", "尊敬的用户,你的尾号为558的建行卡,收到汇款人民币888888元,活期余额人民币99999999.52元");
resolver.insert(uri, values);
};
}.start();
}
5.ContentObserver(内容观察者)
观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理要想实现一个内容观察者,必须在ContentResolver的实例中注册一个ContentObserver:
resovler.registContentObserver(Uri uri,notifyForDescendents,observer),参数解释如下:
uri 需要观察的Uri(需要在UriMatcher里注册,否则该Uri也没有意义了)
notifyForDescendents 这是一个布尔类型的数值,表示是否模糊匹配该uri
observer ContentObserver的派生类实例
使用内容观察者监听短信变化
public class MainActivety extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreadte(savedInstanceState);
setContentView(R.layout.activety_main);
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms/");
resolver.registerContentObserver(uri,true,new MyObserver(new Handler()));
}
private class MyObserver extends ContentObserver{
public MyObserver(Handler handler){
super(handler);
}
//当内容观察者观察到是数据库的内容变化了,调用这个方法
//观察到消息邮箱里面有一条数据库内容变化的通知
@Override
public void onChange(boolean selfChange){
super.onChange(selfchange);
Toast.makeText(MainActivity.this,"数据库的内容发生改变了",1).show();
}
}
}
实现原理:当内容发生改变的时候,底层会调用ContentResolver的notifyChange(uri,observer)方法,发送一个数据库改变的消息。我们也可以在实际开发中,将自己的数据库改变的消息通过些原理发送出去。