安卓基础巩固(三)多线程、数据存储、文件IO、SQLite

news2025/1/7 6:31:56

文章目录

  • 多线程
    • 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内存泄露

前置知识

  1. 内存泄露出现的原因:
    当一个对象已经不再使用时,本身被回收但却因为有另外一个正在使用的对象持有它的引用,从而导致它不能被回收,这就导致了内存泄露。
  2. 主线程的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,从而导致内存泄露。

在这里插入图片描述

解决方案一:静态内部类+弱引用

  1. 使用静态内部类构建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内的消息队列。

  1. 当外部类结束生命周期,清空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:内部静态类,用于实现工作线程与主线程之间的通信。

在这里插入图片描述

  1. SerialExecutor是所有实例化的AsyncTask对象公有的,其内部维护了一个双向队列。final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();存放可执行任务。
  2. SerialExecutor通过synchronized 修饰自己的execute()方法,每次从队头取出一个任务Runnable mActive执行。
  3. 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

使用步骤

  1. 第一步是获取到一个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),编写创建数据库 & 操作数据库(增、删、查、改)的方法。

  1. 自定义子类
/** 
  * 创建数据库子类,继承自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);  
    }

}

  1. 创建/开发数据库

对于操作 = “增、删、改(更新)”,需获得 可"读 / 写"的权限:getWritableDatabase()
对于操作 = “查询”,需获得 可"读 "的权限getReadableDatabase()

  1. 操作数据库
    建议使用原生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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/562017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构学习记录——图应用实例-六度空间(题目描述、算法思路、伪代码及解读、图解)

目录 题目描述 算法思路 伪代码 总体算法 BFS算法 伪代码解读 BFS算法 图解 题目描述 六度空间理论的核心观点是&#xff0c;人类社交网络中的任何两个人之间&#xff0c;平均只需要通过不超过六个中间人&#xff08;也就是六个社交关系&#xff09;就可以建立联系。换…

多台plc之间如何快速实现以太网无线连接?

常规来说&#xff0c;多台plc要实现以太网无线连接&#xff0c;首先要先确定以太网线必须正确连接&#xff0c;并建立物理连接。然后需要在PLC端设置好IP地址&#xff0c;以使不同PLC以相同协议可以实现通信交流。最后是建立PLC端数据采集及交换系统&#xff0c;要求在PLC端设置…

《封号码罗》关于js逆向猿人学第二题cookies里面m值的获取[纯扣算法](二十六)

这一题有点儿误打误撞的感觉。 本题使用了抓包工具Fiddler&#xff0c;m值在cookie里面&#xff0c;而且这个cookie是本地生成的 抓包发现有两次请求&#xff0c;第一次返回了一堆JS&#xff0c;而且cookie里面没有m值&#xff0c;第二次请求就带上了m值&#xff0c;所以m应该…

信号完整性分析基础知识之传输线和反射(七):带负载传输线、感性不连续引起的反射

带负载传输线 如果在传输线上有一个小的容性负载&#xff0c;信号会出现失真&#xff0c;上升时间也会降低。每个分立电容都会降低信号在其附近看到的阻抗。如果传输线上分布有多个容性负载&#xff08;例如一个总线上每隔1.2inch有一个2pF的连接器残桩&#xff0c;或者一个内…

单模光纤一维模场分布的MATLAB仿真

根据已知的单模光纤电场z分量分布&#xff0c;可以用MATLAB展示一维的模场分布 具体来说&#xff0c;通过数值计算解出给定光纤&#xff08;n_1&#xff0c;n_2&#xff0c;a&#xff09;参数时对应的V参量 通过特征方程解出V对应的W和U 通过这三个参数带入到光场的表达式中…

07. 算法之一致性哈希算法介绍

前言 哈希算法在程序开发中的很多地方都能看到他的身影&#xff0c;但是哈希有他的局限性&#xff0c;比如如果两个key哈希到同一个位置的时候&#xff0c;此时就不好处理。本节我们介绍一下常规处理方式。 1. 什么是哈希算法 哈希算法将任意长度的二进制值映射为较短的固定…

界面控件DevExtreme使用指南 - 如何自定义上下文菜单和工具栏

DevExtreme File Manager&#xff08;文件管理器&#xff09;小部件现在支持自定义内置的工具栏和上下文菜单&#xff0c;用户可以使用标准和定义的命令项填充项目集合&#xff0c;并配置设置来更改其外观和操作。 DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#x…

基于Keras-YOLO实现目标检测

Keras-YOLO 3项目使用Python语言实现了YOLO v3网络模型&#xff0c;并且可以导入Darknet网络预先训练好的权重文件信息直接使用网络进行目标识别。 1. 下载Keras-YOLO 3项目 执行如下命令下载Keras-YOLO 3项目代码&#xff1a; git clone https://github.com/qqwweee/keras-…

直播和短视频美颜sdk的开发流程、代码分析

目前&#xff0c;美颜技术是提高视频质量的重要手段之一&#xff0c;特别是短视频和直播两个行业。本文将介绍其开发流程和代码分析。 一、美颜SDK的开发流程 1.需求分析 首先我们需要明确的一点就是“需求”&#xff0c;例如&#xff1a;美颜效果、美颜程度、性能要求等。同…

解决找不到微信支付V3版本公钥问题

参考微信文档链接为签名验证-接口规则 | 微信支付商户平台文档中心 写的内容特别不明显&#xff0c;往下面看会找到 下载openssl工具使用命令从私钥证书中导出即可。

地图在线编辑平台,无基础轻松实现私域地图

位构云平台让用户轻松构建诸如空间信息管理、建筑信息管理及三维空间数据可视化、导航等类型应用的多平台、综合型地图引擎&#xff0c;基于OpenGLES/WebGL三维可视化技术体系的自主研发图形引擎&#xff0c;可以让开发者轻松构建运行在 Web、Android、iOS 等多平台的应用程序。…

安捷伦DSO80404B(Agilent)dso80404b租售回收 数字示波器

DSO80404B 是 Agilent 的 4 GHz、4 通道数字示波器。测量电子电路或组件中随时间变化的电压或电流信号&#xff0c;以显示振幅、频率和上升时间等。应用包括故障排除、生产测试和设计。 附加功能&#xff1a; 4 GHz 带宽&#xff0c;可升级至 13 GHz 4个模拟通道 高达 40 G…

day10 前端技术-HTMLCSS

HTML 含义:超文本标记语言,静态网页,用于在浏览器显示数据 双标签:<> </>,开始标签和结束标签同时出现 单标签: 属性名:属性后面的值都加双引号 常用的HTML标签 :文档的根标签 :HTML页面的头部标签 “”:页面标题 “”:页面主体部分 “ “ “ 到 ”:标题…

『树莓派云台机器人』02. 电脑连接树莓派 配置开发环境

目录 1. 下载ssh交互工具 Xshell 与XFTP&#xff08;有过相关使用经历的朋友可以跳过这一节内容&#xff09;2. 下载VNC远程控制工具软件3. 连接过程4. Xshell 命令工具5. XFTP 文件传送工具6. 关于联网总结 欢迎关注 『树莓派云台机器人』 博客&#xff0c;持续更新中 欢迎关注…

PoseiSwap IDO在Bounce上启动在即,如何参与?

目前&#xff0c;Nautilus Chain 生态基本完成测试&#xff0c;并即将在不久上线主网。PoseiSwap 作为 Nautilus Chain 上的首个 DEX&#xff0c;也即将面向市场并上线正式版本。我们看到&#xff0c; PoseiSwap 也正式发布了新的市场进程&#xff0c;基于其治理代币 POSE 的 I…

转辙机介绍

简介 转辙机是指用以可靠地转换道岔位置&#xff0c;改变道岔开通方向&#xff0c;锁闭道岔尖轨&#xff0c;反映道岔位置的重要的信号基础设备&#xff0c;它可以很好地保证行车安全&#xff0c;提高运输效率&#xff0c;改善行车人员的劳动强度。 分类 01、转辙机按动作时…

如何做出有价值的知识管理文档?

知识管理文档是企业中重要的资产&#xff0c;它可以帮助企业员工更好地理解业务流程、产品功能、标准操作等信息。如何做出有价值的知识管理文档&#xff0c;满足员工知识需求&#xff0c;提高工作效率&#xff0c;本文将探讨以下几个方面&#xff1a; 一、制定有效的知识管理…

jsp网上鞋城系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java 网上鞋城系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5 开发&#xff0c;数据库为Mysql&#xff0c;使用j…

建议熟知:2023谷歌新搜索规则!

谷歌作为全球最大的搜索引擎之一&#xff0c;不断更新和调整其搜索算法和规则&#xff0c;以提供更精准、高质量的搜索结果。2023年&#xff0c;谷歌搜索将迎来一系列新的搜索规则&#xff0c;同时&#xff0c;AI工具的快速发展也为谷歌搜索带来了全新的应用场景和可能性。 这…

基于Go开发PaaS平台1

Go开发PaaS平台核心功能 代码仓库地址GitHub - yunixiangfeng/gopaas 1 云原生PaaS平台介绍 随着云计算的发展&#xff0c;越来越多的企业逐步的把IT资源迁移到云上。PaaS平台作为基础设施基座&#xff0c;可以帮助企业快速构建功能丰富的容器云平台&#xff0c;提升交付效率…