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

多线程

多线程的应用在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来更新UI操作:

  1. 创建Mhandler 继承自Handler
  2. 复写handlerMessage,这这里接收消息,并且执行UI更新
  3. 构建子线程1,在线程的run()方法中,构建Message对象,执行handler的sendMessage()方法。
  4. 子线程1发送到message会触发handlerMessage回调,执行子线程对UI的更新更新。
public class MainActivity extends AppCompatActivity {
    
    public TextView mTextView;
    public Handler mHandler;

    // 步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
    class Mhandler extends Handler {

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
            // 根据不同线程发送过来的消息,执行不同的UI操作
            // 根据 Message对象的what属性 标识不同的消息
            switch (msg.what) {
                case 1:
                    mTextView.setText("执行了线程1的UI操作");
                    break;
                case 2:
                    mTextView.setText("执行了线程2的UI操作");
                    break;
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.show);

        // 步骤2:在主线程中创建Handler实例
        mHandler = new Mhandler();
       
        // 采用继承Thread类实现多线程演示
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                 // 步骤3:创建所需的消息对象
                 Message msg = Message.obtain();
                 msg.what = 1; // 消息标识
                 msg.obj = "A"; // 消息内存存放

                 // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
                 mHandler.sendMessage(msg);
            }
        }.start();
        // 步骤5:开启工作线程(同时启动了Handler)

        // 此处用2个工作线程展示
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 通过sendMessage()发送
                 // a. 定义要发送的消息
                 Message msg = Message.obtain();
                 msg.what = 2; //消息的标识
                 msg.obj = "B"; // 消息的存放
                 // b. 通过Handler发送消息到其绑定的消息队列
                 mHandler.sendMessage(msg);
            }
        }.start();

    }
}

Handler 机制工作原理

Handler机制的工作流程主要包括4个步骤:

  • 异步通信准备
  • 消息入队
  • 消息循环
  • 消息处理
    在这里插入图片描述
    一个线程只能有一个Looper,但可以绑定到多个Handler。在工作进程通过Handler发送Message到消息队列中,Looper将消息再传递给创建该消息的Handler,Handler在主线程中接收到消息,并执行UI操作。
    在这里插入图片描述

在这里插入图片描述

  1. 每一个线程中最多只有一个Looper,通过ThreadLocal来保存,Looper中有MessageQueue,保存Handler并且执行Handler发送的Message。
  2. UI线程或者HandlerThread是默认提供了循环器Looper了的,其他线程如果想要使用Handler机制,需要通过Looper.prepare()来创建Looper,并且通过ThreadLocal来保存Looper,每一个线程中只能调用一次Looper.prepare(),也就是说一个线程只能有一个Looper,保证了线程中Looper的唯一性。
  3. Handler执行sendMessage或者post操作,这些操作执行的线程是Handler中Looper所在的线程和Handler在哪里创建没有关系,和Handler中的Looper在哪里创建有关系。

Handler机制中,sendMessage和post(Runnable)区别

post的runnable会直接在循环器Looper中被调用,执行run()方法,而sendMessage()方法要用户重写handlerMessage()方法来处理发送过来的消息。

Looper会一直消耗系统资源吗?

Looper不会一直消耗系统资源,当Looper中的MessageQueue中没有消息,或者定时消息没到执行时间时,当前持有的Looper线程就会进入阻塞状态。

主线程的Looper是怎么判断接收的消息由那个Handler传来的?

handler在sendMessage时,执行消息入队函数enqueue()时,会将handler对象放在Message的target里边:msg.target = this;,这样Looper就可以根据Message中的target判断当前message由哪个handler传来。

postDelayed实现原理

1.消息是通过MessageQueen中的enqueueMessage()方法加入消息队列中的,并且它在放入中就进行好排序,链表头的延迟时间小,尾部延迟时间最大

2.Looper.loop()通过MessageQueue中的next()去取消息

3.next()中如果当前链表头部消息是延迟消息,则根据延迟时间进行消息队列会阻塞,不返回给Looper message,知道时间到了,返回给message

4.如果在阻塞中有新的消息插入到链表头部则唤醒线程

5.Looper将新消息交给回调给handler中的handleMessage后,继续调用MessageQueen的next()方法,如果刚刚的延迟消息还是时间未到,则计算时间继续阻塞

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重启任务线程

IntentService

IntentService有以下特点:

  • IntentService继承自Service,可以用startService启动,也需要在AndroidManifest.xml中注册
  • IntentService在一个单独的worker线程中处理任务,任务完成后,会自动停止。
  • 可以多次启动同一个IntentService,但只会在第一次启动时创建service实例(这与Service不同,多次启动Service会创建多个实例),多个IntentService请求会一个接一个排队,保证请求执行的顺序性,等待前一个执行完毕,自动执行下一个。

常常使用IntentService执行离线下载任务。

使用步骤

  1. 定义IntentService的子类
    需传入线程名称,复写onHandleIntent()方法。
public class myIntentService extends IntentService {

  /** 
    * 在构造函数中传入线程名字
    **/  
    public myIntentService() {
        // 调用父类的构造函数
        // 参数 = 工作线程的名字
        super("myIntentService");
    }

   /** 
     * 复写onHandleIntent()方法
     * 根据 Intent实现 耗时任务 操作
     **/  
    @Override
    protected void onHandleIntent(Intent intent) {

        // 根据 Intent的不同,进行不同的事务处理
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
            case "task1":
                Log.i("myIntentService", "do task1");
                break;
            case "task2":
                Log.i("myIntentService", "do task2");
                break;
            default:
                break;
        }
    }

    @Override
    public void onCreate() {
        Log.i("myIntentService", "onCreate");
        super.onCreate();
    }
   /** 
     * 复写onStartCommand()方法
     * 默认实现 = 将请求的Intent添加到工作队列里
     **/  
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}
  1. 在Mainifest.xml中注册服务
<service android:name=".myIntentService">
            <intent-filter >
                <action android:name="cn.scu.finch"/>
            </intent-filter>
        </service>

  1. 在Activity中开启Service。
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

            // 同一服务只会开启1个工作线程
            // 在onHandleIntent()函数里,依次处理传入的Intent请求
            // 将请求通过Bundle对象传入到Intent,再传入到服务里

            // 请求1
            Intent i = new Intent("cn.scu.finch");
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            // 请求2
            Intent i2 = new Intent("cn.scu.finch");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);

            startService(i);  //多次启动
        }
    }

工作原理

接下来,我们将通过 源码分析 解决以下问题:

  • IntentService 如何单独开启1个新的工作线程
  • IntentService 如何通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中

在IntentService源码的onCreate()方法中,可以看到它实例化了一个内部封装了Looper的HandlerThread,并构建了一个Handler绑定到自己的循环器。也就是说IntentService内部有工作线程,且有自己的Looper循环器,维护者自己的工作队列,也是通过Handler来处理信息。

@Override
public void onCreate() {
    super.onCreate();
    
    // 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
    // HandlerThread继承自Thread,内部封装了 Looper
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
  
    // 2. 获得工作线程的 Looper & 维护自己的工作队列
    mServiceLooper = thread.getLooper();

    // 3. 新建mServiceHandler & 绑定上述获得Looper
    // 新建的Handler 属于工作线程 ->>分析1
    mServiceHandler = new ServiceHandler(mServiceLooper); 
}


   /** 
     * 分析1:ServiceHandler源码分析
     **/ 
     private final class ServiceHandler extends Handler {

         // 构造函数
         public ServiceHandler(Looper looper) {
         super(looper);
       }

        // IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
        @Override
         public void handleMessage(Message msg) {
  
          // onHandleIntent 方法在工作线程中执行
          // onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
          onHandleIntent((Intent)msg.obj);
          // 执行完调用 stopSelf() 结束服务
          stopSelf(msg.arg1);

    }
}

   /** 
     * 分析2: onHandleIntent()源码分析
     * onHandleIntent() = 抽象方法,使用时需重写
     **/ 
      @WorkerThread
      protected abstract void onHandleIntent(Intent intent);

那么IntentService是如果将Intent传递给服务,并且插入到工作队列中去的呢?

在下面的源码可以看到,在IntentService的onStart()这个阶段,会获取IntentService的消息引用message,并把Intent参数封装到message的obj中,然后发送消息,即把消息添加到Looper的消息队列中。


/** 
  * onStartCommand()源码分析
  * onHandleIntent() = 抽象方法,使用时需重写
  **/ 
  public int onStartCommand(Intent intent, int flags, int startId) {

    // 调用onStart()->>分析1
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

/** 
  * 分析1:onStart(intent, startId)
  **/ 
  public void onStart(Intent intent, int startId) {

  	// 1. 获得ServiceHandler消息的引用
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;

    // 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
    //这里的Intent  = 启动服务时startService(Intent) 里传入的 Intent
    msg.obj = intent;

    // 3. 发送消息,即 添加到消息队列里
    mServiceHandler.sendMessage(msg);
}

原理总结

从上边的源码可以看出:IntentService的本质 = Handler + HandlerThread;

IntentService创建了一个拥有Looper循环器的工作线程HandlerThread,为其绑定一个Handler到循环器Looper上,每次启动一个IntentService时,将Intent参数封装到message,然后发送到工作线程的Looper所维护的消息队列中。

注意事项

  1. 工作任务队列 = 顺序执行:即如果一个任务正在IntentService中执行,此时再发送一个新的任务请求会一直等待前面一个任务执行完毕后再执行。若这个过程中服务停止,则会清除消息队列中的消息,后续事件不再执行。

  2. 不建议通过bindService()启动IntentService

因为从源码中可以看到,封装Intent参数到message是在onStartCommand()和onStart()这两个方法中实现的。

但是绑定式服务的生命周期是:
onCreate() -> onBind() ->onUnbind -> onDestroy()

因此Intent无法传递给工作线程的Looper。

应该使用启动式服务,其生命周期为:
onCreate()-> onStartCommond()->onstart() -> onDestroy()

数据存储

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");
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值