Android消息循环分析

我们的常用的系统中,程序的工作通常是有事件驱动和消息驱动两种方式,在Android系统中,Java应用程序是靠消息驱动来工作的。

消息驱动的原理就是:

1. 有一个消息队列,可以往这个队列中投递消息;

2. 有一个消息循环,不断从消息队列中取出消息,然后进行处理。

在Android中通过Looper来封装消息循环,同时在其中封装了一个消息队列MessageQueue。

另外Android给我们提供了一个封装类,来执行消息的投递,消息的处理,即Handler。

在我们的线程中实现消息循环时,需要创建Looper,如:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare(); //1.调用prepare
        ......
        Looper.loop();  //2.进入消息循环
    }
}

看上面的代码,其实就是先准备Looper,然后进入消息循环。

1. 在prepare的时候,创建一个Looper,同时在Looper的构造方法中创建一个消息队列MessageQueue,同时将Looper保存到TLV中(这个是关于ThreadLocal的,不太懂,以后研究了再说)

2. 调用loop进入消息循环,此处其实就是不断到MessageQueue中取消息Message,进行处理。

然后再看我们如何借助Handler来发消息到队列和处理消息

Handler的成员(非全部):


final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;

Message的成员(非全部):


Handler target;
Runnable callback;

可以看到Handler的成员包含Looper,通过查看源代码,我们可以发现这个Looper是有两种方式获得的,1是在构造函数传进来,2是使用当前线程的Looper(如果当前线程无Looper,则会报错。我们在Activity中创建Handler不需要传Handler是因为Activity本身已经有一个Looper了),MessageQueue也就是Looper中的消息队列。

然后我们看怎么向消息队列发送消息,Handler有很多方法发送队列(这个自己可以去查),比如我们看sendMessageDelayed(Message msg, long delayMillis)

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// SystemClock.uptimeMillis() 获取开机到现在的时间
}
//最终所有的消息是通过这个发,uptimeMillis是绝对时间(从开机那一秒算起)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
return sent;
}

看上面的的代码,可以看到Handler将自己设为Message的target,然后然后将msg放到队列中,并且指定执行时间。

消息处理

处理消息,即Looper从MessageQueue中取出队列后,调用msg.target的dispatchMessage方法进行处理,此时会按照消息处理的优先级来处理:

1. 若msg本身有callback,则交其处理;

2. 若Handler有全局callback,则交由其处理;

3. 以上两种都没有,则交给Handler子类实现的handleMessage处理,此时需要重载handleMessage。

我们通常采用第三种方式进行处理。

注意!!!!我们一般是采用多线程,当创建Handler时,LooperThread中可能还未完成Looper的创建,此时,Handler中无Looper,操作会报错。

我们可以采用Android为我们提供的HandlerThread来解决,该类已经创建了Looper,并且通过wait/notifyAll来避免错误的发生,减少我们重复造车的事情。我们创建该对象后,调用getLooper()即可获得Looper(Looper未创建时会等待)。

补充

本文所属为Android中java层的消息循环机制,其在Native层还有消息循环,有单独的Looper。并且2.3以后MessageQueue的核心向Native层下移,native层java层均可以使用。这个我没有过多的研究了!哈哈

PS:本文参考《深入理解Android:卷I》

管理Activity的生命周期

Activity是android的四大组件之一,我们编写程序时,主要通过Activity来显示我们的UI。我们需要了解他的生命周期,以及 每个周期可以做什么。
一个Activity存在三种状态:

Resumed:

activity显示在屏幕的最前面,并且获取用户焦点。

Paused:

其他activity在当前activity之前,并获得焦点。当前activity还能够部分显示,仍然维护着所有状态,当内存低的时候才会被系统杀死。

Stopped:

当前activity完全不可见。但是仍然存在,其他应用需要内存的时候会被杀死(不一定是低内存的时候)。

具体生命周期见图:

  1. 启动Activity
    执行onCreate() –> onStart() –> onResumed()
    这个过程执行完,我们的activity就显示在屏幕上了。
    我们可以重写相应的方法,在其中实现我们需要的操作。
  2. 销毁Activity
    当我们执行finish()或者被系统强制杀死时,我们的activity会被销毁。
    我们的activity内部会执行 onPause()–>onStop()->onDestory()
  3. 暂停和继续
    当我们的界面被部分挡住时,会进入暂停状态。
    此时会执行onPause()
    界面重新完全显示后又会回到继续状态(Resumed),会执行onResumed()
    在即将pause时,我应应该在onPause中执行一些释放操作,比如停止正在进行的动画,一些用户的状态(确定用户会保存的,比如邮件草稿),释放系统支援(如广播接收者,传感器),以及其他会消耗电池并且在paused时用户不需要用到的。
    同时这些释放或者保存的,我们在onResumed时候需要恢复。
  4. 停止和重启
    用户进去其他界面之后,如接听电话,或者打开其他activity,我们的界面会停止,进入stoped状态。此时会执行onPause()–>onStop()
    重启时会执行onRestart()–>onStart()–>onResume()
    重启时候和我们创建时相比,多一步onRestart()。
    ​在stop的时候,我们需要执行一些比在onPause中更加消耗CPU的更大的任务,比如写数据库。
    ​同时建议,在onStart()中恢复,而不是在onRestart()中恢复。
  5. 重新创建Activity
    ​ ​当我们的进程被destory(不是用户主动调用finish),可以返回。返回的时候会重新创建。执行过程和创建activity一样。
    ​当activity被系统kill之前,会调用onSaveInstanceState()去保存UI状态,如果我们有信息需要保存,也可以去重写这个方法去做。
    同时我们可以重写onRestoreInstanceState()去恢复状态。不重写,系统会恢复系统保存的那一部分UI。或者我们可以在onCreate中恢复,onCreate的参数savedInstanceState就是我们保存的信息,可以判断该参数是否为空,来恢复界面。

解决Ubuntu下Sublime text 3中文输入的问题

好久之前便听朋友说起Sublime Text这款软件很好用,终于这几天有空折腾,把软件给装起来了。用起来确实很不错,写代码很爽。

但是用了一段时间之后,我需要输入中文了,无论怎么切换输入法,都无法切换到中文。
网上搜索了一下,原来这是Bug。找解决方法吧。下面介绍我的解决方案,是大神cjacker解决成功的啦,我只是copy一下,方便大家在遇到这个问题的时候可以方便解决。

    我的系统:ubuntu 13.04  
    我的输入法:fcitx   
    sublime版本:3059    

理论上支持 sublime text2/3

1.保存代码sublime-imfix.c

/*
sublime-imfix.c
Use LD_PRELOAD to interpose some function to fix sublime input method support for linux.
By Cjacker Huang <jianzhong.huang at i-soft.com.cn>

gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC
LD_PRELOAD=./libsublime-imfix.so sublime_text
*/
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
typedef GdkSegment GdkRegionBox;

struct _GdkRegion
{
    long size;
    long numRects;
    GdkRegionBox *rects;
    GdkRegionBox extents;
};

GtkIMContext *local_context;

void
gdk_region_get_clipbox (const GdkRegion *region,
        GdkRectangle    *rectangle)
{   
    g_return_if_fail (region != NULL);
    g_return_if_fail (rectangle != NULL);

    rectangle->x = region->extents.x1;
    rectangle->y = region->extents.y1;
    rectangle->width = region->extents.x2 - region->extents.x1;
    rectangle->height = region->extents.y2 - region->extents.y1;
    GdkRectangle rect;
    rect.x = rectangle->x;
    rect.y = rectangle->y;
    rect.width = 0;
    rect.height = rectangle->height; 
    //The caret width is 2; 
    //Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.
    if(rectangle->width == 2 && GTK_IS_IM_CONTEXT(local_context)) {
        gtk_im_context_set_cursor_location(local_context, rectangle);
    }
}

//this is needed, for example, if you input something in file dialog and return back the edit area
//context will lost, so here we set it again.

static GdkFilterReturn event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer im_context)
{
    XEvent *xev = (XEvent *)xevent;
    if(xev->type == KeyRelease && GTK_IS_IM_CONTEXT(im_context)) {
        GdkWindow * win = g_object_get_data(G_OBJECT(im_context),"window");
        if(GDK_IS_WINDOW(win))
            gtk_im_context_set_client_window(im_context, win);
    }
    return GDK_FILTER_CONTINUE;
}

void gtk_im_context_set_client_window (GtkIMContext *context,
      GdkWindow    *window)
{
    GtkIMContextClass *klass;
    g_return_if_fail (GTK_IS_IM_CONTEXT (context));
    klass = GTK_IM_CONTEXT_GET_CLASS (context);
    if (klass->set_client_window)
    klass->set_client_window (context, window);

    if(!GDK_IS_WINDOW (window))
    return;
    g_object_set_data(G_OBJECT(context),"window",window);
    int width = gdk_window_get_width(window);
    int height = gdk_window_get_height(window);
    if(width != 0 && height !=0) {
        gtk_im_context_focus_in(context);
        local_context = context;
    }
    gdk_window_add_filter (window, event_filter, context); 
}

2.安装C/C++的编译环境和gtk libgtk2.0-dev

sudo    apt-get install build-essential
sudo apt-get install libgtk2.0-dev

3.编译共享内存

gcc -shared -o libsublime-imfix.so sublime_imfix.c  `pkg-config --libs --cflags gtk+-2.0` -fPIC

4.启动测试

LD_PRELOAD = ./libsublime-imfix.so sublime_text

正常的话这样是没有问题的。

然后我们在修改我们的desktop文件,使图标也可以使用

sudo vi /usr/share/applications/sublime-text.desktop

先将so文件移动到sublime text的目录

然后按照如下替换(主要是每次执行之前,去预加载我们的libsublime-imfix.so库)

[Desktop Entry]
Version=1.0
Type=Application
Name=Sublime Text
GenericName=Text Editor
Comment=Sophisticated text editor for code, markup and prose
Exec=bash -c 'LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so /opt/sublime_text/sublime_text' %F
Terminal=false
MimeType=text/plain;
Icon=sublime-text
Categories=TextEditor;Development;
StartupNotify=true
Actions=Window;Document;

[Desktop Action Window]
Name=New Window
Exec=bash -c 'LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so /opt/sublime_text/sublime_text' -n
OnlyShowIn=Unity;

[Desktop Action Document]
Name=New File
Exec=bash -c 'LD_PRELOAD=/opt/sublime_text/libsublime-imfix.so /opt/sublime_text/sublime_text' --command new_file
OnlyShowIn=Unity;

Toast的使用详解

Android中提供一种简单的Toast消息提示框机制,可以在用户点击了某些按钮后,提示用户一些信息,提示的信息不能被用户点击,Toast的提示信息根据用户设置的显示时间后自动消失。Toast的提示信息可以在调试程序的时候方便的显示某些想显示的东西,或者给用户提供友好的界面显示效果。

有两种方式去创建并且显示Toast:

  1. Toast.makeText(Context context, int resId, int duration)

    Toast.makeText(Context context, CharSequence text, int duration)

    Context为上下文,通常为当前activity;resId是string字符串的id,CharSequence为你要显示的字符串,duration为显示的时间,可以选择Toast.LENGTH_SHORT或Toast.LENGTH_LONG,也可自定义时间。
    使用方法:

    Toast.makeText(this, "this is string", Toast.LENGTH_SHORT).show();

  2. 自己创建Toast,并且设置视图,即自定义

    如:

Toast toast = new Toast(this);      
// 定义一个ImageView        
ImageView imageView = new ImageView(this);      
imageView.setImageResource(R.drawable.ic_launcher);     
// 定义一个Layout,这里是Layout     
LinearLayout Layout = new LinearLayout(this);       
Layout.setOrientation(LinearLayout.HORIZONTAL);     
// 将ImageView放到Layout中      
Layout.addView(imageView);      
// 设置View       
toast.setView(Layout);      
//设置显示时间        
toast.setDuration(20);      
toast.show();       

通过上面的代码就可以自己定义一个Toast了,我们还在其中显示了图片。

如何设置Toast显示的位置

方法一:

setGravity(int gravity, int xOffset, int yOffset) 三个参数分别表示(起点位置,水平向右位移,垂直向下位移)

方法二:

setMargin(float horizontalMargin, float verticalMargin)
以横向和纵向的百分比设置显示位置,参数均为float类型(水平位移正右负左,竖直位移正上负下)

注意事项:

Toast中有一个public方法setText(),可以给toast设置resid或者string,该方式尽可以在我们的第一种方法中使用,第二种自定义toast的方式是不可以使用的,使用的话会抛出异常。

原因是使用第一种方式创建,Toast会自己创建一个view,即textview,而我们使用这个setText实际是向这个TextView设置内容,而自定义的View不会有这个控件,因此会报错。

android异步操作总结

Android中经常会有一些操作比如网络请求,文件读写,数据库操作,比较耗时,我们需要将其放在非UI线程去处理,此时,我们需要处理任务前后UI的变化和交互。我们需要通过类似js中异步请求处理,这里总结我所了解到的,方便自己记忆,也方便别人的浏览。

  1. AsyncTask

new AysncTask().execute();

AsyncTask会按照流程执行在UI线程和一个耗时的任务线程。

1.onPreExecute() 执行预处理,它运行于UI线程,可以为后台任务做一些准备工作,比如绘制一个进度条控件。

2.doInBackground(Params…) 后台进程执行的具体计算在这里实现,doInBackground(Params…)是AsyncTask的关键,此方法必须重载。在这个方法内可以使用publishProgress(Progress…)改变当前的进度值。

3.onProgressUpdate(Progress…) 运行于UI线程。如果在doInBackground(Params…) 中使用了publishProgress(Progress…),就会触发这个方法。在这里可以对进度条控件根据进度值做出具体的响应。

4.onPostExecute(Result) 运行于UI线程,可以对后台任务的结果做出处理,结果就是doInBackground(Params…)的返回值。此方法也要经常重载,如果Result为null表明后台任务没有完成(被取消或者出现异常)。

  1. Handler
    创建Handler时需要传Lopper,默认是UI线程的。
    通过Handler发送消息(Message)到主线程或者Handler的线程,

  2. Activity.runOnUiThread(Runnable)
    Runnable即可在UI线程执行

  3. View.post(Runnable)
    Runnable运行在UI线程
    View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

所有的异步操作原理本质都是通过Handler

基本上就这几种方法,当然也可自己使用消息循环常见类似的任务处理机制。

git使用技巧

这几天开始工作了,工作中使用了git进行项目管理,这才发现原来我以前所知道的git使用那只是一点皮毛。

写一些这几天用到的一些git技巧喽,以后有的话继续更新啦。

git status 查看当前的状态,那些文件修改了,那些文件创建还没有add的。

git add . 添加所有的修改

或者

git add 文件名或者文件路径,添加指定的

git stash 将没有commit的部分文件放到暂存栈去,这样从服务器pull文件的时候不会有问题。

git stash pop 是将暂存栈的东西拿回来
git stash clear 将暂存栈中的东西清空,要慎用,这样你放在暂存中的修改都将丢失

git reset 将所有git add 的撤销

将其他的分支中的某个修改合并到当前分支

  1. git cherry-pick sha1(用gitk 在那个分支上面可以看到)
  2. git reset –soft 合并当前分支和cherry过来的分支。两个分支的代码都保留 如果参数用 hard 那么本地的将会被抹掉
  3. git commit –ammend
    这样就完成了

修改两次前的提交

  1. git rebase -i HEAD~2
  2. 修改那个提交的状态,即把pick改为edit
  3. 回去修改文件
  4. git commit –amend
  5. git rebase –continue
    这样也就ok了

git里面很多东西很有用,好吧,以后好好学习。

Android图像开源视图:SmartImageView

项目需要,开发中需要加载图片,自己要写图片从网上下载的方法,还要写缓存,等等。

在网上找到一个开源项目,smartImageVIew,支持从URL和通讯录中获取图像,可以替代Android标准的ImageView。

特征:

根据URL地址装载图像;
支持装载通讯录中的图像;
支持异步装载;
支持缓存;

这个是作者的项目主页,有使用方法。

http://loopj.com/android-smart-image-view/

下载作者的jar包导入项目后,在xml中加入控件

<com.loopj.android.image.SmartImageView android:id="@+id/my_image" />    

代码里找到该控件

SmartImageView myImage = (SmartImageView) this.findViewById(R.id.my_image); 

使用控件

通过url加载图片

myImage.setImageUrl("http://www.awesomeimages.com/myawesomeimage.jpg");   

加载通讯录的图片

myImage.setImageContact(contactAddressBookId);  

github上面有源码,需要的可以看看:

https://github.com/loopj/android-smart-image-view    

Java中HashMap和HashTable的区别

HashMap 是Hashtable 的轻量级实现(非线程安全的实现),他们都完成了Map 接口,主要区别在于HashMap 允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。

HashMap 允许将null 作为一个entry 的key 或者value,而Hashtable 不允许。

HashMap 把Hashtable 的contains 方法去掉了,改成containsvalue 和containsKey。因为contains方法容易让人引起误解。

Hashtable 继承自Dictionary 类,而HashMap 是Java1.2 引进的Map interface 的一个实现。

最大的不同是,Hashtable 的方法是Synchronize 的,而HashMap 不是,在多个线程访问Hashtable 时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。

Hashtable 和HashMap 采用的hash/rehash 算法都大概一样,所以性能不会有很大的差异。

Java中Collection和Collections的区别

前几天去一个公司参加面试遇到这个问题,Java中Collection和Collections的区别,当时不会,回来从网上找到,现在记录一下。

1、java.util.Collection 是一个 集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

Collection

├List

│├LinkedList

│├ArrayList

│└Vector

│ └Stack└Set

2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的 静态多态方法。此类 不能实例化 ,就像一个 工具类,服务于Java的Collection框架。

import java.util.ArrayList;      
import java.util.Collections;      
import java.util.List;      
public class TestCollections {      
    public static void main(String args[]) {     
        //注意List是实现Collection接口的       
        List list = new ArrayList();      
        double array[] = { 112, 111, 23, 456, 231 };      
        for (int i = 0; i < array.length; i++) {      
            list.add(new Double(array[i]));      
        }      
        Collections.sort(list);      
        for (int i = 0; i < array.length; i++) {      
            System.out.println(list.get(i));      
        }      
        // 结果:23.0 111.0 112.0 231.0 456.0      
    }      
}

intent和intent过滤以及匹配规则

在Android系统中打开Activity,Service, BroadCast 都需要使用到Intent(意图),那么作为一个开发者就需要知道怎么使用Intent,知道怎么通过Intent的匹配规则打开其他的组件,或者给其他组件提供访问接口。因此在这里总结一下Intent和Intent的匹配规则。

Intent使用示例

显式使用Intent

Intent intent = new Intent(context, XXXActivity.class);
intent.putExtra(Intent.EXTRA_TEXT, "string text");

隐式使用

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parser("http://blog.isming.me"));

AndroidManifest定义IntentFilter规则
xml
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

以上是简单的Intent示例,下面看Intent里面有那些东西,以及Intent的解析规则.

Intent的成员

action 动作,比如上面的隐式Intent,就是传递了一个View的动作.

data 数据,通过Intent发送的数据, 上面的隐式意图就是通过Uri解析出来数据

category 标签

type 类型 上面的隐式意图就是通过Uri来解析出来类型,即MIME type.

component 组件,即要打开的组件的信息,包名+类名.上面的显式意图就是设置要打开的组件。其他隐式意图也是通过各种参数来最终获取组件。

extras 附加数据,在Intent中附加传递的数据.

IntentFilter

IntentFilter,刚刚的一个xml配置的intentFilter,同时也可以在代码中配置。通过IntentFilter可以过滤掉不需要的一些隐式意图,必须有action 和category,还可以附加data规则,包含(data type 和data scheme+authority+path),通常一个Url包含://:/,可以通过设置data规则只有在指定的格式才能打开页面。

比如:

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

上面这个就只有数据是图片的时候才可以打开。

<intent-filter>
    <data android:scheme="http" />
    ...
</intent-filter>

上面这个规则则只允许数据是http的网址可以。

参考资料:

http://developer.android.com/guide/components/intents-filters.html

http://developer.android.com/reference/android/content/IntentFilter.html