1. 理论概述
Android数据存储方式:
- SharedPreferences存储
- 手机内部文件存储
- 手机外部文件存储
- sqlite数据库存储
- 远程服务器存储
2. 数据存储开发
2.1 SharedPreferences存储
说明
- SP存储专门用来存储一些单一的小数据
- 存储数据的类型:boolean,float,int,long,String
- 数据保存的路径:/data/data/packageName/shared_prefs/yyy.xml
- 可以设置数据只能是当前应用读取,而别的应用不可以
- 应用卸载时会删除数据
相关API
- SharedPrefences:对应sp文件的接口
context.getSharedPreferences(String name,int mode)
:得到SP对象
- name:文件名(不带.xml)
- mode:生成的文件模式(是否是私有的,即其它应用是否可以访问)
Editor sp.edit()
:得到Editor对象Xxx sp.getXxx(name, defaultValue)
:根据name得到对应的数据
- Editor:能更新SP文件的接口
Editor put(name, value)
:保存一个键值对,没有真正保存到文件中Editor remove(name)
commit()
:提交,数据真正保存到文件中
示例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/et_sp_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="存储的key" />
<EditText
android:id="@+id/et_sp_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="存储的value" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="save"
android:text="保 存" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="read"
android:text="读 取" />
</LinearLayout>
</LinearLayout>
activity_sp.xml
/**
* 测试sp存储的界面
*/
public class SpActivity extends Activity {
private EditText et_sp_key;
private EditText et_sp_value;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sp);
et_sp_key = (EditText) findViewById(R.id.et_sp_key);
et_sp_value = (EditText) findViewById(R.id.et_sp_value);
//1. 得到sp对象
sp = getSharedPreferences("atguigu", Context.MODE_PRIVATE);
}
public void save(View v) {
//2. 得到editor对象
SharedPreferences.Editor edit = sp.edit();
//3. 得到输入的key/value
String key = et_sp_key.getText().toString();
String value = et_sp_value.getText().toString();
//4. 使用editor保存key-value
edit.putString(key, value).commit();
//5. 提示
Toast.makeText(this, "保存完成!", 0).show();
}
public void read(View v) {
//1. 得到输入的key
String key = et_sp_key.getText().toString();
//2. 根据key读取对应的value
String value = sp.getString(key, null);
//3. 显示
if(value==null) {
Toast.makeText(this, "没有找到对应的value", 0).show();
} else {
et_sp_value.setText(value);
}
}
}
SpActivity.java
package com.atguigu.l04_datastorage;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 测试sp存储
public void onClickSP(View v) {
startActivity(new Intent(this, SpActivity.class));
}
// 测试手机内部文件存储
public void onClickIF(View v) {
startActivity(new Intent(this, IFActivity.class));
}
// 测试手机外部文件存储
public void onClickOF(View v) {
startActivity(new Intent(this, OFActivity.class));
}
public void onClickDB(View v) {
}
public void onClickNW(View v) {
}
}
MainActivity.java
2.2 手机内部file存储
说明
- 应用运行需要的一些较大的数据或图片可以用文件保存在手机内部
- 文件类型:任意
- 数据保存的路径:/data/data/projectPackage/files/
- 可以设置数据只能是当前应用读取,而别的应用不可以
- 应用卸载时会删除此数据
相关API
- 读取文件
FileInputStream fis = openFileInput("logo.png");
- 保存文件
FileOutputStream fos = openFileOutput("logo.png",MODE_PRIVATE);
- 得到files文件夹对象
File filesDir = getFilesDir();
- 操作asserts下的文件
- 得到Assetmanager:
context.getAssets();
- 读取文件:
InputStream open(filename);
- 加载图片文件
-
Bitmap BitmapFactory.decodeFile(String pathName)
//.bmp/.png/.jpg
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="1. 将asserts下的logo.png保存到手机内部\n2. 读取手机内部图片文件显示"
android:textColor="#ff0000"
android:textSize="15sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/btn_if_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="save"
android:text="保 存" />
<Button
android:id="@+id/btn_if_read"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="read"
android:text="读 取" />
</LinearLayout>
<ImageView
android:id="@+id/iv_if"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</LinearLayout>
activity_if.xml
package com.atguigu.l04_datastorage;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 测试手机内部文件存储
*/
public class IFActivity extends Activity {
private ImageView iv_if;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_if);
iv_if = findViewById(R.id.iv_if);
}
public void save(View view) throws IOException {
//1.得到InputStream ->读取assets下的logo.png
//得到AssetManager
AssetManager manager = getAssets();
//读取文件
InputStream is = manager.open("logo.png");
//2.得到OutputStream->/data/data/packageName/files/logo.png
FileOutputStream fos = openFileOutput("logo.png", Context.MODE_PRIVATE);
//3.边读边写
byte[] buffer = new byte[1024];
int len = 1;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
//4.提示
Toast.makeText(this,"保存完成",Toast.LENGTH_SHORT).show();
}
// /data/data/packageName/files/logo.png
public void read(View view){
//1. 得到图片文件的路径 /data/data/packageName/files
String filesPath = getFilesDir().getAbsolutePath();
String imagePath = filesPath + "/logo.png";
//2.读取加载图片文件得到bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
//3.将其设置到imageView中显示
iv_if.setImageBitmap(bitmap);
}
}
IFActivity.java
2.3 手机外部file存储
说明
- 应用运行用到的数据文件(如图片)可以保存到sd卡中
- 文件类型:任意
- 数据保存的路径:
- 路径1:/storage/sdcard/Android/data/packageName/files/
- 路径2:/storage/sdcard/xx/
- 路径1:其它应用可以访问,应用卸载时删除
- 路径2:共它应用可以访问,应用卸载时不会删除
- 必须保证sd卡挂载在手机上才能读写,否则不能操作
相关API
- Environment:操作SD卡的工具类
- 得到SD卡的状态:Environment.getExternalStorageState()
- 提到SD卡的路径:Environment.getExternalStorageDirectory()
- SD卡可读写的挂载状态值:Enviroment.MEDIA_MOUNTED
- context.getExternalFilesDir():
- 得到/mnt/sdcard/Android/data/package_name/files/xxx.txt
- 操作SD卡的权限:
- android.permission.WRITE_EXTERNAL_STORAGE
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/et_of_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="存储的文件名" />
<EditText
android:id="@+id/et_of_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="存储的文件内容" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="save"
android:text="保 存" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="read"
android:text="读 取" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="save2"
android:text="保 存2" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="read2"
android:text="读 取2" />
</LinearLayout>
</LinearLayout>
activity_of.xml
package com.atguigu.l04_datastorage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
/**
* 测试手机外部文件存储
*/
public class OFActivity extends Activity {
private EditText et_of_name;
private EditText et_of_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_of);
et_of_name = (EditText) findViewById(R.id.et_of_name);
et_of_content = (EditText) findViewById(R.id.et_of_content);
}
public void save(View v) throws IOException {
//1. 判断sd卡状态, 如果是挂载的状态才继续, 否则提示
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//2. 读取输入的文件名/内容
String fileName = et_of_name.getText().toString();
String content = et_of_content.getText().toString();
//3. 得到指定文件的OutputStream
//1).得到sd卡下的files路径
String filesPath = getExternalFilesDir(null).getAbsolutePath();
//2).组成完整路径
String filePath = filesPath+"/"+fileName;
//3). 创建FileOutputStream
FileOutputStream fos = new FileOutputStream(filePath);
//4. 写数据
fos.write(content.getBytes("utf-8"));
fos.close();
//5. 提示
Toast.makeText(this, "保存完成", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "sd卡没有挂载", Toast.LENGTH_SHORT).show();
}
}
public void read(View v) throws Exception {
// 1. 判断sd卡状态, 如果是挂载的状态才继续, 否则提示
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// 2. 读取输入的文件名
String fileName = et_of_name.getText().toString();
// 3. 得到指定文件的InputStream
// 1).得到sd卡下的files路径
String filesPath = getExternalFilesDir(null).getAbsolutePath();
// 2).组成完整路径
String filePath = filesPath + "/" + fileName;
// 3). 创建FileInputStream
FileInputStream fis = new FileInputStream(filePath);
// 4. 读取数据, 成String
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while((len=fis.read(buffer))!=-1) {
baos.write(buffer, 0, len);
}
String content = baos.toString();
// 5. 显示
et_of_content.setText(content);
} else {
Toast.makeText(this, "sd卡没有挂载", Toast.LENGTH_SHORT).show();
}
}
// /storage/sdcard/atguigu/xxx.txt
public void save2(View v) throws IOException {
//1. 判断sd卡状态, 如果是挂载的状态才继续, 否则提示
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//2. 读取输入的文件名/内容
String fileName = et_of_name.getText().toString();
String content = et_of_content.getText().toString();
//3. 得到指定文件的OutputStream
//1). /storage/sdcard/
String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
//2). /storage/sdcard/atguigu/(创建文件夹)
File file = new File(sdPath+"/atguigu");
if(!file.exists()) {
file.mkdirs();//创建文件夹
}
//3). /storage/sdcard/atguigu/xxx.txt
String filePath = sdPath+"/atguigu/"+fileName;
//4). 创建输出流
FileOutputStream fos = new FileOutputStream(filePath);
//4. 写数据
fos.write(content.getBytes("utf-8"));
fos.close();
//5. 提示
Toast.makeText(this, "保存完成", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "sd卡没有挂载", Toast.LENGTH_SHORT).show();
}
}
public void read2(View v) throws Exception {
// 1. 判断sd卡状态, 如果是挂载的状态才继续, 否则提示
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// 2. 读取输入的文件名
String fileName = et_of_name.getText().toString();
// 3. 得到指定文件的InputStream
String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
String filePath = sdPath+"/atguigu/"+fileName;
FileInputStream fis = new FileInputStream(filePath);
// 4. 读取数据, 成String
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while((len=fis.read(buffer))!=-1) {
baos.write(buffer, 0, len);
}
String content = baos.toString();
fis.close();
// 5. 显示
et_of_content.setText(content);
} else {
Toast.makeText(this, "sd卡没有挂载", Toast.LENGTH_SHORT).show();
}
}
}
OFActivity.java
比较内部文件与外部文件存储?
- 存储空间的大小
- 是否是私有的
- 应用卸载是否自动删除
2.4 SQLite数据库存储
说明
- 应用运行需要保存一系列有一定结构的数据,比如说公司员工信息
- 文件类型:db
- 数据保存的路径 :/data/data/projectPackage/databases/xxx.db
- 默认情况下其它应用不能访问,当前应用可以通过ContentProvider提供其它应用操作
- 应用卸载时会删除此数据
SQLite数据库
SQLite(http://www.sqlite.org),是一款轻型的关系型数据库服务器,移动设备的数据库存储都使用SQLite,它的特点:
- 安装文件小:最小只有几百K,Android系统已经安装
- 支持多操作系统:Android,WP,IOS,Windows,Linux等
- 支持多语言:比如Java、PHP、C#等
- 处理速度快:处理速度比Mysql,Oracle,SQLServer都要快(数据量不是特别大)
- SQLite中的一个数据库就是一个.db文件(本质上.db的后缀都可以不指定)
SQLite数据库命令行
- adb shell 进入系统根目录
- cd data/data/…/databases:进入包含数据库文件的文件夹下
- sqlite3 contacts2.db:使用sqlite3命令连接指定的数据库文件,进入连接模式
- help:查看命令列表
- tables:查看所有表的列表
- 执行 insert/delete/update/select语句
- exit:退出数据库连接模式
- Ctrl+C:直接退出shell模式
数据类型
SQLite支持的数据类型与MySQL相似,常用的数据类型
- INT/INTEGER:整数
- FLOAT/DOUBLE:小数
- CHAR/VARCHAR/TEXT:字符串文本
- BLOB:文件
- DATE/DATETIME:日期/日期时间
SQLite建表
SQLite操作数据库的sql语句基本与mysql一样,但需要注意下面2点:
- 最大的不同在于创建表时可以不用指定字段类型,sqlite可以适时的自动转换,但除varchar类型外最好指定类型
- sqlite中的主键名称建议使用_id
create table employee(
_id integer primary key autoincrement,/*主键,自增长*/
name varchar, /* 字符串*/
salary double, /* 小数 */
birthday date /* 日期,可直接插入日期格式字符串*/
)
sqlite建表
SQLite的CRUD语句
/*插入*/
INSERT INTO employee(name,salary,birthday) VALUES('Tom',8000,'1988-09-21');
/*删除*/
DELETE FROM employee WHERE _id=2
/*更新*/
UPDATE employee SET name='Jack',salary=salary+1000 WHERE _id=1
/*查找*/
SELECT * FROM employee WHERE _id=3
View Code
相关API
- SQLiteOpenHepler:数据库操作的抽象帮助类
- SQLiteOpenHelper(Context context,String name, CursorFactory
- factory, int version):构造方法,指定数据库文件名和版本号
- abstract void onCreate(SQLiteDatabases db):用于创建表
- abstract void onUpgrade():用于版本更新
- SqliteDatabase getReadableDatabase():得到数据库连接
- sqliteDatabase:代表与数据库的连接的类
- long insert():用于执行insert SQL,返回id值
- int update():用于执行update SQL
- int delete():用于执行delete SQL
- Cursor query():用于执行select SQL,返回包含查询结果数据的Cursor
- void execSql(sql):执行sql语句
- beginTransaction():开启事务
- setTransactionSuccessful():设置事务是成功的
- endTransaction():结束事务,可能提交事务或回滚事务
- openDatabase(String path, CursorFactory factory, int flags):得到数据库连接
- Cursor:包含所有查询结果记录的结果集对象(光标,游标)
- int getCount():匹配的总记录数
- boolean moveToNext():将游标移动到下一条记录的前面
- Xxx getXxx(columnIndex):根据字段下标得到对应值
- int getColumnIndex(columnname):根据字段名得到对应的下标
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testCreateDB"
android:text="Create DB" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testUpdateDB"
android:text="Update DB" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testInsert"
android:text="Insert" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testUpdate"
android:text="Update" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testDelete"
android:text="Delete" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testQuery"
android:text="query" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="testTransaction"
android:text="Test Transaction" />
</LinearLayout>
activity_db.xml
package com.atguigu.l04_datastorage;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
/**
* 测试Sqlite数据库存储
*
*/
public class DBActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_db);
}
/*
* 创建库
*/
public void testCreateDB(View v) {
DBHelper dbHelper = new DBHelper(this, 1);
//获取连接
SQLiteDatabase database = dbHelper.getReadableDatabase();
Toast.makeText(this, "创建数据库", 0).show();
}
/*
* 更新库
*/
public void testUpdateDB(View v) {
DBHelper dbHelper = new DBHelper(this, 2);
//获取连接
SQLiteDatabase database = dbHelper.getReadableDatabase();
Toast.makeText(this, "更新数据库", 0).show();
}
/*
* 添加记录
*/
public void testInsert(View v) {
//1. 得到连接
DBHelper dbHelper = new DBHelper(this, 2);
SQLiteDatabase database = dbHelper.getReadableDatabase();
//2. 执行insert insert into person(name, age) values('Tom', 12)
ContentValues values = new ContentValues();
values.put("name", "Tom");
values.put("age", 12);
long id = database.insert("person", null, values);
//3. 关闭
database.close();
//4. 提示
Toast.makeText(this, "id="+id, 1).show();
}
/*
* 更新
*/
public void testUpdate(View v) {
DBHelper dbHelper = new DBHelper(this, 2);
SQLiteDatabase database = dbHelper.getReadableDatabase();
//执行update update person set name=Jack, age=13 where _id=4
ContentValues values = new ContentValues();
values.put("name", "jack");
values.put("age", 13);
int updateCount = database.update("person", values , "_id=?", new String[]{"4"});
database.close();
Toast.makeText(this, "updateCount="+updateCount, 1).show();
}
/*
* 删除
*/
public void testDelete(View v) {
// 1. 得到连接
DBHelper dbHelper = new DBHelper(this, 2);
SQLiteDatabase database = dbHelper.getReadableDatabase();
// 2. 执行delete delete from person where _id=2
int deleteCount = database.delete("person", "_id=2", null);
// 3. 关闭
database.close();
// 4. 提示
Toast.makeText(this, "deleteCount=" + deleteCount, 1).show();
}
/*
* 查询
*/
public void testQuery(View v) {
// 1. 得到连接
DBHelper dbHelper = new DBHelper(this, 2);
SQLiteDatabase database = dbHelper.getReadableDatabase();
// 2. 执行query select * from person
Cursor cursor = database.query("person", null, null, null, null, null, null);
//cursor = database.query("person", null, "_id=?", new String[]{"3"}, null, null, null);
//得到匹配的总记录数
int count = cursor.getCount();
//取出cursor中所有的数据
while(cursor.moveToNext()) {
//_id
int id = cursor.getInt(0);
//name
String name = cursor.getString(1);
//age
int age = cursor.getInt(cursor.getColumnIndex("age"));
Log.e("TAG", id+"-"+name+"-"+age);
}
// 3. 关闭
cursor.close();
database.close();
// 4. 提示
Toast.makeText(this, "count=" + count, 1).show();
}
/*
* 测试事务处理
* update person set age=16 where _id=1
* update person set age=17 where _id=3
*
* 一个功能中对数据库进行的多个操作: 要就是都成功要就都失败
* 事务处理的3步:
* 1. 开启事务(获取连接后)
* 2. 设置事务成功(在全部正常执行完后)
* 3. 结束事务(finally中)
*/
public void testTransaction(View v) {
SQLiteDatabase database = null;
try{
DBHelper dbHelper = new DBHelper(this, 2);
database = dbHelper.getReadableDatabase();
//1. 开启事务(获取连接后)
database.beginTransaction();
//执行update update person set age=16 where _id=1
ContentValues values = new ContentValues();
values.put("age", 16);
int updateCount = database.update("person", values , "_id=?", new String[]{"1"});
Log.e("TAG", "updateCount="+updateCount);
//出了异常
boolean flag = true;
if(flag) {
throw new RuntimeException("出异常啦!!!");
}
//执行update update person set age=17 where _id=3
values = new ContentValues();
values.put("age", 17);
int updateCount2 = database.update("person", values , "_id=?", new String[]{"3"});
Log.e("TAG", "updateCount2="+updateCount2);
//2. 设置事务成功(在全部正常执行完后)
database.setTransactionSuccessful();
} catch(Exception e) {
e.printStackTrace();
Toast.makeText(this, "出异常啦!!!", 1).show();
} finally {
//3. 结束事务(finally中)
if(database!=null) {
database.endTransaction();
database.close();
}
}
}
}
DBActivity
package com.atguigu.l04_datastorage;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
/**
* 数据库操作的帮助类
*/
public class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context,int version) {
super(context, "atguigu.db", null, version);
}
/**
* 什么时候才会创建数据库文件?
* 1). 数据库文件不存在
* 2). 连接数据库
*
* 什么时候调用?
* 当数据库文件创建时调用(1次)
* 在此方法中做什么?
* 建表
* 插入一些初始化数据
*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.e("TAG", "DBHelper onCreate()");
//建表
String sql = "create table person(_id integer primary key autoincrement, name varchar,age int)";
db.execSQL(sql);
//插入一些初始化数据
db.execSQL("insert into person (name, age) values ('Tom1', 11)");
db.execSQL("insert into person (name, age) values ('Tom2', 12)");
db.execSQL("insert into person (name, age) values ('Tom3', 13)");
}
//当传入的版本号大于数据库的版本号时调用
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.e("TAG", "DBHelper onUpgrade()");
}
}
DBHelper.java
Android中的Junit测试
1. 添加配置信息
<application>
<!-- 使用android测试包 -->
<uses-library android:name="android.test.runner" />
</application>
<!-- android:targetPackage的值应与manifest的package的值一致 -->
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.atguigu.app04_sqlite" />
View Code
2. 编写测试类
class StudentTest extends AndroidTestCase
2.5 远程服务器存储
说明
- 对于联网的APP来说,可能需要通过请求向服务器提交请求数据,也可能需要从服务器端获取数据显示
- 如何编码实现客户端与服务器端的交互呢?
- JDK内置的原生API
HttpUrlConnection
- Android内置的包装API
HttpClient
- 异步网络请求框架
- Volley
- Xutils
- 注意:
- 访问网络,需要声明权限:android.permission.INTERNET
- 访问网络的程序必须在分线程执行
使用HttpConnection
- URL : 包含请求地址的类
- URL(path) : 包含请求路径的构造方法
- openConnection() : 得到连接对象
- HttpURLConnection : 代表与服务器连接的类
- setMethod(“GET/POST”) : 设置请求方式
- setConnectTimeout(time) : 设置连接超时时间, 单位为ms
- setReadTimeout(time): 设置读取服务器返回数据的时间
connect() : 连接服务器
int getResponseCode(): 得到服务器返回的结果码
Int getContentLength() : 得到服务器返回数据的长度(字节)
getOutputStream() : 返回一个指向服务器端的数据输出流
getInputStream() : 返回一个从服务器端返回的数据输入流
disconnect() : 断开连接
使用HttpClient
- HttpClient/DefaultHttpClient : 能提交请求的客户端对象
- HttpResponse execute (HttpUriRequest request) 执行包含请求数据的请求对象, 返回包含响应数据的响应对象
- HttpParams getParams()得到包含请求参数的对象
- HttpConnectionParams : 设置请求参数的工具类
- static setConnectionTimeout(params, time) : 设置获取连接的超时时间
- static setSoTimeout(params, time): 设置读取数据的超时时间
- HttpGet : Get请求
- HttpGet(String path) : 包含请求路径的构造方法
- HttpPost : Post请求
- HttpPost(String path) : 包含请求路径的构造方法
- setEntity(HttpEntity entity) : 设置请求体
- NameValuePair/BasicNameValuePair : 包含参数键值对
- BasicNameValuePair (String name, String value)
- HttpResponse : 服务器返回的响应
- getStatusLine() : 得到响应状态行, 从而得到状态码
- getEntity() : 得到响应体数据对象
- EntityUtils : 解析HttpEntity的工具类
- toString(httpEntity): 解析响应体, 得其内容字符串
- 关闭连接, 释放资源:
- client.getConnectionManager().shutdown();
Volley
- Volley是Google 2013年的 I/O大会 上,发布了的一个框架
- Volley是Android上的网络通信库,能使网络通信更快,更简单,更健壮
- Volley特别适合数据量不大但是通信频繁的场景: 带图片的列表
Volley相关API
- RequestQueue : 请求队列, 会自动执行队列中的请求
- Volley. newRequestQueue(context) : 创建一个请求队列
- addReqeust(Request reqeust) : 将请求添加到请求队列
- Request<T>: 代表请求的接口
- StringRequest : 获取字符串结果的请求
- JsonRequest : 获取Json数据结果的请求
- ImageRequest : 获取图片结果的请求
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<EditText
android:id="@+id/et_network_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:text="@string/url" >
</EditText>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="1. 测试HttpUrlConnection"
android:textSize="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="testConnectionGet"
android:text="GET请求" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="testConnectionPost"
android:text="POST请求" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="2. 测试HttpClient"
android:textSize="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="testClientGet"
android:text="GET请求" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="testClientPost"
android:text="POST请求" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="3. 测试Volley框架"
android:textSize="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="testVolleyGet"
android:text="GET请求" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="testVolleyPost"
android:text="POST请求" />
</LinearLayout>
<EditText
android:id="@+id/et_network_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:hint="用来显示网络请求返回的结果数据" >
</EditText>
</LinearLayout>
activity_network.xml
package com.atguigu.l04_datastorage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import com.android.volley.Request.Method;
import com.android.volley.AuthFailureError;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class NetworkActivity extends Activity {
private EditText et_network_url;
private EditText et_network_result;
private RequestQueue queue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network);
et_network_url = (EditText) findViewById(R.id.et_network_url);
et_network_result = (EditText) findViewById(R.id.et_network_result);
queue = Volley.newRequestQueue(this);
}
/*
* 使用httpUrlConnection提交get请求
*/
/*
1. 显示ProgressDialog
2. 启动分线程
3. 在分线程, 发送请求, 得到响应数据
1). 得到path, 并带上参数name=Tom1&age=11
2). 创建URL对象
3). 打开连接, 得到HttpURLConnection对象
4). 设置请求方式,连接超时, 读取数据超时
5). 连接服务器
6). 发请求, 得到响应数据
得到响应码, 必须是200才读取
得到InputStream, 并读取成String
7). 断开连接
4. 在主线程, 显示得到的结果, 移除dialog
*/
public void testConnectionGet(View v) {
//1. 显示ProgressDialog
final ProgressDialog dialog = ProgressDialog.show(this, null, "正在请求中...");
//2. 启动分线程
new Thread(){
//3. 在分线程, 发送请求, 得到响应数据
public void run() {
try {
//1). 得到path, 并带上参数name=Tom1&age=11
String path = et_network_url.getText().toString()+"?name=Tom1&age=11";
//2). 创建URL对象
URL url = new URL(path);
//3). 打开连接, 得到HttpURLConnection对象
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//4). 设置请求方式,连接超时, 读取数据超时
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(6000);
//5). 连接服务器
connection.connect();
//6). 发请求, 得到响应数据
//得到响应码, 必须是200才读取
int responseCode = connection.getResponseCode();
if(responseCode==200) {
//得到InputStream, 并读取成String
InputStream is = connection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while((len=is.read(buffer))!=-1) {
baos.write(buffer, 0, len);
}
final String result = baos.toString();
baos.close();
is.close();
//4. 在主线程, 显示得到的结果, 移除dialog
runOnUiThread(new Runnable() {
@Override
public void run() {
et_network_result.setText(result);
dialog.dismiss();
}
});
}
//7). 断开连接
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
//如果出了异常要移除dialog
dialog.dismiss();
}
}
}.start();
}
/*
* 使用httpUrlConnection提交post请求
*/
/*
1. 显示ProgressDialog
2. 启动分线程
3. 在分线程, 发送请求, 得到响应数据
1). 得到path
2). 创建URL对象
3). 打开连接, 得到HttpURLConnection对象
4). 设置请求方式,连接超时, 读取数据超时
5). 连接服务器
6). 发请求, 得到响应数据
得到输出流, 写请求体:name=Tom1&age=11
得到响应码, 必须是200才读取
得到InputStream, 并读取成String
7). 断开连接
4. 在主线程, 显示得到的结果, 移除dialog
*/
public void testConnectionPost(View v) {
//1. 显示ProgressDialog
final ProgressDialog dialog = ProgressDialog.show(this, null, "正在加载中...");
//2. 启动分线程
new Thread(new Runnable() {
//3. 在分线程, 发送请求, 得到响应数据
@Override
public void run() {
try {
//1). 得到path
String path = et_network_url.getText().toString();
//2). 创建URL对象
URL url = new URL(path);
//3). 打开连接, 得到HttpURLConnection对象
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//4). 设置请求方式,连接超时, 读取数据超时
connection.setRequestMethod("POST");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
//5). 连接服务器
connection.connect();
//6). 发请求, 得到响应数据
//得到输出流, 写请求体:name=Tom1&age=11
OutputStream os = connection.getOutputStream();
String data = "name=Tom2&age=12";
os.write(data.getBytes("utf-8"));
//得到响应码, 必须是200才读取
int responseCode = connection.getResponseCode();
if(responseCode==200) {
//得到InputStream, 并读取成String
InputStream is = connection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while((len=is.read(buffer))!=-1) {
baos.write(buffer, 0, len);
}
final String result = baos.toString();
baos.close();
is.close();
//4. 在主线程, 显示得到的结果, 移除dialog
runOnUiThread(new Runnable() {
@Override
public void run() {
et_network_result.setText(result);
dialog.dismiss();
}
});
}
os.close();
//7). 断开连接
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
dialog.dismiss();
}
}
}).start();
}
/*
* 使用httpClient提交get请求
*/
public void testClientGet(View v) {
//1. 显示ProgressDialog
final ProgressDialog dialog = ProgressDialog.show(this, null, "正在请求中...");
//2. 启动分线程
new Thread(){
//3. 在分线程, 发送请求, 得到响应数据
public void run() {
try {
//1). 得到path, 并带上参数name=Tom1&age=11
String path = et_network_url.getText().toString()+"?name=Tom3&age=13";
//2). 创建HttpClient对象
HttpClient httpClient = new DefaultHttpClient();
//3). 设置超时
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 5000);
HttpConnectionParams.setSoTimeout(params, 5000);
//4). 创建请求对象
HttpGet request = new HttpGet(path);
//5). 执行请求对象, 得到响应对象
HttpResponse response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode==200) {
//6). 得到响应体文本
HttpEntity entity = response.getEntity();
final String result = EntityUtils.toString(entity);
//4. 要主线程, 显示数据, 移除dialog
runOnUiThread(new Runnable() {
@Override
public void run() {
et_network_result.setText(result);
dialog.dismiss();
}
});
}
//7). 断开连接
httpClient.getConnectionManager().shutdown();
} catch (Exception e) {
e.printStackTrace();
//如果出了异常要移除dialog
dialog.dismiss();
}
}
}.start();
}
/*
* 使用httpClient提交post请求
*/
public void testClientPost(View v) {
//1. 显示ProgressDialog
final ProgressDialog dialog = ProgressDialog.show(this, null, "正在请求中...");
//2. 启动分线程
new Thread(){
//3. 在分线程, 发送请求, 得到响应数据
public void run() {
try {
//1). 得到path
String path = et_network_url.getText().toString();
//2). 创建HttpClient对象
HttpClient httpClient = new DefaultHttpClient();
//3). 设置超时
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 5000);
HttpConnectionParams.setSoTimeout(params, 5000);
//4). 创建请求对象
HttpPost request = new HttpPost(path);
//设置请求体
List<BasicNameValuePair> parameters = new ArrayList<BasicNameValuePair>();
parameters.add(new BasicNameValuePair("name", "Tom4"));
parameters.add(new BasicNameValuePair("age", "14"));
HttpEntity entity = new UrlEncodedFormEntity(parameters);
request.setEntity(entity);
//5). 执行请求对象, 得到响应对象
HttpResponse response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode==200) {
//6). 得到响应体文本
entity = response.getEntity();
final String result = EntityUtils.toString(entity);
//4. 要主线程, 显示数据, 移除dialog
runOnUiThread(new Runnable() {
@Override
public void run() {
et_network_result.setText(result);
dialog.dismiss();
}
});
}
//7). 断开连接
httpClient.getConnectionManager().shutdown();
} catch (Exception e) {
e.printStackTrace();
//如果出了异常要移除dialog
dialog.dismiss();
}
}
}.start();
}
/*
* 使用Volley提交get请求
*/
/*
1. 创建请求队列对象(一次)
2. 创建请求对象StringRequest
3. 将请求添加到队列中
*/
public void testVolleyGet(View v) {
final ProgressDialog dialog = ProgressDialog.show(this, null, "正在请求中...");
//创建请求对象StringRequest
String path = et_network_url.getText().toString()+"?name=Tom5&age=15";
StringRequest request = new StringRequest(path, new Response.Listener<String>() {
@Override
public void onResponse(String response) {//在主线程执行
et_network_result.setText(response);
dialog.dismiss();
}
}, null);
//将请求添加到队列中
queue.add(request);
}
/*
* 使用Volley提交post请求
*/
public void testVolleyPost(View v) {
final ProgressDialog dialog = ProgressDialog.show(this, null, "正在请求中...");
//创建请求对象StringRequest
String path = et_network_url.getText().toString();
StringRequest request = new StringRequest(Method.POST, path, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
et_network_result.setText(response);
dialog.dismiss();
}
}, null){
//重写此方法返回参数的map作为请求体
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
map.put("name", "Tom6");
map.put("age", "16");
return map;
}
};
//将请求添加到队列中
queue.add(request);
}
}
NetworkActivity.java
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">L04_DataStorage</string>
<string name="hello_world">Hello world!</string>
<string name="title_activity_sp">SpActivity</string>
<string name="title_activity_if">IFActivity</string>
<string name="title_activity_of">OFActivity</string>
<string name="title_activity_db">DBActivity</string>
<string name="title_activity_network">NetworkActivity</string>
<string name="url">http://192.168.10.165:8080/Web_Server/index.jsp</string>
</resources>
strings.xml
3. 应用练习
3.1 修改防盗名称
- 功能描述:
- 长按手机防盗, 显示修改的Dialog
- 通过dialog修改手机防盗名称
- 关键技术点:
- SharedPreferences的使用
- AlertDialog的使用
- GridView+BaseAdapter的使用
3.2 手机黑名单管理
黑名单的管理
- 功能描述:
- 黑名单添加
- 显示所有黑名单列表
- 删除指定黑名单
- 修改黑名单
- 关键技术点:
- SQLite数据库的操作
- ListView列表显示
- AlertDialog的使用
- contextMenu的使用
1. 界面布局
ListView
2. DBHelper
数据库
表
3. 实体类
4. DAO并单元测试
5. 显示列表
6. 添加
1. 显示添加的dialog(带输入框)
2. 在确定的回调方法实现:
1). 保存数据表中
2). 保存数据到List
3). 通知更新列表
问题1: 新添加的没有显示在第一行
add到集合中的第一位
问题2: 初始显示的列表顺序不对
查询根据_id倒序
7. 删除
1. 显示ContextMenu
2. 响应对item的选择
1). 删除数据表对应的数据
2). 删除List对应的数据
3). 通知更新列表
问题: 如何得到长按的position?
8. 更新
1. 显示更新的Dialog
2. 点击确定的响应
1). 更新数据表对应的数据
2). 更新List对应的数据
3). 通知更新列表
9. 使用ListActivity优化功能
1. extends ListActivity
2. 布局文件中的<ListView>的id必须是系统定义的id: list
3. 如果想在没有数据时显示一个提示文本, 可以在布局中定义 一个<TextView>(id必须为empty)
/*
一个功能的主要工作
*/
1. 内存的操作(集合)
2. 存储的操作(sp/数据库/文件)
3. 界面的操作(列表)
思路
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</ListView>
<TextView
android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="还没有一个黑名单"
android:gravity="center"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="添 加"
android:onClick="add"/>
</LinearLayout>
activity_main.xml
package com.atguigu.app04_sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
/**
* 数据库操作的帮助类
*
*/
public class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context) {
super(context, "atguigu.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.i("TAG", "DBHelper onCreate()");
//创建表
db.execSQL("create table black_number(_id integer primary key autoincrement, number varchar)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
DBHelper.java
实体类BlackNumber.java
package com.atguigu.app04_sqlite;
/**
* black_number表对应的实体类
*/
public class BlackNumber {
private int id;
private String number;
public BlackNumber(int id, String number) {
super();
this.id = id;
this.number = number;
}
public BlackNumber() {
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "BlackNumber [id=" + id + ", number=" + number + "]";
}
}
BlackNumber.java
package com.atguigu.app04_sqlite;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
/**
* 操作black_number表的DAO类
*
*/
public class BlackNumberDao {
private DBHelper dbHelper;
public BlackNumberDao(Context context) {
dbHelper = new DBHelper(context);
}
/**
* 添加一条记录
* @param blackNumber
*/
public void add(BlackNumber blackNumber) {
//1. 得到连接
SQLiteDatabase database = dbHelper.getReadableDatabase();
//2. 执行insert insert into black_number (number) values(xxx)
ContentValues values = new ContentValues();
values.put("number", blackNumber.getNumber());
long id = database.insert("black_number", null, values);
Log.i("TAG", "id="+id);
//设置id
blackNumber.setId((int) id);
//3. 关闭
database.close();
}
/**
* 根据id删除一条记录
*/
public void deleteById(int id) {
//1. 得到连接
SQLiteDatabase database = dbHelper.getReadableDatabase();
//2. 执行delete delete from black_number where _id=id
int deleteCount = database.delete("black_number", "_id=?", new String[]{id+""});
Log.i("TAG", "deleteCount="+deleteCount);
//3. 关闭
database.close();
}
/**
* 更新一条记录
*/
public void update(BlackNumber blackNumber) {
//1. 得到连接
SQLiteDatabase database = dbHelper.getReadableDatabase();
//2. 执行update update black_number set number=xxx where _id=id
ContentValues values = new ContentValues();
values.put("number", blackNumber.getNumber());
int updateCount = database.update("black_number", values , "_id="+blackNumber.getId(), null);
Log.i("TAG", "updateCount="+updateCount);
//3. 关闭
database.close();
}
/**
* 查询所有记录封装成List<BLackNumber>
*/
public List<BlackNumber> getAll() {
List<BlackNumber> list = new ArrayList<BlackNumber>();
//1. 得到连接
SQLiteDatabase database = dbHelper.getReadableDatabase();
//2. 执行query select * from black_number
Cursor cursor = database.query("black_number", null, null, null, null, null, "_id desc");
//3. 从cursor中取出所有数据并封装到List中
while(cursor.moveToNext()) {
//id
int id = cursor.getInt(0);
//number
String number = cursor.getString(1);
list.add(new BlackNumber(id, number));
}
//4. 关闭
cursor.close();
database.close();
return list;
}
}
BlackNumberDao.java
配置android测试包
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.atguigu.app04_sqlite"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!-- 使用android测试包 -->
<uses-library android:name="android.test.runner" />
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<!-- android:targetPackage的值应与manifest的package的值一致 -->
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.atguigu.app04_sqlite" />
</manifest>
AndroidManifest.xml
测试用例
package com.atguigu.app04_sqlite.test;
import java.util.List;
import com.atguigu.app04_sqlite.BlackNumber;
import com.atguigu.app04_sqlite.BlackNumberDao;
import android.test.AndroidTestCase;
import android.util.Log;
/**
* BlackNumberDao的单元测试类
*
*/
public class BlackNumberDaoTest extends AndroidTestCase {
public void testAdd() {
// 创建dao对象
BlackNumberDao dao = new BlackNumberDao(getContext());
// 调用方法
dao.add(new BlackNumber(-1, "123"));
}
public void testGetAll() {
// 创建dao对象
BlackNumberDao dao = new BlackNumberDao(getContext());
// 调用方法
List<BlackNumber> list = dao.getAll();
Log.i("TAG", list.toString());
}
public void testUpdate() {
// 创建dao对象
BlackNumberDao dao = new BlackNumberDao(getContext());
// 调用方法
dao.update(new BlackNumber(2, "321"));
}
public void testDeleteById() {
// 创建dao对象
BlackNumberDao dao = new BlackNumberDao(getContext());
// 调用方法
dao.deleteById(2);
}
}
BlackNumberDaoTest.java
package com.atguigu.app04_sqlite;
import java.util.List;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends ListActivity {
private ListView lv_main;
private BlackNumberAdapter adapter;
private BlackNumberDao dao;
private List<BlackNumber> data;
private int position;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv_main = getListView();
adapter = new BlackNumberAdapter();
dao = new BlackNumberDao(this);
data = dao.getAll();
lv_main.setAdapter(adapter);
//给listView设置创建contextMenu的监听
lv_main.setOnCreateContextMenuListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
//添加2个item
menu.add(0, 1, 0, "更新");
menu.add(0, 2, 0, "删除");
//得到长按的position
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
position = info.position;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
//得到对应的BlackNumber对象
BlackNumber blackNumber = data.get(position);
switch (item.getItemId()) {
case 1://更新
//1. 显示更新的Dialog
showUpdateDialog(blackNumber);
break;
case 2://删除
//1). 删除数据表对应的数据
dao.deleteById(blackNumber.getId());
//2). 删除List对应的数据
data.remove(position);
//3). 通知更新列表
adapter.notifyDataSetChanged();
break;
default:
break;
}
return super.onContextItemSelected(item);
}
/**
* 显示更新的Dialog
* @param blackNumber
*/
private void showUpdateDialog(final BlackNumber blackNumber) {
final EditText editText = new EditText(this);
editText.setHint(blackNumber.getNumber());
new AlertDialog.Builder(this)
.setTitle("更新黑名单")
.setView(editText)
.setPositiveButton("更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//1). 更新List对应的数据
String newNumber = editText.getText().toString();
blackNumber.setNumber(newNumber);
//2). 更新数据表对应的数据
dao.update(blackNumber);
//3). 通知更新列表
adapter.notifyDataSetChanged();
}
})
.setNegativeButton("取消", null)
.show();
}
public void add(View v) {
//1. 显示添加的dialog(带输入框)
final EditText editText = new EditText(this);
editText.setHint("输入黑名单号");
new AlertDialog.Builder(this)
.setTitle("添加黑名单")
.setView(editText)
.setPositiveButton("添加", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//1). 保存数据表中
String number = editText.getText().toString();
BlackNumber blackNumber = new BlackNumber(-1, number);
dao.add(blackNumber);
//2). 保存数据到List
//data.add(blackNumber);//已经有id了
data.add(0, blackNumber);
//3). 通知更新列表
adapter.notifyDataSetChanged();
}
})
.setNegativeButton("取消", null)
.show();
}
class BlackNumberAdapter extends BaseAdapter {
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null) {
convertView = View.inflate(MainActivity.this, android.R.layout.simple_list_item_1, null);
}
BlackNumber blackNumber = data.get(position);
TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
textView.setText(blackNumber.getNumber());
return convertView;
}
}
}
MainActivity.java
package com.atguigu.app04_sqlite;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.AdapterContextMenuInfo;
public class MainActivity1 extends Activity {
private ListView lv_main;
private BlackNumberAdapter adapter;
private BlackNumberDao dao;
private List<BlackNumber> data;
private int position;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//lv_main = (ListView) findViewById(R.id.lv_main);
adapter = new BlackNumberAdapter();
dao = new BlackNumberDao(this);
data = dao.getAll();
lv_main.setAdapter(adapter);
//给listView设置创建contextMenu的监听
lv_main.setOnCreateContextMenuListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
//添加2个item
menu.add(0, 1, 0, "更新");
menu.add(0, 2, 0, "删除");
//得到长按的position
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
position = info.position;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
//得到对应的BlackNumber对象
BlackNumber blackNumber = data.get(position);
switch (item.getItemId()) {
case 1://更新
//1. 显示更新的Dialog
showUpdateDialog(blackNumber);
break;
case 2://删除
//1). 删除数据表对应的数据
dao.deleteById(blackNumber.getId());
//2). 删除List对应的数据
data.remove(position);
//3). 通知更新列表
adapter.notifyDataSetChanged();
break;
default:
break;
}
return super.onContextItemSelected(item);
}
/**
* 显示更新的Dialog
* @param blackNumber
*/
private void showUpdateDialog(final BlackNumber blackNumber) {
final EditText editText = new EditText(this);
editText.setHint(blackNumber.getNumber());
new AlertDialog.Builder(this)
.setTitle("更新黑名单")
.setView(editText)
.setPositiveButton("更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//1). 更新List对应的数据
String newNumber = editText.getText().toString();
blackNumber.setNumber(newNumber);
//2). 更新数据表对应的数据
dao.update(blackNumber);
//3). 通知更新列表
adapter.notifyDataSetChanged();
}
})
.setNegativeButton("取消", null)
.show();
}
public void add(View v) {
//1. 显示添加的dialog(带输入框)
final EditText editText = new EditText(this);
editText.setHint("输入黑名单号");
new AlertDialog.Builder(this)
.setTitle("添加黑名单")
.setView(editText)
.setPositiveButton("添加", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//1). 保存数据表中
String number = editText.getText().toString();
BlackNumber blackNumber = new BlackNumber(-1, number);
dao.add(blackNumber);
//2). 保存数据到List
//data.add(blackNumber);//已经有id了
data.add(0, blackNumber);
//3). 通知更新列表
adapter.notifyDataSetChanged();
}
})
.setNegativeButton("取消", null)
.show();
}
class BlackNumberAdapter extends BaseAdapter {
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null) {
convertView = View.inflate(MainActivity1.this, android.R.layout.simple_list_item_1, null);
}
BlackNumber blackNumber = data.get(position);
TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
textView.setText(blackNumber.getNumber());
return convertView;
}
}
}
MainActivity1.java
3.3 最新APK下载安装
- 功能描述:
- 下载远程服务器端的APK文件
- 同步显示下载进度
- 下载完成自动安装
- 关键技术点:
- SD卡文件读写
- ProgressDialog的使用
- 分线程请求网络
- 安装APK