文章目录
- 多线程
- Handler
- 相关概念
- UI线程/主线程
- Message
- Message Queue
- Looper
- Handler
- 使用步骤
- Handler.sendMessage()
- Handler.post()
- Handler 机制工作原理
- Handler内存泄露
- 前置知识
- 案例分析
- 解决方案一:静态内部类+弱引用
- 解决方案一:onDestroy()时清空Handler内的消息队列。
- AsynTask
- 属性介绍
- 用法示例
- 工作原理
- AsyncTask使用问题
- 数据存储
- SharedPreferences
- 使用步骤
- 文件存储
- 内部存储器和外部存储器
- SQLite
- SQLiteOpenHelper类
- 使用案例
多线程
多线程的应用在Android开发中是非常常见的,常用方法主要有:
Handler
Handler是Android开发中的一种线程间消息传递机制,使用Handler可以在多个线程并发更新UI的同时,保证线程安全。
相关概念
UI线程/主线程
在Android开发中,为了UI操作是安全的,规定只允许UI线程更新Activity里的UI组件,UI线程也是主线程!
UI线程在程序第1次启动时自动开启,处理与UI相关的事件。
Message
Message是线程间通信的数据单元,存储需要操作的通信信息。
Message Queue
一种先进先出,用于存储Handler发送过来的Message的数据结构。
Looper
Looper直译为循环器,它是Message Queue与Handler之间的通信媒介,Looper是持有MessageQueue
的,它的作用包含:
- 消息获取:循环取出Message Queue中的消息。
- 消息分发:将取出的Message发送给对应的Handler。
每个线程中只能拥有一个Looper,一个Looper可绑定多个线程的Handler,也就是说,多个线程的Handler可以向一个线程的Looper所持有的MessageQueue中发送Message。
Handler
Handler直译为处理者,它是子线程与主线程间的通信媒介,线程消息的主要处理者。Handler持有 Looper 的实例,直接持有looper的消息队列。
- Handle可以添加Message到Message Queue。
- 可以处理Looper分发来的消息。
使用步骤
Handler.sendMessage()
Handler.sendMessage()的入参是Message对象,可以在工作线程中使用Handler.sendMessage(),发送Message,然后在主线程中接收Message,执行更新UI的操作。
可以通过新建Handler的子类或者构建匿名内部类的形式实现。
/**
* 方式1:新建Handler子类(内部类)
*/
// 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法
class mHandler extends Handler {
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
}
// 步骤2:在主线程中创建Handler实例
private Handler mhandler = new mHandler();
// 步骤3:创建所需的消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤4:在工作线程中 通过Handler发送消息到消息队列中
// 可通过sendMessage() / post()
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);
// 步骤5:开启工作线程(同时启动了Handler)
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
/**
* 方式2:匿名内部类
*/
// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
private Handler mhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
};
// 步骤2:创建消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);
// 步骤4:开启工作线程(同时启动了Handler)
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
Handler.post()
Handler.post()的入参是一个Runnable对象,重写Runnable对象的run()方法,进行UI的更新。
下边是一个利用handle的postDelayed实现的验证码倒计时。
Handler countHandler = new Handler(); //验证码发送等待倒计时
// 发送验证码倒计时
private final Runnable myRunnable = new Runnable() {
@Override
public void run() {
if(wait >=0) {
sendCodeButton.setText(String.valueOf(wait));
countHandler.postDelayed(this, 1000);
//从当前时间开始延迟delayMillis时间后执行Runnable
wait--;
}
else{
wait = 60;
sendCodeButton.setText("重新发送");
sendCodeButton.setEnabled(true);
countHandler.removeCallbacks(this);
}
}
};
countHandler.postDelayed(myRunnable,1000);
Handler 机制工作原理
Handler机制的工作流程主要包括4个步骤:
- 异步通信准备
- 消息入队
- 消息循环
- 消息处理
一个线程只能有一个Looper,但可以绑定到多个Handler。在工作进程通过Handler发送Message到消息队列中,Looper将消息再传递给创建该消息的Handler,Handler在主线程中接收到消息,并执行UI操作。
Handler内存泄露
前置知识
- 内存泄露出现的原因:
当一个对象已经不再使用时,本身被回收但却因为有另外一个正在使用的对象持有它的引用,从而导致它不能被回收,这就导致了内存泄露。 - 主线程的Looper对象的生命周期 = 该应用程序的生命周期
在java中,非静态内部类
和匿名内部类
都默认持有外部类的引用(可以调用this)
案例分析
在java中如果按照下边的方式使用Handler会警告出现内存泄露。
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主线程创建时便自动创建Looper & 对应的MessageQueue
// 之后执行Loop()进入消息循环
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 通过匿名内部类实例化的Handler类对象
//注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
showhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
};
出现内存泄露的原因:
- 在Handler消息队列还有未处理的消息/正在处理的消息时,消息队列中的Message持有Handler实例的引用(Message是Handler的内部类)。
- 由于Handle 是非静态内部类,那么它又持有外部MainActivity类的引用。
- 上述引用关系会一直保存,直到Handler消息队列中的消息被处理完毕。
- 如果在Handler消息队列还有未处理的消息,用户关闭了APP,此时需要销毁MainActivity,但是由于上述引用关系,导致垃圾回收器无法回收MainActivity,从而导致内存泄露。
解决方案一:静态内部类+弱引用
- 使用静态内部类构建Handler实例,并使用WeakReference弱引用持有外部类,保证外部类能被回收。因为:弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 实例化自定义的Handler类对象->>分析1
// 注:
// a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
// b. 定义时需传入持有的Activity实例(弱引用)
showhandler = new FHandler(this);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放
showhandler.sendMessage(msg);
}
}.start();
}
// 设置为:静态内部类
private static class FHandler extends Handler{
// 定义 弱引用实例
private WeakReference<Activity> reference;
// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity实例
reference = new WeakReference<Activity>(activity); }
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
}
}
解决方案一:onDestroy()时清空Handler内的消息队列。
- 当外部类结束生命周期,清空Handler内的消息队列。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}
activity被销毁前一定会执行onDestroy方法,在此处清空Handler内的消息队列,就可以保证Handler被正常销毁。
为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式
AsynTask
AsyncTask是一个在不需要开发者直接操作多线程和Handler的情况下的轻量级异步类,适用于短时间的操作(最多几秒),即时使用。
属性介绍
AsyncTask泛型:
AsyncTask<Params, Progress, Result>
- Params 执行任务前,传入的参数的类型。
- Progress 后台线程执行的时候,用来表示进度的类型
- Result 表示执行结果的类型
这3个类型需要开发者自己指定。比如指定 String, Integer 等。这3个类型在后面的方法里会用到。
如果不使用泛型可以用Void
表示:
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
要使用AsyncTask,必须新建一个类来继承它,并且重写doInBackground方法。通常也会重写onPostExecute方法。 执行异步任务的时候,我们主要关心下面这4个方法。
- onPreExecute() 执行任务前在ui线程调用。通用用来设置任务,比如在界面上显示一个进度条。
- Result doInBackground(Params… params) 在onPreExecute()结束后立即调用这个方法。耗时的异步任务就在这里操作。执行任务时传入的参数会被传到这里。异步任务的中间结果在这里可以用publishProgress发送到主线程。
- onProgressUpdate(Progress… values) 在ui线程中执行。后台任务还在进行的时候,这里负责处理进度信息。比如在这显示进度条动画,修改文字显示等。
- onPostExecute(Result result) 后台任务结束了调这个方法。它在ui线程执行。最后的结果会传到这。
用法示例
下边是一个进度条的用法示例:
主要步骤如下:
步骤1:构建异步任务类MyTask继承自AsyncTask,重写方法。
步骤2:doInBackground()
编写耗时任务逻辑,这里是工作线程,在这里还可以使用publishProgress()
,向主线程发布进度。
步骤3:onProgressUpdate
处接收到doInBackground
中使用publishProgress()
发布的进度,在这里执行UI更新操作,这里是主线程。
步骤4:在主线程创建MyTask实例对象,调用execute()方法,开启任务。同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常。
步骤5:执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
public class MainActivity extends AppCompatActivity {
// 线程变量
MyTask mTask;
// 主布局中的UI组件
Button button,cancel; // 加载、取消按钮
TextView text; // 更新的UI组件
ProgressBar progressBar; // 进度条
/**
* 步骤1:创建AsyncTask子类
* 注:
* a. 继承AsyncTask类
* b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
* 此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型
* c. 根据需求,在AsyncTask子类内实现核心方法
*/
private class MyTask extends AsyncTask<String, Integer, String> {
// 方法1:onPreExecute()
// 作用:执行 线程任务前的操作
@Override
protected void onPreExecute() {
text.setText("加载中");
// 执行前显示提示
}
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 此处通过计算从而模拟“加载进度”的情况
@Override
protected String doInBackground(String... params) {
try {
int count = 0;
int length = 1;
while (count<99) {
count += length;
// 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
publishProgress(count);
// 模拟耗时任务
Thread.sleep(50);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// 方法3:onProgressUpdate()
// 作用:在主线程 显示线程任务执行的进度
@Override
protected void onProgressUpdate(Integer... progresses) {
progressBar.setProgress(progresses[0]);
text.setText("loading..." + progresses[0] + "%");
}
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) {
// 执行完毕后,则更新UI
text.setText("加载完毕");
}
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
text.setText("已取消");
progressBar.setProgress(0);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 绑定UI组件
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
cancel = (Button) findViewById(R.id.cancel);
text = (TextView) findViewById(R.id.text);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
/**
* 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
* 注:AsyncTask子类的实例必须在UI线程中创建
*/
mTask = new MyTask();
// 加载按钮按按下时,则启动AsyncTask
// 任务完成后更新TextView的文本
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
* 注:
* a. 必须在UI线程中调用
* b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
* c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
* d. 不能手动调用上述方法
*/
mTask.execute();
}
});
cancel = (Button) findViewById(R.id.cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取消一个正在执行的任务,onCancelled方法将会被调用
mTask.cancel(true);
}
});
}
}
工作原理
AsyncTask的实现原理 = 线程池 + Handler
其内部封装了2个线程池和1个Handler:
- 任务队列线程池:SerialExecutor,它是一个静态内部类,作用是让需要执行的多个线程任务可以按照顺序排列。
- 执行线程池:THREAD_POOL_EXECUTOR,它也是一个静态内部类,这里是真正执行具体线程任务的地方。
- Handler:内部静态类,用于实现工作线程与主线程之间的通信。
- SerialExecutor是所有实例化的AsyncTask对象公有的,其内部维护了一个双向队列。
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
存放可执行任务。 - SerialExecutor通过
synchronized
修饰自己的execute()
方法,每次从队头取出一个任务Runnable mActive
执行。 - SerialExecutor中实际上是通过调用
THREAD_POOL_EXECUTOR.execute(mActive);
来真正执行任务的。
AsyncTask使用问题
- 在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean);
- 若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露。建议将其声明为静态内部类
- 当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作。在Activity的onResume(0重启任务线程。
数据存储
Android中常见的数据存储方式主要包括:
- SharedPreferences
- SQLite数据库
- 文件存储
- ContentProvider
- 网络存储
SharedPreferences
SharedPreferences : 直译为共享偏好。
它是一种采用key-value
键值对的形式保存轻量级数据的方式,其存储格式为XML格式,文件存放的默认路径为data/data/包名/shared_prefs/文件名.xml
使用步骤
- 第一步是获取到一个SharedPreferences对象。有如下方法:
- Activity.getSharedPreferences(String name, int mode)方法
- context.getSharedPreferences(String name, int mode)。
- PreferenceManager 类中的getDefaultSharedPreferences()方法:
这是一个静态方法,它接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件。
传入这个sp的名字,以及开发的模式。
2. 使用示例:
1)写入数据:
//步骤1:创建一个SharedPreferences对象
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
//步骤2: 实例化SharedPreferences.Editor对象
SharedPreferences.Editor editor = sharedPreferences.edit();
//步骤3:将获取过来的值放入文件
editor.putString("name", “Tom”);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);
//步骤4:提交
editor.commit();
2)读取数据:
SharedPreferences sharedPreferences= getSharedPreferences("data", Context .MODE_PRIVATE);
String userId=sharedPreferences.getString("name","");
3)删除指定数据
editor.remove("name");
editor.commit();
4)清空数据
editor.clear();
editor.commit();
除了使用.commit(),还可以使用.apply()方法,
同步提交(commit)、异步提交(Apply)
注意:如果在 Fragment 中使用 SharedPreferences 时,需要放在 onAttach(Activity activity) 里面进行 SharedPreferences 的初始化,否则会报空指针 即 getActivity()会可能返回null !
文件存储
文件存储是 Android 中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的文本数据或二进制数据。
内部存储器和外部存储器
- 所有的Android设备都有两个文件存储区域:内部(Internal)和外部(External)存储器。
- 内部存储器:应用程序始终可以访问,默认情况下存储的文件只能自己的应用访问,且不需要使用权限。主要适用于不想被其他应用和用户随意访问的数据。
- getFilesDir()
- getCacheDir()
- 外部存储器:用户插拔后不可用访问,无限制可读,存放文件可被外部应用读取,在一些版本中使用外部存储中需要申请权限,主要适用于共享的数据信息。
- getExternalFilesDir()
- getExternalCacheDir()
注意:卸载应用时,所以目录下的文件将被移除。并且其他应用无法访问这些专属文件。
使用方式参考这里
SQLite
SQLiteOpenHelper类
SQLiteOpenHelper类 常用方法:
/**
* 创建数据库
*/
// 1. 创建 or 打开 可读/写的数据库(通过 返回的SQLiteDatabase对象 进行操作)
getWritableDatabase()
// 2. 创建 or 打开 可读的数据库(通过 返回的SQLiteDatabase对象 进行操作)
getReadableDatabase()
// 3. 数据库第1次创建时 则会调用,即 第1次调用 getWritableDatabase() / getReadableDatabase()时调用
// 在继承SQLiteOpenHelper类的子类中复写
onCreate(SQLiteDatabase db)
// 4. 数据库升级时自动调用
// 在继承SQLiteOpenHelper类的子类中复写
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
// 5. 关闭数据库
close()
/**
* 数据库操作(增、删、减、查)
*/
// 1. 查询数据
(Cursor) query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
// 查询指定的数据表返回一个带游标的数据集。
// 各参数说明:
// table:表名称
// colums:列名称数组
// selection:条件子句,相当于where
// selectionArgs:条件语句的参数数组
// groupBy:分组
// having:分组条件
// orderBy:排序类
// limit:分页查询的限制
// Cursor:返回值,相当于结果集ResultSet
(Cursor) rawQuery(String sql, String[] selectionArgs)
//运行一个预置的SQL语句,返回带游标的数据集(与上面的语句最大的区别 = 防止SQL注入)
// 2. 删除数据行
(int) delete(String table,String whereClause,String[] whereArgs)
// 3. 添加数据行
(long) insert(String table,String nullColumnHack,ContentValues values)
// 4. 更新数据行
(int) update(String table, ContentValues values, String whereClause, String[] whereArgs)
// 5. 执行一个SQL语句,可以是一个select or 其他sql语句
// 即 直接使用String类型传入sql语句 & 执行
(void) execSQL(String sql)
使用案例
在具体使用时,我们需要自定义数据库子类(继承自SQLiteOpenHelper),编写创建数据库 & 操作数据库(增、删、查、改)的方法。
- 自定义子类
/**
* 创建数据库子类,继承自SQLiteOpenHelper类
* 需 复写 onCreat()、onUpgrade()
*/
public class DatabaseHelper extends SQLiteOpenHelper {
// 数据库版本号
private static Integer Version = 1;
/**
* 构造函数
* 在SQLiteOpenHelper的子类中,必须有该构造函数
*/
public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version) {
// 参数说明
// context:上下文对象
// name:数据库名称
// param:一个可选的游标工厂(通常是 Null)
// version:当前数据库的版本,值必须是整数并且是递增的状态
// 必须通过super调用父类的构造函数
super(context, name, factory, version);
}
/**
* 复写onCreate()
* 调用时刻:当数据库第1次创建时调用
* 作用:创建数据库 表 & 初始化数据
* SQLite数据库创建支持的数据类型: 整型数据、字符串类型、日期类型、二进制
*/
@Override
public void onCreate(SQLiteDatabase db) {
// 创建数据库1张表
// 通过execSQL()执行SQL语句(此处创建了1个名为person的表)
String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))";
db.execSQL(sql);
// 注:数据库实际上是没被创建 / 打开的(因该方法还没调用)
// 直到getWritableDatabase() / getReadableDatabase() 第一次被调用时才会进行创建 / 打开
}
/**
* 复写onUpgrade()
* 调用时刻:当数据库升级时则自动调用(即 数据库版本 发生变化时)
* 作用:更新数据库表结构
* 注:创建SQLiteOpenHelper子类对象时,必须传入一个version参数,该参数 = 当前数据库版本, 若该版本高于之前版本, 就调用onUpgrade()
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 参数说明:
// db : 数据库
// oldVersion : 旧版本数据库
// newVersion : 新版本数据库
// 使用 SQL的ALTER语句
String sql = "alter table person add sex varchar(8)";
db.execSQL(sql);
}
}
- 创建/开发数据库
对于操作 = “增、删、改(更新)”,需获得 可"读 / 写"的权限:getWritableDatabase()
对于操作 = “查询”,需获得 可"读 "的权限getReadableDatabase()
- 操作数据库
建议使用原生SQL语言操作,因为更加通用、简单
/**
* 1. 创建 & 打开数据库
*/
// a. 创建DatabaseHelper对象
// 注:一定要传入最新的数据库版本号
SQLiteOpenHelper dbHelper = new DatabaseHelper(SQLiteActivity.this,"test_carson",2);
// b.创建 or 打开 可读/写的数据库
SQLiteDatabase sqliteDatabase = dbHelper.getWritableDatabase();
/**
* 操作1:插入数据 = insert()
*/
// a. 创建ContentValues对象
ContentValues values = new ContentValues();
// b. 向该对象中插入键值对
values.put("id", 1);
values.put("name", "carson");
//其中,key = 列名,value = 插入的值
//注:ContentValues内部实现 = HashMap,区别在于:ContenValues Key只能是String类型,Value可存储基本类型数据 & String类型
// c. 插入数据到数据库当中:insert()
sqliteDatabase.insert("user", null, values);
// 参数1:要操作的表名称
// 参数2:SQl不允许一个空列,若ContentValues是空,那么这一列被明确的指明为NULL值
// 参数3:ContentValues对象
// 注:也可采用SQL语句插入
String sql = "insert into user (id,name) values (1,'carson')";
db.execSQL(sql) ;
/**
* 操作2:修改数据 = update()
*/
// a. 创建一个ContentValues对象
ContentValues values = new ContentValues();
values.put("name", "zhangsan");
// b. 调用update方法修改数据库:将id=1 修改成 name = zhangsan
sqliteDatabase.update("user", values, "id=?", new String[] { "1" });
// 参数1:表名(String)
// 参数2:需修改的ContentValues对象
// 参数3:WHERE表达式(String),需数据更新的行; 若该参数为 null, 就会修改所有行;?号是占位符
// 参数4:WHERE选择语句的参数(String[]), 逐个替换 WHERE表达式中 的“?”占位符;
// 注:调用完upgrate()后,则会回调 数据库子类的onUpgrade()
// 注:也可采用SQL语句修改
String sql = "update [user] set name = 'zhangsan' where id="1";
db.execSQL(sql);
/**
* 操作3:删除数据 = delete()
*/
// 删除 id = 1的数据
sqliteDatabase.delete("user", "id=?", new String[]{"1"});
// 参数1:表名(String)
// 参数2:WHERE表达式(String),需删除数据的行; 若该参数为 null, 就会删除所有行;?号是占位符
// 参数3:WHERE选择语句的参数(String[]), 逐个替换 WHERE表达式中 的“?”占位符;
// 注:也可采用SQL语句修改
String sql = "delete from user where id="1";
db.execSQL(sql);
/**
* 操作4:查询数据1 = rawQuery()
* 直接调用 SELECT 语句
*/
Cursor c = db.rawQuery("select * from user where id=?",new Stirng[]{"1"});
// 返回值一个 cursor 对象
// 通过游标的方法可迭代查询结果
if(cursor.moveToFirst()) {
String password = c.getString(c.getColumnIndex("password"));
}
//Cursor对象常用方法如下:
c.move(int offset); //以当前位置为参考,移动到指定行
c.moveToFirst(); //移动到第一行
c.moveToLast(); //移动到最后一行
c.moveToPosition(int position); //移动到指定行
c.moveToPrevious(); //移动到前一行
c.moveToNext(); //移动到下一行
c.isFirst(); //是否指向第一条
c.isLast(); //是否指向最后一条
c.isBeforeFirst(); //是否指向第一条之前
c.isAfterLast(); //是否指向最后一条之后
c.isNull(int columnIndex); //指定列是否为空(列基数为0)
c.isClosed(); //游标是否已关闭
c.getCount(); //总数据项数
c.getPosition(); //返回当前游标所指向的行数
c.getColumnIndex(String columnName);//返回某列名对应的列索引值
c.getString(int columnIndex); //返回当前行指定列的值
// 通过游标遍历1个名为user的表
Cursor result=db.rawQuery("SELECT _id, username, password FROM user");
result.moveToFirst();
while (!result.isAfterLast()) {
int id=result.getInt(0);
String name=result.getString(1);
String password =result.getString(2);
// do something useful with these
result.moveToNext();
}
result.close();
// 若查询是动态的,使用该方法会复杂。此时使用 query() 会方便很多
// 注:无法使用SQL语句,即db.execSQL(sql);
/**
* 操作4:查询数据2 = query()
* 直接调用 SELECT 语句
*/
// 方法说明
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
// 参数说明
// table:要操作的表
// columns:查询的列所有名称集
// selection:WHERE之后的条件语句,可以使用占位符
// groupBy:指定分组的列名
// having指定分组条件,配合groupBy使用
// orderBy指定排序的列名
// limit指定分页参数
// distinct可以指定“true”或“false”表示要不要过滤重复值
// 所有方法将返回一个Cursor对象,代表数据集的游标
// 具体使用
Cursor cursor = sqliteDatabase.query("user", new String[] { "id","name" }, "id=?", new String[] { "1" }, null, null, null);
// 参数1:(String)表名
// 参数2:(String[])要查询的列名
// 参数3:(String)查询条件
// 参数4:(String[])查询条件的参数
// 参数5:(String)对查询的结果进行分组
// 参数6:(String)对分组的结果进行限制
// 参数7:(String)对查询的结果进行排序
// 注:无法使用SQL语句,即db.execSQL(sql);
/**
* 操作5:关闭数据库 = close()
* 注:完成数据库操作后,记得调用close()关闭数据库,从而释放数据库的连接
*/
sqliteDatabase.close();
/**
* 操作6:删除数据库 = deleteDatabase()
*/
// 删除 名为person的数据库
deleteDatabase("test.db");