AutoCompleteTextView实现简单的输入提示功能

 更新时间:2016年9月20日 19:53  点击:2176
AutoCompleteTextView就是类是百度搜索引擎中的一个下拉框了,就是我们输入内容时自动匹配内容出来了,下面我们一起来看看小编整理的例子。
我们在使用各种搜索引擎的搜索功能时,只需要输入一部分内容,就会显示出后续内容,比如,我们输入一个“1”,下面就会出现”12306″,”123″,”126″等等的提示内容,我们可以快捷选择自己想要搜索的内容进行搜索.如下图:

今天我所写的只是一个简单的,不连接数据库,不访问网络的一个输入提示框,方法如下:

第一步:建立XML文件并照下图编写代码:

图中的 android:completionThreshold=”1″代表我们输入几个字符时才会提示,设置为2的话,我们需要输入两个字符才会提示后面的内容.

第二步:在Activity中编写如下代码:

第三部:运行模拟器:

可以看出,成功的完成了输入提示功能,但是这只是最简单的,后续如果项目需要,还需要做更多复杂的操作,如界面优化等,Android的道路还很远,我和大家一起努力!

上面是一个规定的并没有词库的,下面升级一下。

下面先上我写的代码:

import android.app.Activity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;

public class Read_historyActivity extends Activity implements
  OnClickListener {
 private AutoCompleteTextView autoTv;

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  autoTv = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
  initAutoComplete("history",autoTv);
  Button search = (Button) findViewById(R.id.button1);
  search.setOnClickListener(this);
 }
 @Override
 public void onClick(View v) {
  // 这里可以设定:当搜索成功时,才执行保存操作
  saveHistory("history",autoTv);
 }

 /**
  * 初始化AutoCompleteTextView,最多显示5项提示,使
  * AutoCompleteTextView在一开始获得焦点时自动提示
  * @param field 保存在sharedPreference中的字段名
  * @param auto 要操作的AutoCompleteTextView
  */
 private void initAutoComplete(String field,AutoCompleteTextView auto) {
  SharedPreferences sp = getSharedPreferences("network_url", 0);
  String longhistory = sp.getString("history", "nothing");
  String[]  hisArrays = longhistory.split(",");
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    android.R.layout.simple_dropdown_item_1line, hisArrays);
  //只保留最近的50条的记录
  if(hisArrays.length > 50){
   String[] newArrays = new String[50];
   System.arraycopy(hisArrays, 0, newArrays, 0, 50);
   adapter = new ArrayAdapter<String>(this,
     android.R.layout.simple_dropdown_item_1line, newArrays);
  }
  auto.setAdapter(adapter);
  auto.setDropDownHeight(350);
  auto.setThreshold(1);
  auto.setCompletionHint("最近的5条记录");
  auto.setOnFocusChangeListener(new OnFocusChangeListener() {
   @Override
   public void onFocusChange(View v, boolean hasFocus) {
    AutoCompleteTextView view = (AutoCompleteTextView) v;
    if (hasFocus) {
      view.showDropDown();
    }
   }
  });
 }

 

 /**
  * 把指定AutoCompleteTextView中内容保存到sharedPreference中指定的字符段
  * @param field  保存在sharedPreference中的字段名
  * @param auto  要操作的AutoCompleteTextView
  */
 private void saveHistory(String field,AutoCompleteTextView auto) {
  String text = auto.getText().toString();
  SharedPreferences sp = getSharedPreferences("network_url", 0);
  String longhistory = sp.getString(field, "nothing");
  if (!longhistory.contains(text + ",")) {
   StringBuilder sb = new StringBuilder(longhistory);
   sb.insert(0, text + ",");
   sp.edit().putString("history", sb.toString()).commit();
  }
<SPAN style="BACKGROUND-COLOR: rgb(240,240,240); FONT-FAMILY: monospace; WHITE-SPACE: pre"> }
}</SPAN>

     上面的代码我实现了autocomplettextview的从sharepreference中读取历史记录并显示的功能,当没有任何输入时,提示最新的5项历史记录(这里可以加个条件,当有历史记录时才显示)
              补上布局的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <TextView android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/hello" />
 <LinearLayout android:layout_width="0px"
  android:layout_height="0px" android:focusable="true"
  android:focusableInTouchMode="true"></LinearLayout>
 <AutoCompleteTextView
  android:hint="请输入文字进行搜索" android:layout_height="wrap_content"
  android:layout_width="match_parent"
  android:id="@+id/autoCompleteTextView1">
 </AutoCompleteTextView>
 <Button android:text="搜索" android:id="@+id/button1"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"></Button>
</LinearLayout>

   当之有一个edittext或者auto的时候,进入画面时是默认得到焦点的,要想去除焦点,可以在auto之前加一个o像素的layout,并设置他先得到焦点。


下面出现的是源码内容

 需要注意的是,我这里用到的AutoCompleteTextView的几个方法

1. setAdapter()方法:这里要传递的adapter参数必须是继承ListAdapter和Filterable的,其中arrayAdapter和simpleAdapter都能满足要求,我们常用arrayAdapter,因为他不需要像simpleAdapte那样设置他的显示位置和textview组件。
               要想掌握它,就必须查看他的源码,我们可以看看arrayadapter是如何实现
              凡是继承了Filterable的adapter都必须重写getFilter接口方法

    public Filter getFilter() {
        if (mFilter == null) {
            mFilter = new ArrayFilter();
        }
        return mFilter;
    }          这个filter 就是实现过滤方法的对象,同样,我们可以查看他的源码是如何实现的
          

 /**
     * <p>An array filter constrains the content of the array adapter with
     * a prefix. Each item that does not start with the supplied prefix
     * is removed from the list.</p>
     */
    private class ArrayFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence prefix) {
            FilterResults results = new FilterResults();

            if (mOriginalValues == null) {
                synchronized (mLock) {
                    mOriginalValues = new ArrayList<T>(mObjects);
                }
            }

            if (prefix == null || prefix.length() == 0) {
                synchronized (mLock) {
                    ArrayList<T> list = new ArrayList<T>(mOriginalValues);
                    results.values = list;
                    results.count = list.size();
                }
            } else {
                String prefixString = prefix.toString().toLowerCase();

                final ArrayList<T> values = mOriginalValues;
                final int count = values.size();

                final ArrayList<T> newValues = new ArrayList<T>(count);

                for (int i = 0; i < count; i++) {
                    final T value = values.get(i);
                    final String valueText = value.toString().toLowerCase();

                    // First match against the whole, non-splitted value
                    if (valueText.startsWith(prefixString)) {
                        newValues.add(value);
                    } else {
                        final String[] words = valueText.split(" ");
                        final int wordCount = words.length;

                        for (int k = 0; k < wordCount; k++) {
                            if (words[k].startsWith(prefixString)) {
                                newValues.add(value);
                                break;
                            }
                        }
                    }
                }

                results.values = newValues;
                results.count = newValues.size();
            }

            return results;
        }
          这是arrayAdapter自定义的一个私有内部类,所谓私有,就意味着你不能通过继承去修改这种过滤方法,同样你也不能直接得到他过滤后结果集results。假如你想使用新的过滤方法,你必须重写getfilter()方法,返回的filter对象是你要新建的filter对象(在里面包含performFiltering()方法重新构造你要的过滤方法)
         
         2.setDropDownHeight方法 ,用来设置提示下拉框的高度,注意,这只是限制了提示下拉框的高度,提示数据集的个数并没有变化
         3.setThreshold方法,设置从输入第几个字符起出现提示
         4.setCompletionHint方法,设置提示框最下面显示的文字
         5.setOnFocusChangeListener方法,里面包含OnFocusChangeListener监听器,设置焦点改变事件
         6.showdropdown方法,让下拉框弹出来
        

        我没有用到的一些方法列举
1.clearListSelection,去除selector样式,只是暂时的去除,当用户再输入时又重新出现
2.dismissDropDown,关闭下拉提示框
3.enoughToFilter,这是一个是否满足过滤条件的方法,sdk建议我们可以重写这个方法
4. getAdapter,得到一个可过滤的列表适配器
5.getDropDownAnchor,得到下拉框的锚计的view的id
6.getDropDownBackground,得到下拉框的背景色
7.setDropDownBackgroundDrawable,设置下拉框的背景色
8.setDropDownBackgroundResource,设置下拉框的背景资源
9.setDropDownVerticalOffset,设置下拉表垂直偏移量,即是list里包含的数据项数目
10.getDropDownVerticalOffset ,得到下拉表垂直偏移量
11..setDropDownHorizontalOffset,设置水平偏移量
12.setDropDownAnimationStyle,设置下拉框的弹出动画
13.getThreshold,得到过滤字符个数
14.setOnItemClickListener,设置下拉框点击事件
15.getListSelection,得到下拉框选中为位置
16.getOnItemClickListener。得到单项点击事件
17.getOnItemSelectedListener得到单项选中事件
18.getAdapter,得到那个设置的适配器

在Android应用开发中,ListView数据的刷新经常用,我们都熟悉,如果我们在ListView实现Item局部刷新,刷新的同时实现下载进度条局部更新呢?本文来告诉你。

对于ListView数据的刷新大家都知道,改变Adapter的数据源,然后调用Adapter的notifyDateSetChanged()方法即可。

但是在做公司项目的时候,有个下载模块,因为可能同时下载好几个数据,所以用的listview展示所有正在下载的内容。因为下载进度要实时更新,所以要不停的调用notifyDateSetChanged刷新数据。这样会不停的重新绘制整个listview的界面,性能开销非常大。而且如果每个item有图片的话,每个item的图片都需要重新加载,就算图片做了内存缓存,刷新一下图片也会闪一下,不停的刷新就会导致各个item的图片不停的闪,体验一点都不好。

那么对于上面问题,有没有解决办法呢?当然是有的。我们可以针对某一个item进行局部更新,而不影响其它没有修改的item。那么具体如何实现的呢?我们看下面的代码。

private void updateView(int itemIndex) {
    //得到第一个可显示控件的位置,
    int visiblePosition = mListView.getFirstVisiblePosition();
    //只有当要更新的view在可见的位置时才更新,不可见时,跳过不更新
    if (itemIndex - visiblePosition >= 0) {
        //得到要更新的item的view
        View view = mListView.getChildAt(itemIndex - visiblePosition);
        //调用adapter更新界面
        mAdapter.updateView(view, itemIndex);
    }
}

这个函数主要是根据传入的itemIndex来获取第itemIndex的数据所显示的view。itemIndex就是要修改的数据再List集合中的位置,比如我这里下载进度有更新,发了一个广播这里接收到了,需要修改该下载内容的进度条,广播接收器可以这么写:

@Override
public void onReceive(Context context, Intent intent) {
    AppContent appContent = intent.getParcelableExtra("appContent");
    if(appContent == null) return;
    int itemIndex = 0;
    for(AppContent appContent1 : mList) {
        if(appContent.getUrl().equals(appContent1.getUrl())) {
            itemIndex = mList.indexOf(appContent1);
            appContent1.setDownloadPercent(appContent.getDownloadPercent());
            break;
        }
    }
    updateView(itemIndex);
}

下面看Adapter的具体代码:

public class AppContentAdapter extends BaseAdapter{

    private List<AppContent> mDates = null;
    private Context mContext;

    public AppContentAdapter(Context context) {
        this.mContext = context;
    }

    @Override
    public int getCount() {
        return mDates.size();
    }

    @Override
    public Object getItem(int position) {
        return mDates.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public void setDates(List<AppContent> mDates) {
        this.mDates = mDates;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = LayoutInflater.from(mContext).inflate(
                    R.layout.listitem_download, null);
            holder.statusIcon = (DownloadPercentView) convertView.findViewById(R.id.status_icon);
            holder.name = (TextView) convertView.findViewById(R.id.name);
            holder.downloadPercent = (TextView) convertView.findViewById(R.id.download_percent);
            holder.progressBar = (ProgressBar) convertView.findViewById(R.id.progressbar);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        setData(holder, position);
        return convertView;
    }

    /**
     * 设置viewHolder的数据
     * @param holder
     * @param itemIndex
     */
    private void setData(ViewHolder holder, int itemIndex) {
        AppContent appContent = mDates.get(itemIndex);
        holder.name.setText(appContent.getName());
        holder.progressBar.setProgress(appContent.getDownloadPercent());
        setIconByStatus(holder.statusIcon, appContent.getStatus());
        if(appContent.getStatus() == AppContent.Status.PENDING) {
            holder.downloadPercent.setVisibility(View.INVISIBLE);
        } else {
            holder.downloadPercent.setVisibility(View.VISIBLE);
            holder.statusIcon.setProgress(appContent.getDownloadPercent());
            holder.downloadPercent.setText("下载进度:" + appContent.getDownloadPercent() + "%");
        }
    }


    /**
     * 局部刷新
     * @param view
     * @param itemIndex
     */
    public void updateView(View view, int itemIndex) {
        if(view == null) {
            return;
        }
        //从view中取得holder
        ViewHolder holder = (ViewHolder) view.getTag();
        holder.statusIcon = (DownloadPercentView) view.findViewById(R.id.status_icon);
        holder.name = (TextView) view.findViewById(R.id.name);
        holder.downloadPercent = (TextView) view.findViewById(R.id.download_percent);
        holder.progressBar = (ProgressBar) view.findViewById(R.id.progressbar);
        setData(holder, itemIndex);
    }

    /**
     * 根据状态设置图标
     * @param downloadPercentView
     * @param status
     */
    private void setIconByStatus(DownloadPercentView downloadPercentView, AppContent.Status status) {
        downloadPercentView.setVisibility(View.VISIBLE);
        if(status == AppContent.Status.PENDING) {
            downloadPercentView.setStatus(DownloadPercentView.STATUS_PEDDING);
        }
        if(status == AppContent.Status.DOWNLOADING) {
            downloadPercentView.setStatus(DownloadPercentView.STATUS_DOWNLOADING);
        }
        if(status == AppContent.Status.WAITING) {
            downloadPercentView.setStatus(DownloadPercentView.STATUS_WAITING);
        }
        if(status == AppContent.Status.PAUSED) {
            downloadPercentView.setStatus(DownloadPercentView.STATUS_PAUSED);
        }
        if(status == AppContent.Status.FINISHED) {
            downloadPercentView.setStatus(DownloadPercentView.STATUS_FINISHED);
        }
    }

    private class ViewHolder {
        private DownloadPercentView statusIcon;
        private TextView name;
        private TextView downloadPercent;
        private ProgressBar progressBar;
    }
}


ListView中 局部刷新Item 实现下载进度条局部更新

当更新当前正在下载的任务的时候,使用 notifyDataSetChanged();方法会使整个页面都会刷新。

而且进度更新比较频繁,这就造成了内存的消耗和页面卡顿(在进度更新很频繁的情况),笔者甚至出现了卡住页面无法进行操作的情况。

所以想到了能不能局部刷新某个Item。也查了下资料,问题解决。

解决思路:

通过listview.getFirstVisiblePosition()方法获取到显示的item的首个位置 ,再根据position, 计算出view的位置。获取到具体的view后,对view进行操作,就能够实现局部刷新了。

关键代码:

 public void updateView(int itemIndex) {  
        //得到第一个可显示控件的位置,  
        int visiblePosition = mListView.getFirstVisiblePosition();  
        //只有当要更新的view在可见的位置时才更新,不可见时,跳过不更新  
        if (itemIndex - visiblePosition >= 0) {  
            //得到要更新的item的view  
            View view = mListView.getChildAt(itemIndex - visiblePosition);  
            //从view中取得holder  
            ViewHolder holder = (ViewHolder) view.getTag();  

            HashMap<String, Object> item = data.get(itemIndex);  
            //获取到具体的控件,
            holder.name = (TextView) view.findViewById(R.id.name);  
            holder.process = (ProcessBar) view.findViewById(R.id.process);  
            .......
            //对控件进行操作
            holder.process.setMax(item.get("max"));
            holder.process.setProgress(item.get("progress"));
            ......
     
        }         
    } 

下面我们一起来看一篇关于 解决:Bitmap too large to be uploaded into a texture exception问题解决办法。

最近做项目发现其他手机没有问题,但是出现了一个手机报异常,最难过的是不显示报错信息,弄了很久,才发现了一句话:Bitmap too large to be uploaded into a texture exception,百度一下才知道怎么回事。简单说就是硬件加速的时候,对图片的大小有限制。不同设备可能有不同的最大值。这个问题悲催的地方是,程序貌似没有捕获到这个exception, 结果是程序也不报错,图片也显示不出来。只有看debug log才能发现这个error message.

一个解决的方法是禁止硬件加速,简单粗暴:

 代码如下 复制代码


<application android:hardwareAccelerated="false" ...>

比较好的解决方法是类似google map的实现:将图片分成不同的块,每次加载需要的块。android提供了一个方法:

http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html

 代码如下 复制代码


public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
public Bitmap decodeRegion (Rect rect, BitmapFactory.Options options)

采取上述操作后,就可以加载很多图片,同时也可以显示超级大图了。

还有用ImageLoad加载图片,如果出现这个问题我们可以这样处理:

可以对ImageView设置最大高度和最大宽度:

 

 代码如下 复制代码
android:maxHeight="1000dip"
 
android:maxWidth="1000dip"

同时相应的scaleType为FIT_CENTER  FIT_XY   FIT_START FIT_END CENTER_INSIDE

在上面这些scaleType下,当bitmap的高和宽一个大于所设的最大值时,imageloader会自动处理,按比例缩放。当scaleType为 MATRIX  CENTER  CENTER_CROP时  要高宽两者都大于所设的最大值时,imageloder才会处理。

下面我们一起来看一篇Android Design Support Library 02 — CollapsingToolbarLayout&&CardView问题解决办法。

Material Design的第二篇更新啦!这次介绍两个控件CollapsingToolbarLayout&&CardView

1、CollapsingToolbarLayout

5.0之后,折叠效果的App出现了,前段时间google在material design的设计中也推出了这个控件。
Ok,还是先上视频!

 代码如下 复制代码
<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="256dp"
        android:fitsSystemWindows="true">
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="#30469b"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
  
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@mipmap/bg"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7"  />
  
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

官方提示,使用CollasingToolbarLayout和Toolbar,title用CollapsingToolbar来设置
This setup uses CollapsingToolbarLayout’s app:layout_collapseMode=”pin” to ensure that the Toolbar itself remains pinned to the top of the screen while the view collapses. Even better, when you use CollapsingToolbarLayout and Toolbar together, the title will automatically appear larger when the layout is fully visible, then transition to its default size as it is collapsed. Note that in those cases, you should call setTitle() on the CollapsingToolbarLayout, rather than on the Toolbar itself.

2、CardView

实现了卡片式的并且有阴影效果。

 代码如下 复制代码

<android.support.v7.widget.CardView
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_margin="@dimen/card_margin">
 
       <LinearLayout
           style="@style/Widget.CardContent"
           android:layout_width="match_parent"
           android:layout_height="wrap_content">
 
           <TextView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="界冢伊奈帆(かいづか いなほ)"
               android:textAppearance="@style/TextAppearance.AppCompat.Title" />
 
           <TextView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="主人公。居住在地..." />
 
           <ImageView
               android:id="@+id/iv_ynf"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content" />
 
       </LinearLayout>
</android.support.v7.widget.CardView>

Android开发语言是Java,所以也分主线程、子线程,那么我们如何要主线程来向子线程发送消息,希望子线程来处理,该如何实现呢?

有时候,我们也可能碰到这样子的一种需求:需要主线程来向子线程发送消息,希望子线程来完成什么任务。如果这样子应该怎么做呢?这就是这篇文章将要讨论的内容。

一、HandlerThread类

主线程发送消息给子线程,通常思维逻辑就是:其实很简单,在主线程中实例化一个Handler,然后让他与子线程相关联(只要它与子线程的Looper相关联即可),这样子它处理的消息就是该子线程中的消息队列,而处理的逻辑都是在该子线程中执行的,不会占用主线程的时间。那么我们就来实现一下,看看这样子到底行得通还是行不通。新建项目,修改它的MainActivity的代码,如下即可:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    //创建子线程
    class MyThread extends Thread{
        private Looper looper;//取出该子线程的Looper
        public void run() {
         
            Looper.prepare();//创建该子线程的Looper
            looper = Looper.myLooper();//取出该子线程的Looper
            Looper.loop();//只要调用了该方法才能不断循环取出消息
        }
    }
    
    private TextView tv;
    private MyThread thread;
    
    
    private Handler mHandler;//将mHandler指定轮询的Looper
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
            thread = new MyThread();
            thread.start();//千万别忘记开启这个线程
            //下面是主线程发送消息
            mHandler = new Handler(thread.looper){
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            mHandler.sendEmptyMessage(1);
    }
}


好了,现在运行该程序。有没有得到预期的结果呢?显然没有,因为报错误了,如下:


这是一个空指针错误。这是为什么呢?仔细思考,也不难发现原因。因为当主线程走到第38行时,此时子线程的Looper对象还没有被创建出来,那么此时thread.looper肯定为空了。其实这个时间是很不好控制的,当然了,你可以让主线程休眠2秒后再执行第38行以后的代码。但是如果有很多个子线程都需要主线程类给其分配任务怎么办??那简直要乱套了。所以我们就更好的解决方式。就是android显然也考虑到了这个问题,于是它我们提供了一个HandlerThread类。这个类是专门处理这个问题的。

当主线程中有耗时的操作时,需要在子线程中完成,通常我们就把这个逻辑放在HandlerThread的对象中执行(该对象就是一个子线程),然后在需要开始执行逻辑的地方发送一个Message来通知一下就可以了。下面我们就修改上面的代码,看一看如何使用HandlerThread这个类。修改MainActivity中的代码如下:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    
    private TextView tv;
    private Handler mHandler;//将mHandler指定轮询的Looper
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
        
            //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
            HandlerThread thread = new HandlerThread("handler thread");
            thread.start();//千万不要忘记开启这个线程
            //将mHandler与thread相关联
            mHandler = new Handler(thread.getLooper()){
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            mHandler.sendEmptyMessage(1);//发送消息
    }
}


运行程序,打印的结果如下:


从打印结果来看,当前子线程的名字正是我们所起的那个名字“handler thread"。

你会有疑问,表面上看HandlerThread并没有创建自己的Looper啊?而且既然是一个线程,那么我们肯定也能重写它的run方法吧。在解答你的疑问之前,我们不妨重写它的run方法来看一看会有什么结果。将代码修改如下:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    
    private TextView tv;
    private Handler mHandler;//将mHandler指定轮询的Looper
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
        
            //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
            HandlerThread thread = new HandlerThread("handler thread"){
                @Override
                public void run() {
                    for(int i=0;i<3;i++){
                        Log.d("handler thread run ",i+"");
                    }
                }
            };
//            HandlerThread thread = new HandlerThread("handler thread");
            thread.start();//千万不要忘记开启这个线程
            //将mHandler与thread相关联
            mHandler = new Handler(thread.getLooper()){
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            mHandler.sendEmptyMessage(1);//发送消息
    }
}


红色部分就是我们重写了它的run方法。再云运行程序,打印的结果如下:

for循环的打印结果正常,但是为什么没有打印出”当前子线程“呢。其实这正是我们要解释的地方。还记得上一篇文章中实现与子线程相关联的的Handler,我们是怎么做的吗?没读过的朋友看以点击链接(http://www.cnblogs.com/fuly550871915/p/4889838.html)。其实我们实现Handlei与线程的关联正是写在run方法中的。而对于HandlerThread这样的线程,也是如此。我们翻看这个类的源代码,找到它的run方法,如下:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    

在源代码的第4行,进行了实例化自己的Looper,如果继续追踪源代码翻看其getLooper方法你会发现,如果一个Handler在与HandlerThread进行绑定时,发现Looper为空,Handler则会一直等待直到Looper被创建出来为止,然后才继续执行后续的代码。所以我们重写了HandlerThread的run方法,肯定就不会去创建Looper对象,那么绑定的Handler就会永远处于等待状态,自然而然就不会打印出”当前子线程“信息了。这也是为什么我们要使用HandlerThread这个特殊的线程,因为使用这个,我们不必关心多线程会混乱,Looper会为空等一系列问题,只要去关心我们要实现的逻辑就行了。

好了,现在做一下简单的总结吧。

 小结:
1. Handler与哪个线程的Looper相关联,那么它的消息处理逻辑就在与之相关的线程中执行,相应的消息的走向也就在相关联的MessageQueue中。(最常见的就是Handler与主线程关联,那么接收Looper回传的消息后的逻辑就会在主线程中执行)
2. 当主线程中需要与子线程进行通信时(比如将耗时操作放在子线程中),建议使用HandlerThread。同时要注意,千万不要去重写它的run方法。

二、一个主线程与子线程互相通信的例子

知识点都说完了。下面我们来写一个具体的例子实践一下吧。新建一个项目,修改它的MainActivity代码,如下:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    
    private TextView tv;
    private Handler mHandler;//与子线程关联的Handler
    private Handler handler;//与主线程关联的Handler
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
        
            //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
            HandlerThread thread = new HandlerThread("handler thread");
            thread.start();//千万不要忘记开启这个线程
            //将mHandler与thread相关联
            mHandler = new Handler(thread.getLooper()){
                public void handleMessage(android.os.Message msg) {
                    Log.d("我是子线程----->", Thread.currentThread()+"");
                    handler.sendEmptyMessage(1);//发送消息给主线程
                };
            };
            
            handler = new Handler(){
                public void handleMessage(android.os.Message msg) {
                    Log.d("我是主线程----->", Thread.currentThread()+"");
                    mHandler.sendEmptyMessage(1);//发送消息给子线程
                };
            };
            mHandler.sendEmptyMessage(1);//发送消息
            handler.sendEmptyMessage(1);//发送消息
    }
}


注释很详细,不解释 了。运行程序,结果如下:


这样子,就会一直循环下去,轮流打印出主线程和子线程。




Android主线程、子线程通信(Thread+handler)

Android是基于Java的,所以也分主线程,子线程!
主线程:实现业务逻辑、UI绘制更新、各子线程串连,类似于将军;
子线程:完成耗时(联网取数据、SD卡数据加载、后台长时间运行)操作,类似于小兵;

一、子线程向主线程发消息(Thread+handler):


1、主线程中定义Handler:

Handler mHandler = new Handler(){  
  
    @Override  
    public void handleMessage(Message msg) {  
        super.handleMessage(msg);  
        switch (msg.what) {  
        case 0:  
            //do something,refresh UI;  
            break;  
        default:  
            break;  
        }  
    }  
      
};

    
2、子线程处理完耗时操作之后发消息给主线程,更新UI:

mHandler.sendEmptyMessage(0);  

这样在子线程与主线程任务分工的条件下完成了消息交互;

二、主线程向子线程发送消息(Thread+handler):

主线程碰到耗时操作要子线程完成,此时发通知给子线程,操作步骤如下:

1、子线程中定义Handler,Handler定义在哪个线程中,就跟那个线程绑定,在线程中绑定Handler需要调用Looper.prepare();方法,主线程中不调用是因为主线程默认帮你调用了;

public class LoopThread implements Runnable {  
  
    public Handler mHandler = null;  
  
    @Override  
    public void run() {  
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {  
                String result = NetUtil.getJsonContent("北京");  
                //完成了获取北京天气的操作;  
                Log.i("test", "handler"+result);  
            }  
        };  
        Looper.loop();  
    }  
  
}



其中Looper.prepare();和Looper.loop();维护了一个消息队列,等待消息注入并在子线程中执行;

2、主线程中这样调用:


lThread.mHandler.sendEmptyMessage(0);  

主线程向子线程发消息,让子线程执行指定的操作,在Android中还有一种方法,即:HandlerThread,看下面的例子:

HandlerThread handlerThread = new HandlerThread("jerome");  
handlerThread.start();  
  
/** 
 * 这里要将HandlerThread创建的looper传递给threadHandler,即完成绑定; 
 */  
threadHandler = new Handler(handlerThread.getLooper()) {  
  
    @Override  
    public void handleMessage(Message msg) {  
        super.handleMessage(msg);  
        switch (msg.what) {  
        case 0:  
这儿可以做耗时的操作;  
            Log.i("jerome", "hello,I am sub thread");  
            break;  
        default:  
            break;  
        }  
    }  
};


[!--infotagslink--]

相关文章

  • php错误提示 open_basedir restriction in effect 解决

    今天在帮助一个朋友配置一台服务器时发现网站配置好了缓存目录读写不成功,在打开错误时发现提示 Warning: file_exists() [function.file-exists]: open_basedir restr...2016-11-25
  • php语言实现redis的客户端

    php语言实现redis的客户端与服务端有一些区别了因为前面介绍过服务端了这里我们来介绍客户端吧,希望文章对各位有帮助。 为了更好的了解redis协议,我们用php来实现...2016-11-25
  • jQuery+jRange实现滑动选取数值范围特效

    有时我们在页面上需要选择数值范围,如购物时选取价格区间,购买主机时自主选取CPU,内存大小配置等,使用直观的滑块条直接选取想要的数值大小即可,无需手动输入数值,操作简单又方便。HTML首先载入jQuery库文件以及jRange相关...2015-03-15
  • JS实现的简洁纵向滑动菜单(滑动门)效果

    本文实例讲述了JS实现的简洁纵向滑动菜单(滑动门)效果。分享给大家供大家参考,具体如下:这是一款纵向布局的CSS+JavaScript滑动门代码,相当简洁的手法来实现,如果对颜色不满意,你可以试着自己修改CSS代码,这个滑动门将每一...2015-10-21
  • 解决Antd Table表头加Icon和气泡提示的坑

    这篇文章主要介绍了解决Antd Table表头加Icon和气泡提示的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-17
  • jQuery+slidereveal实现的面板滑动侧边展出效果

    我们借助一款jQuery插件:slidereveal.js,可以使用它控制面板左右侧滑出与隐藏等效果,项目地址:https://github.com/nnattawat/slideReveal。如何使用首先在页面中加载jquery库文件和slidereveal.js插件。复制代码 代码如...2015-03-15
  • PHP+jQuery翻板抽奖功能实现

    翻板抽奖的实现流程:前端页面提供6个方块,用数字1-6依次表示6个不同的方块,当抽奖者点击6个方块中的某一块时,方块翻转到背面,显示抽奖中奖信息。看似简单的一个操作过程,却包含着WEB技术的很多知识面,所以本文的读者应该熟...2015-10-21
  • SQLMAP结合Meterpreter实现注入渗透返回shell

    sqlmap 是一个自动SQL 射入工具。它是可胜任执行一个广泛的数据库管理系统后端指印, 检索遥远的DBMS 数据库等,下面我们来看一个学习例子。 自己搭建一个PHP+MYSQ...2016-11-25
  • mysql提示Changed limits: max_open_files: 2048 max_connections: 1910 table_cache: 64的解决

    在windows下安装Mysql系统日志出现max_open_files: 2048 max_connections: 510 table_cache: 764 类似错误是因为 max_connections 最大连接数和max_open_files、table_cache 不匹配。适当的降低max_connections 或调...2014-05-31
  • PHP实现今天是星期几的几种写法

    复制代码 代码如下: // 第一种写法 $da = date("w"); if( $da == "1" ){ echo "今天是星期一"; }else if( $da == "2" ){ echo "今天是星期二"; }else if( $da == "3" ){ echo "今天是星期三"; }else if( $da == "4"...2013-10-04
  • 原生js实现fadein 和 fadeout淡入淡出效果

    js里面设置DOM节点透明度的函数属性:filter= "alpha(opacity=" + value+ ")"(兼容ie)和opacity=value/100(兼容FF和GG)。 先来看看设置透明度的兼容性代码: 复制代码 代码如下: function setOpacity(ele, opacity) { if (...2014-06-07
  • JavaScript实现输入框(密码框)出现提示语

    有时候我们需要在登陆表单有一些提示语言,比如“请输入用户名”和“请输入密码”等语言,通过本文给大家介绍JavaScript实现输入框(密码框)出现提示语的相关知识,对js实现输入框提示相关知识感兴趣的朋友一起学习吧...2016-01-14
  • 详解pycharm的python包opencv(cv2)无代码提示问题的解决

    这篇文章主要介绍了详解pycharm的python包opencv(cv2)无代码提示问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-29
  • Android中用HttpClient实现Http请求通信

    本文我们需要解决的问题是如何实现Http请求来实现通信,解决Android 2.3 版本以后无法使用Http请求问题,下面请看正文。 Android开发中使用HttpClient来开发Http程序...2016-09-20
  • js滑动提示效果代码分享

    这篇文章主要为大家分享了js滑动提示效果代码,实现方法简单,感兴趣的小伙伴们可以参考一下...2016-03-12
  • jquery插件autocomplete用法示例

    这篇文章主要介绍了jquery插件autocomplete用法,结合实例形式分析了jQuery插件autocomplete与后台交互实现搜索的自动完成功能实现技巧,需要的朋友可以参考下...2016-07-06
  • 原生JS实现登录框邮箱提示

    这篇文章主要为大家详细介绍了原生JS实现登录框邮箱提示,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-10-18
  • mysql存储过程实现split示例

    复制代码 代码如下:call PROCEDURE_split('分享,代码,片段',',');select * from splittable;复制代码 代码如下:drop PROCEDURE if exists procedure_split;CREATE PROCEDURE `procedure_split`( inputstring varc...2014-05-31
  • 基于bootstrap插件实现autocomplete自动完成表单

    这篇文章主要介绍了基于bootstrap插件实现autocomplete自动完成表单的相关资料,感兴趣的朋友可以参考一下...2016-05-09
  • jQuery文字提示与图片提示效果实现方法

    这篇文章主要介绍了jQuery文字提示与图片提示效果实现方法,涉及jQuery针对鼠标事件的响应与页面元素动态操作相关技巧,需要的朋友可以参考下...2016-07-06