一路追梦想

热爱技术,更热爱生活


  • 首页

  • 关于

  • 标签

  • 归档

Android中通过HandlerThread实现真正的多线程

发表于 2013-10-23 | 分类于 Android

上一篇博客中我在解释Handler的过程中提到了HandlerThread,我们先回顾一下上一篇博客中对于Handler的定义:Handler会关联一个单独的线程和消息队列。Handler默认关联主线程,虽然要提供Runnable参数,但默认是直接调用Runnable中的run()方法。也就是默认下会在主线程执行,如果在这里面的操作会有阻塞,界面也会卡住。

也就是说如果我们使用Handler提供的方法handler.post(new Runnable())时候,如果使用不当的话一样会出现界面卡死,因为其默认管线的还是主线程,也就是说其还是会在主线程中简单地调用Runnable中的run()方法,这样耗时任务虽然在run()方法中,但是还是运行在主线程,所以同样会阻塞UI,接下来我们来看两个例子,就会更明白了,第一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package cn.picksomething.testhandler;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

public class MainActivity extends Activity {

private String TAG = "TAG";
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    handler.post(new myRunnable());
    Log.d(TAG, "onCreate-----The thread id is:" + Thread.currentThread().getId());
    setContentView(R.layout.main);
}

class myRunnable implements Runnable{
    @Override
    public void run() {
        Log.d(TAG, "run method is calling----");
        Log.d(TAG, "The thread id is:" + Thread.currentThread().getId());
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
}

运行上述程序,程序启动6秒之后,才看到布局被加载,显示出TextView上的Hello World!字样

分析一下程序执行的过程,首先程序启动,就把myRunnable加入到当前线程的消息队列中,也就是handler默认关联的主线程消息队列,android的handler确实是异步机制,所以在handler.post(new myRunnable());之后,程序会继续执行,所以以后的语句会继续执行,会输出onCreate中的当前线程ID。但同时myRunnable()的run方法也在运行,一样输出run方法中的当前线程ID,然后让当前线程休眠6秒

通过输出结果和分析,我们可以得出handler和主线程是同一个线程,handler也是运行在主线程中的,这个时候若是执行耗时任务,显然会阻塞UI。当然肯定有解决办法的:

Android给我们提供了Looper这样一个类,上一遍博客中已经介绍过了,Android中每一个Thread都跟着一个Looper,Looper可以帮助Thread维护一个消息队列,每个Thread都只有一个消息队列。我们看看另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package cn.picksomething.testlooper;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.app.Activity;
import android.util.Log;

public class MainActivity extends Activity {

private String TAG = "TAG";
private Handler handler = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    HandlerThread mThread = new HandlerThread("handlerThread");
    mThread.start();
    handler = new Handler(mThread.getLooper());
    handler.post(new myRunnable());
    Log.d(TAG, "onCreate-----The Thread id is: " + Thread.currentThread().getId());
    setContentView(R.layout.main);
}

class myRunnable implements Runnable{
@Override
    public void run() {
    Log.d(TAG, "myRunnable is running----");
        Log.d(TAG, "The thread id is: " + Thread.currentThread().getId());
        try {
        Thread.sleep(6000);
        } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
}
}

运行上述程序,过滤打印信息,我们看到LogCat输出如下结果:

1
2
3
onCreate-----The Thread id is: xxx
myRunnable is running----
The thread id is: xxx

同时我们看到程序已启动,就立即输出了布局文件中的Hello world!信息。

在这个例子中,用到了HandlerThread,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将这个Looper对象映射到一个Handler中来实现一个线程同步机制,所以就会有上面的结果了。这样就达到了多线程的效果

补充:HandlerThread这个类,HandlerThread继承于Thread,所以它本质就是个Thread。HandlerThread类用于方便的创建一个含有looper的线程类,looper用来创建handler类。我们一般不创建looper对象,直接调用HandlerThread即可,HandlerThread本身实现了循环处理消息的功能,不用再直接调用Looper.prepare()方法,与普通Thread的差别就在于它有个Looper成员变量,这个Looper其实就是对消息队列以及队列处理逻辑的封装,简单说就是消息队列+消息循环

Android在非UI线程中更新UI的方法与扩展

发表于 2013-10-23 | 分类于 Android

很多时候我们开发Android程序的时候都会遇到一些耗时的操作,比如获取网络资源啊,比如截图并保存啊等等,我们不可能把这些耗时的任务放到主线程中来完成,因为那样的话很容易就会出现ANR,那么我们一般的思路应该就是开辟一个新的线程来执行耗时操作,举个例子:我们需要获取某深圳的天气预报,界面上有一个Button,点击Button开始获取,还有一个TextView控件用于显示获取的天气,获取天气是一个耗时操作,我们应该单独开一个线程,于是很容易想到这么做:

1
2
3
4
5
6
7
8
9
10
11
12
public void onClick(View v) {
//创建一个子线程执行耗时的从网络上获得天气信息的操作
new Thread() {
    @Override
    public void run() {
        //调用Google中的结构获取天气信息
        String weather = getWetherByCity("深圳");
        //把图片显示到ImageView中
        textView.setText(weather.toString());
    }
}.start();
}

但是很不幸,你会发现Android会提示程序由于异常而终止。看起来合情合理的一段代码为什么会出错呢?这是因为出于性能考虑,Android的UI操作并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能导致线程安全问题,为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件

本例中显示天气信息的textView实际是就是一个由UI线程所创建的TextView,所以试图在一个子线程中去更改TextView的肯定会报错了,在这种机制下,为了解决类似的问题,Android设计了一个MessageQueue(消息队列),线程间可以通过该MessageQueue并结合Handler和线程的Looper组件进行信息交换,简单介绍一下:

阅读全文 »

onTextChanged参数解释及实现EditText字数监听

发表于 2013-10-18 | 分类于 Android

由于最近做项目要检测EditText中输入的字数长度,从而接触到了Android中EditText的监听接口,TextWatcher。
它有三个成员方法,第一个after很简单,这个方法就是在EditText内容已经改变之后调用,重点看下面两个方法:

1
beforeTextChanged(CharSequence s, int start, int count, int after)

这个方法是在Text改变之前被调用,它的意思就是说在原有的文本s中,从start开始的count个字符将会被一个新的长度为after的文本替换,注意这里是将被替换,还没有被替换

1
onTextChanged(CharSequence s, int start, int before, int count)

这个方法是在Text改变过程中触发调用的,它的意思就是说在原有的文本s中,从start开始的count个字符替换长度为before的旧文本,注意这里没有将要之类的字眼,也就是说一句执行了替换动作

可能说起来比较抽象,我举个简单的例子,比如说我们监听一个EditText,默认开始的时候EditText中没有文本,当我们输入LOVE四个字母的时候,在打印信息中我输出各个参数看一下参数的变化。

阅读全文 »

Android中Activity的四种加载模式

发表于 2013-09-17 | 分类于 Android

之前看Android深入理解Activity和Fragment这一章的时候,看到过Activity的四种加载模式,当时一方面觉得有点晦涩难懂,一方面觉得可能在以后的开发过程中用不到,所以就没有怎么细看。今天在看师兄做的项目代码时,发现在配置文件中都会指定加载模式android:launchMode属性。问了一下师兄,才知道这和属性和效率是有点关系的,于是又回头看了一下,顺便做下笔记,方便以后不懂的时候再过来看看。
配置Activity的时候可以为其指定加载模式,Activity有四种加载模式:

  • standard:标准模式,这是默认的加载模式

  • singleTop:Task顶单例模式

  • singleTask:Task内单例模式

  • singleInstance:全局单例模式

介绍加载模式之前先介绍Android对Activity的管理:Android采用Task来管理多个Activity,当我们启动一个应用时,Android就会为之创建一个Task,然后启动这个应用的入口Activity

阅读全文 »

对于Java中正则表达式的一点理解

发表于 2013-09-02 | 分类于 Java

因为以后要搞安卓开发,以前又没怎么学习过Java,趁着现在没有正式进入部门之前就想先把Java在重新学习一下,为自己以后的职业道路做好充分准备

我们都知道正则表达式在编程中是经常使用的,判断邮箱格式是否正确,判断某一个字符串是否包含某一个子串,字符串的分割和替换等等,正确地使用正则表达式在很多场合都会给我们带来很多方便和效率,我就根据自己看书和理解,以及到网上发帖询问获得的知识,来简单地谈一下关于正则表达式。
在使用正则表达式之前,我们需要导入java.util.regex这个库,你需要先使用Pattern.compile()方法编译正则表达式,注意这是一个静态方法(类方法,可以直接使用类名调用),它根据输入的字符串参数生成一个Pattern对象,例如:

1
private static Pattern p = Pattern.compile("\\w+\\.");

阅读全文 »

Ubuntu下git的配置和使用

发表于 2013-08-25 | 分类于 Git , Linux

现在进入部门了,有了自己的办公桌和电脑,感觉还是很棒的。虽然还是在轮岗期,不过部门负责人还是给我们新员工每人分配了一个课题,就是在两周的时间做一个安卓小应用。由于平时轮岗也有很多事要做,所以每天在公司写一些代码,然后用优盘拷贝回宿舍继续写。觉得这样挺麻烦的,想起以前用github保存代码,正好现在我还是在用ubuntu,就打算继续使用github来保存。因为以前配置在ubuntu下配置git的时候有大神fookwood的帮助,现在只能自己摸索了,网上搜了很多,不过最后都有各种问题,今天夜里又重新整理了一下,终于配置成功了,现在把过程写下来,分享给需要的,也备以后自己参考。
准备工作:首先要现在github上注册一个帐号,右上角有个sign up注册帐号,要是已经有了就点sign in直接登录就好。登录成功之后点击右上角一个图标(create a new repo)新建一个代码仓库就是存放你以后项目的仓库(repositories),当然可以创建很多个,这个都应该会,就不多说了。下面开始安装和配置过程了。

安装git,后面的-y是打包安装这三个程序

1
sudo apt-get install git-core git-gui git-doc -y

生成本地SSH密匙(这是关键的一步)

如果用户是第一次使用git,就需要现在家目录创建一个.ssh文件夹,生成密匙用的默认文件夹,由于是隐藏的,用户可以在家目录按Ctrl+h来显示隐藏文件夹,看看是否已经有这个.ssh文件夹,如果没有,就打开终端输入如下命令:

1
mkdir ~/.ssh

PS:要是没有创建之前已经有的.ssh目录的话,切换到.ssh,用ls命令查看目录下是否有id_rsa.pub与id_rsa两个文件,有的话就代表密匙已经生成过,就不需要再执行生产密匙的步骤。

创建好目录之后,就开始输入如下命令生成密匙。

1
ssh-keygen -t rsa -C "your_email@example.com"

注意,为了保证是默认设置,当提示让输入保存密匙的文件的时候,直接按回车键就可以了。之后会让输入密码,和确认密码,可以输要保证两次一样,也可以都按回车键不输密码。执行结束之后终端会输出如下信息。

1
2
3
4
Your identification has been saved in /home/you/.ssh/id_rsa.
# Your public key has been saved in /home/you/.ssh/id_rsa.pub.
# The key fingerprint is:
# 01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com

阅读全文 »

入职感受

发表于 2013-08-03 | 分类于 Career

因为到公司报道之后,每天都在忙着培训,周末都没有,所以也一直没有办网。现在入职培训算是告一段落了,这两天又正好休息,今天就去电信把网办了,然后趁现在有时间写一写入职感受吧。

说实话入职半个多月了,从整体上并没有觉得和在学校有多大的差别,至少从很多人的思想和做法上我看到他们依然把现在当做在学校一样,不过我自己在内心已经做好了足够的准备,时刻告诉自己:我已经不是一名学生了,要做一名合格的职业人。

入职的这半个月,每天的日程都是满满的,集训开营,签约,军事训练,篮球赛,辩论赛,户外运动,参观公司,彩排节目,欢乐谷游玩,汇报演出,再加上各种课程培训,总之每一天都是早上七点到夜里九点多。不过说实话现在回想起来,觉得这半个多月的集训还是很让我记忆深刻的,我觉得对我以后的职业发展和规划会有很大的帮助。首先认识了很多人,无论是研发的,还是销售的以及财务的,朋友是一笔无形的财富;其次,真的学到了很多,公司文化培训,安全知识培训,特别是职业人第一课和礼仪培训更是让我获益匪浅,受用终生。最后,在各种活动中也收获到了很多东西,给家人的一封信,让我学会了感恩和责任,篮球赛,户外运动让我体会到了团结协作的重要性,军训让我明白坚持和高效的执行力的重要等等。唯一有点遗憾的就是培训过程中自己一方面是有点内向,不太爱和别人交流,另一方面自己在培训期间也生病了,所以对于自己的表现并不是很满意。

阅读全文 »

Android中使用百度SDK获得经纬度和位置信息

发表于 2013-05-04 | 分类于 Android

自己的毕业设计选择的是开发一个美食分享的客户端,由于也是刚开始接触安卓开发,所以在做毕设的过程中遇到了很多问题,不过每当解决一个问题的之后,感觉收获还是很大的,有很多问题自己都想些在博客里,供自己和网友参考,但是由于快答辩了,一直在完善毕设,所以就没有时间写,等答辩完,再一一整理,写在这里。
今天解决了关于使用百度SDK获取经纬度和位置信息这方面的内容,由于毕设有一点需要获取当前的位置,但是不需要显示百度地图,直接把位置信息显示出来就行,刚开始接触百度SDK,很多知识都不是很熟悉,就从网上找,但是发现大部分获取的位置信息返回的都是null,最后才发现少了一句option.setAddrType("all");
下面贴出代码,供网友参考,首先是布局文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
    android:id="@+id/btn_start"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    android:text="Start"/>
 <TextView
    android:id="@+id/tv_loc_info"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp" />
</LinearLayout>

阅读全文 »

Android中Listview重复显示搜索结果

发表于 2013-04-29 | 分类于 Android

最近做毕设,在写搜索这一功能模块的时候发现一个问题,我是从数据库将美食信息搜索出来并显示在ListView中。写完之后,测试的时候发现虽然搜索结果能够正常显示,但是当进行下一次搜索的时候,上一次的搜索结果还会显示,从网上搜了很多,但是大部分都是说清空ListView,问题就是如果选择清空ListView的话,一方面如果不判断就清空的话,你会发现搜索结果就不会显示出来。另一方面,如果判断ListView是否为空,再清空的话,结果一样无法显示。
我想原因是ListView只是一个控件。不应该对其进行判空,而应该先对填充其中的数据进行判断是否为空。如果不为空,先清空,然后再进行填充数据和显示。下面贴上我的代码。
我首先是声明了一个ArrayList来存放从数据库中取出的数据,同时也是ListView的数据源:

1
ArrayList<Map<String, Object>> SearchResults = new ArrayList<Map<String, Object>>();

ArrayList中是一个Map键值对类型的元素,用于存放数据库中相应字段的值,然后就是关键的判断了:

1
2
3
if(!(SearchResults.isEmpty())){
SearchResults.removeAll(SearchResults);
}

这个就会先判断ArrayList中的数据是否为空,如果不为空,就代表上一次搜索中的数据还在里面,我们就先进行一个清空操作,那样在进行下一次操作的时候就不会出现问题了。其它从数据库中获取,已经使用适配器显示到ListView中的代码网上有很多,我就不贴出来了,如果有需要的或者有什么问题的,可以留言

Android中关于Handler的简单总结

发表于 2013-04-07 | 分类于 Android

Handler的定义:

主要接受子线程发送的数据, 并用此数据配合主线程更新UI

解释: 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 “强制关闭”. 这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程是不安全的,也就是说,更新UI只能在主线程中更新,子线程中操作是危险的. 这个时候,Handler就出现了.,来解决这个复杂的问题 , 由于Handler运行在主线程中(UI线程中), 它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象,(里面包含数据) , 把这些消息放入主线程队列中,配合主线程进行更新UI

Handler一些特点

handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程),
它有两个作用:

  1. 安排消息或Runnable 在某个主线程中某个地方执行,
  2. 安排一个动作在不同的线程中执行

Handler中分发消息的一些方法

1
2
3
4
5
6
7
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)

以上post类方法允许你排列一个Runnable对象到主线程队列中,sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新

Handler实例

子类需要继承Hendler类,并重写handleMessage(Message msg) 方法, 用于接受线程数据,以下为一个实例,它实现的功能是通过线程修改界面Button的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class MyHandlerActivity extends Activity {
Button button;
MyHandler myHandler;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.handlertest);

    button = (Button) findViewById(R.id.button);
    myHandler = new MyHandler();
    // 当创建一个新的Handler实例时, 它会绑定到当前线程和消息的队列中,开始分发数据
    // Handler有两个作用, (1) : 定时执行Message和Runnalbe 对象
    // (2): 让一个动作,在不同的线程中执行.

    MyThread m = new MyThread();
    new Thread(m).start();
}

/**
 * 接受消息,处理消息 ,此Handler会与当前主线程一块运行
 **/

class MyHandler extends Handler {
    public MyHandler() {
    }

    public MyHandler(Looper L) {
        super(L);
    }

    // 子类必须重写此方法,接受数据
    @Override
    public void handleMessage(Message msg) {
        // TODO Auto-generated method stub
        Log.d("MyHandler", "handleMessage......");
        super.handleMessage(msg);
        // 此处可以更新UI
        Bundle b = msg.getData();
        String color = b.getString("color");
        MyHandlerActivity.this.button.append(color);
    }
}

class MyThread implements Runnable {
    public void run() {

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Log.d("thread.......", "mThread........");
        Message msg = new Message();
        Bundle b = new Bundle();// 存放数据
        b.putString("color", "我的");
        msg.setData(b);

        MyHandlerActivity.this.myHandler.sendMessage(msg); // 向Handler发送消息,更新UI

    }
}
}

总结:这些都是安卓开发的难点,要多理解,多操作。

1…567
picksomething

picksomething

专注Android,Java,RN,Python

65 日志
17 分类
148 标签
RSS
© 2018 PICKSOMETHING
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4