文章目录
- 多线程
- 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");


](https://img-blog.csdnimg.cn/b091d34c87e74c39b3f8be09ad52c4e9.png)
















