多线程在Android中的应用以及线程间的通信

4 篇文章 0 订阅
4 篇文章 0 订阅

多线程在安卓中的应用还是挺多的,很多耗时操作,为了避免防止影响用户体验和阻塞线程,我们一般要把这些操作放在线程里面,很多人会把线程和进程弄混,其实只是两个区别很大的概念:

1.进程:安卓系统会给一个程序开启一个进程,而这个进程在程序中有一个组件开启的时候进行分配,当有组件被激活就会被分配一个进程,在已经分配了进程的情况下,再开启其他的组件是不会继续分配进程的,一个程序占有一个进程。
2.线程:线程是程序中的调度单位,进程通过调度线程来完成一系列的事情,在一个进程中可以有多个线程,每一个线程内部都跟其他并列的线程间独立分享各自的栈内存,但是所有的线程之间又共享这个大进程的堆内存。

通常我们在代码中实现多线程有两种方式:

1.实现Runnable接口。
2.继承Thread类。
//通过继承Runnable方式实现一个子线程
public static class BRunnable implements Runnable{

        @Override
        public void run() {
            for( int i = 0 ; i < 10 ; i ++){
                System.out.println("BRunnable : "+ i);
            }
        }
    }
但是开启线程的唯一方式就是Thread.start();所以我们在拿到了Runnable对象之后还要把他创建Thread对象
    Thread BThread = new Thread(new BRunnable()) ;
    BThread.start();
这样就开启了一个Runnable实现的子线程了。接下来就看看看继承Thread方式开启线程。
public static class AThread extends Thread{
        String name ;
        public AThread(String name){
            this.name = name ;
        }
        @Override
        public void run() {
            for( int i = 0 ; i < 10 ; i ++){
                System.out.println(name + "  "+ i);
            }
        }
    }

//测试下运行效果
public static void main(String[] args) {
        AThread threadA = new AThread("A") ;
        AThread threadB = new AThread("B") ;
        Thread BThread = new Thread(new BRunnable()) ;
        threadA.start() ;
//      threadB.start() ;
        BThread.start() ;
    }

这里写图片描述

从运行结果来看,虽然A线程在前面,但是B线程并没有等A执行完毕再执行自己的任务。线程之间是个并列的竞争关系,并不是阻塞进程的,他们运行在自己的进程里面。已经了解完一些基本的线程知识了,要了解安卓中的应用,我们还需要对安卓中的handler来了解一下。
handler:消息处理机制,线程间通过handler来进行消息的传递和处理。在主线程中创建一个handler对象,在线程中触发事件的时候,可以通过handler.post(runnable , delay)来处理事件,也可以用通过handler发送一个message到message queen消息队列中,handler的looper轮询器不断的轮询这个消息队列,扫描到message之后将会发送给handler的handlemessage方法进行处理,之所以需要这样是因为我们在子线程中不能修改主线程的控件,我们经常在完成一个操作之后,需要对控件进行刷新,但是UI控件都是存在于主线程的,子线程无法修改,但是用handler.post(runnable , delay)来处理是代码过于复杂且不易维护,因此我们大多数情况下都是通过handler对象的帮助进行消息的传递和处理的。
下面通过之前写过的一个歌曲扫描的例子来说明
    /**
     * The columns choose to get from your phone
     */
    private String[] projection = {Media._ID, Media.DISPLAY_NAME, Media.DATA, Media.ALBUM, Media.ARTIST, Media.DURATION, Media.SIZE, Media.ALBUM_ID};
    private Uri contentUri = Media.EXTERNAL_CONTENT_URI;
    /**
     * A thread to scan the music in your phone
     */
    class ScanMusicThread extends Thread {
        @Override
        public void run() {
            super.run();
            MusicInfo musicInfo;
            Cursor cursor = resolver.query(contentUri, projection, where, null, sortOrder);
            if (cursor != null) {
                while (cursor.moveToNext() && IS_CONTINUE_SCAN) {
                    String title = cursor.getString(cursor.getColumnIndex(Media.DISPLAY_NAME));
                    String album = cursor.getString(cursor.getColumnIndex(Media.ALBUM));
                    long albumID = cursor.getLong(cursor.getColumnIndex(Media.ALBUM_ID));
                    long id = cursor.getLong(cursor.getColumnIndex(Media._ID));
                    int duration = cursor.getInt(cursor.getColumnIndex(Media.DURATION));
                    long size = cursor.getLong(cursor.getColumnIndex(Media.SIZE));
                    String artist = cursor.getString(cursor.getColumnIndex(Media.ARTIST));
                    String path = cursor.getString(cursor.getColumnIndex(Media.DATA));

                    musicInfo = new MusicInfo(id, albumID, title, artist, duration, path, IS_FAVORITE);
                    musicList.add(musicInfo);

                    if (mHandler != null && SongScanActivity.this != null) {
                        try {
                            Thread.sleep(UPDATE_MUSIC_INFO_DURATION);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Message msg = Message.obtain();
                        msg.obj = musicInfo;
                        msg.what = UPDATE_MESSAGE;
                        mHandler.sendMessage(msg);
                    }
                }

            }
        }
    }
通过一个内容解析者来访问手机自身暴露数来的音乐数据,在这个访问音乐的过程中,是一个比较耗时的过程,是一个将会阻塞主线程的操作,所以我把他放在了一个线程里面,在扫描到音乐的时候,我们要通过界面来显示歌曲名和歌手来显示当前的扫描情况,所以这个涉及到了主线程UI的修改,我们在主线程创建了一个handler对象,并且重写了里面的handleMessage方法来处理。
private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case UPDATE_MESSAGE:
                    int insertCount = 0;
                    MusicInfo tempMusic = (MusicInfo) msg.obj;
                    if (SongScanActivity.this != null) {
                        songCount++;
                        scancount_tv.setText(String.valueOf(songCount));
                        scannow_tv.setText(tempMusic.getTitle() + "--" + tempMusic.getPath());
                        //insert the music that not into database into database
                        if (mifDao.load(tempMusic.getSongId()) == null) {
                            daoSession.insert(tempMusic);
                            insertCount++;
                        }
                        if (SweetApplication.DEBUG) {
                            Log.i("com.cvil.debug", String.valueOf(insertCount));
                        }
                    }
                    break;
            }

        }
    };
还有一种比较危险的情况就是在你退出当前界面之后,这个线程并不会停止它的工作,它将继续扫描你手机里面的音乐,而如果这时候你退出了这个界面,它继续发送消息给handler,handler在进行UI的改变的时候无法找到控件导致控指针异常,这时候又涉及到了线程的控制,我们在扫描的时候, while (cursor.moveToNext() && IS_CONTINUE_SCAN)可以看到在循环的时候有这个全局变量IS_CONTINUE_SCAN的控制,只有在true的时候才继续循环,否则就退出循环,一旦推出循环,那么这个线程将结束它的工作,从而进入一种睡眠或者说杀死的状态,这时候就不会继续消息的发送以及悲剧的导致。这个变量我在onDestroy方法中进行了控制。
@Override
    protected void onDestroy() {
        super.onDestroy();
        IS_CONTINUE_SCAN = false;
        try {
            Thread.sleep(UPDATE_MUSIC_INFO_DURATION);//wait for the thread stop
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scanMusicThread.interrupt();  //interrupt the thread to stop the work of the thread
    }
当界面被销毁的时候,这个成员变量将会被复制false,停止子线程中的循环。从而保证界面的安全刷新。
以下附上完整代码:
package com.huwei.sweetmusicplayer;


import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore.Audio.Media;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.huwei.sweetmusicplayer.dao.DaoSession;
import com.huwei.sweetmusicplayer.dao.MusicInfoDao;
import com.huwei.sweetmusicplayer.models.MusicInfo;

import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;

import java.util.ArrayList;
import java.util.List;

@EActivity(R.layout.activity_songscan)
public class SongScanActivity extends BaseActivity {

    private static final boolean IS_FAVORITE = false;
    private List<MusicInfo> musicList = new ArrayList<>();
    private Uri contentUri = Media.EXTERNAL_CONTENT_URI;
    private ContentResolver resolver;
    private ScanMusicThread scanMusicThread;
    private int songCount = 0;
    /**
     * The columns choose to get from your phone
     */
    private String[] projection = {Media._ID, Media.DISPLAY_NAME, Media.DATA, Media.ALBUM, Media.ARTIST, Media.DURATION, Media.SIZE, Media.ALBUM_ID};

    /**
     * filter condition
     */
    String where = "mime_type in ('audio/mpeg','audio/x-ms-wma')  and is_music > 0";

    /**
     * sort order
     */
    private String sortOrder = Media.DATA;

    private DaoSession daoSession;
    private MusicInfoDao mifDao;

    private static final int UPDATE_MESSAGE = 0;
    private static final int UPDATE_MUSIC_INFO_DURATION = 200;
    private boolean IS_CONTINUE_SCAN = true;

    @ViewById
    TextView scannow_tv;
    @ViewById
    TextView scancount_tv;
    @ViewById
    Button scanfinish_btn;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case UPDATE_MESSAGE:
                    int insertCount = 0;
                    MusicInfo tempMusic = (MusicInfo) msg.obj;
                    if (SongScanActivity.this != null) {
                        songCount++;
                        scancount_tv.setText(String.valueOf(songCount));
                        scannow_tv.setText(tempMusic.getTitle() + "--" + tempMusic.getPath());
                        //insert the music that not into database into database
                        if (mifDao.load(tempMusic.getSongId()) == null) {
                            daoSession.insert(tempMusic);
                            insertCount++;
                        }
                        if (SweetApplication.DEBUG) {
                            Log.i("com.cvil.debug", String.valueOf(insertCount));
                        }
                    }
                    break;
            }

        }
    };

    @ViewById
    Toolbar toolbar;

    @AfterViews
    void init() {
        toolbar.setVisibility(View.VISIBLE);
        toolbar.setTitle("歌曲扫描");
        toolbar.setNavigationIcon(R.drawable.mc_back);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {                                                                                                                                                                                                                                                                                                               
                onBackPressed();
            }
        });
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        resolver = getContentResolver();
        daoSession = SweetApplication.getDaoSession();
        mifDao = daoSession.getMusicInfoDao();
        scanMusicThread = new ScanMusicThread();
        scanMusicThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        IS_CONTINUE_SCAN = false;
        try {
            Thread.sleep(UPDATE_MUSIC_INFO_DURATION);//wait for the thread stop
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scanMusicThread.interrupt();  //interrupt the thread to stop the work of the thread
    }

    /**
     * A thread to scan the music in your phone
     */
    class ScanMusicThread extends Thread {
        @Override
        public void run() {
            super.run();
            MusicInfo musicInfo;
            Cursor cursor = resolver.query(contentUri, projection, where, null, sortOrder);
            if (cursor != null) {
                while (cursor.moveToNext() && IS_CONTINUE_SCAN) {
                    String title = cursor.getString(cursor.getColumnIndex(Media.DISPLAY_NAME));
                    String album = cursor.getString(cursor.getColumnIndex(Media.ALBUM));
                    long albumID = cursor.getLong(cursor.getColumnIndex(Media.ALBUM_ID));
                    long id = cursor.getLong(cursor.getColumnIndex(Media._ID));
                    int duration = cursor.getInt(cursor.getColumnIndex(Media.DURATION));
                    long size = cursor.getLong(cursor.getColumnIndex(Media.SIZE));
                    String artist = cursor.getString(cursor.getColumnIndex(Media.ARTIST));
                    String path = cursor.getString(cursor.getColumnIndex(Media.DATA));

                    musicInfo = new MusicInfo(id, albumID, title, artist, duration, path, IS_FAVORITE);
                    musicList.add(musicInfo);

                    if (mHandler != null && SongScanActivity.this != null) {
                        try {
                            Thread.sleep(UPDATE_MUSIC_INFO_DURATION);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Message msg = new Message();
                        msg.obj = musicInfo;
                        msg.what = UPDATE_MESSAGE;
                        mHandler.sendMessage(msg);
                    }
                }

            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值