Service in Android Development

记录一下,这篇文章写得很好,很简洁,以至于我都没动力复述一遍了:
http://www.cnblogs.com/trinea/archive/2012/11/08/2699856.html

这篇文章中描述了如何创建Service,startService和bindService的区别(后者可以和Activity通讯,其实前者也行)。
针对bindService,在Service中创建一个Binder,里面可以自定义Service提供给外界的接口,在Activity调用bindService之后,在成功连接到Service的回调里面,Service提供的Binder会作为参数传给Activity。

这样,Activity就可以随心所欲操作Service了。

不过这篇文章没有回答的一个问题是:如果Service中的一些事件需要通知Activity怎么办?
这个问题我摸索了一段时间,用了好些方法来做。



第一,创建一个叫做ServiceListener的抽象类,里面定义好Service要通知Activity的函数。然后当Activity绑定到Service的时候,在Binder中提供一个注册Listener的方法(当然unregister也提供)。这样Activity可以创建一个Listener的子类来针对感兴趣的部分进行操作;而Service这边维护一个ArrayList数组,每次事件一到就通知所有的listener。

这个方法可行,不过有一个问题是,这些回调函数不一定是在主线程被调用的(因为Service经常在辅助线程干活儿)。



第二,还是沿用上面的结构,不过把Listener改为Handler,在Service这边定义好各种事件定义的Message,然后当事件发生的时候,直接向所有的Handler发送Message。由于Activity中创建的Handler都是主线程上,所以可以直接干UI的活儿。

这个方法也不错,不过损失了上一个方法中定义好的接口,没有那么方便了,开发的时候,还需要去查查Service怎么定义的各种Message,不是很方便。



第三,综合上面两种。Service还是定义一个Listener,只不过它继承自Handler,并且定义好需要通知Activity的各种接口。然后在这个基类里面,进行handleMessage()。针对每一种Message去调用对应的接口。然后Activity这边创建一个Listener的子类,重载这些接口就能干活儿了。一举两得。



—————-我是跨进程的分割线——————
不过呢,这种做法只能用于同在一个进程内的Service和Activity之间的通讯,如果Activity和Service不在一个进程的话,那就只能借助于AIDL(Android Interface Description Language)了。

在初步研究了AIDL之后,从Activity控制Service似乎是没有什么问题的(代码以后补上)。

但是AIDL并不能传送复杂数据类型,像上面方法三的办法就不能了,虽然可以在Service那边的接口中提供registerListener和unregisterListener方法,但是不能直接传送一个Handler的子类,那怎么办呢?

接着深入研究AIDL,发现虽然不能传送一个复杂数据,但是interface是可以传送的。因此我们再定义一个接口叫做IServiceListener,里面提供以简单数据作为参数的各种操作,这些操作是Service希望通知Listener的。然后在方法三的Listener中,实现这个IServiceListener的Stub类,里面针对各种调用分别向自己(Listener本身是Handler)sendMessage。而且提供一个getStubForRegister()方法返回这个Stub类就好了。这样在IService中的registerListener和unregisterListener都以这个IServiceListener为参数即可。

这样就实现了一个终极方案,不论Service在哪个进程,只需要在Activity所在进程创建一个Listener,然后重载其中的钩子函数,在Bind到Service的时候,向Service注册Listener.getStub()就可以了。Service一旦有消息要通知Listener,就会在Listener所在的线程中(一般是主线程),调用对应的钩子函数,这样就完美了。



—————-我是BUG的分割线——————
今天(20130507)遇到一个问题,bindService总返回false,也就说没有bind成功,各种测试之后发现,额,Manifest.xml中没有声明这个Service。
怎么声明呢?那就是在和activity标签并列的地方插入一段:

<service android:name="com.xxx.xxx.xxxService" />

ListView in Android Develop

ListView是比较常用的控件,相当于iOS中的TableView,用来展示数据列表。

使用方法的话也很简单:第一定义每一个Cell的布局,第二实例化一个SimpleAdapter,第三将这个Adapter绑定到ListView。



一、定义Cell布局:
这个相当于在iOS中新建一个Cell的nib或者如果愿意的话,写代码也行。
比如定义一个Layout XML文件如下(文件名是”list_items.xml”,这个很重要,后面会用到这个名字)

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout   
    android:id="@+id/RelativeLayout01"   
    android:layout_width="fill_parent"   
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:layout_height="wrap_content"   
    android:paddingBottom="4dip"   
    android:paddingLeft="12dip"  
    android:paddingRight="12dip">  
<ImageView   
    android:paddingTop="12dip"  
    android:layout_alignParentRight="true"  
    android:layout_width="wrap_content"   
    android:layout_height="wrap_content"   
    android:id="@+id/ItemImage"  
    />   
<TextView   
    android:text="TextView01"   
    android:layout_height="wrap_content"   
    android:textSize="20dip"   
    android:layout_width="fill_parent"   
    android:id="@+id/ItemTitle"  
    />  
<TextView   
    android:text="TextView02"   
    android:layout_height="wrap_content"   
    android:layout_width="fill_parent"   
    android:layout_below="@+id/ItemTitle"   
    android:id="@+id/ItemText"  
    />  
</RelativeLayout> 

有两个TextView和一个ImageView。



二、实例化一个SimpleAdapter
可以实现自己的数据源,只要实现了ListAdapter接口就行。Android给出了一个方便的Adapter:SimpleAdapter,只要给它数据源和数据源每一项到布局中控件id的映射,它就会给ListView提供每一项的数据和对应的映射,很方便。

//生成动态数组,加入数据  
        ArrayList<HashMap<String, Object>> listItem = new ArrayList<HashMap<String, Object>>();  
        for(int i=0;i<10;i++)  
        {  
            //每一项是一个数据源,相当于是Key-Value。这个例子里面有三项数据,第一是图片,后两个是文本
            HashMap<String, Object> map = new HashMap<String, Object>();  
            map.put("ItemImage", R.drawable.checked);//图像资源的ID  
            map.put("ItemTitle", "Level "+i);  
            map.put("ItemText", "Finished in 1 Min 54 Secs, 70 Moves! ");  
            listItem.add(map);  
        }  
        //生成适配器的Item和动态数组对应的元素  
        SimpleAdapter listItemAdapter = new SimpleAdapter(this,listItem,//数据源   
            R.layout.list_items,//ListItem的XML实现  
            //动态数组与ImageItem对应的子项,这是和上面每一项数据中对应的Key
            new String[] {"ItemImage","ItemTitle", "ItemText"},   
            //ImageItem的XML文件里面的一个ImageView,两个TextView ID
            //这里是针对上面每一项的Key在我们定义的Cell布局中的控件id
            new int[] {R.id.ItemImage,R.id.ItemTitle,R.id.ItemText}  
        ); 

初始化SimpleAdapter的最后两个参数为每一项数据中的内容指定了在Cell中的显示控件。这里比较有意思的是,针对不同的控件,会自动去完成内容的设置,比如textview是setText(aValue),imageview是setImage(aImage)。



三、绑定adapter到listview
接在上面的代码后面,两句话就可以了:

        ListView list = (ListView) findViewById(R.id.ListView01);  
        list.setAdapter(listItemAdapter);

这样,就能显示出需要的数据了。



四、动态删减数据条目
保留住开始数据源的那个listItem,直接对它进行修改,之后调用adapter的notifyDataChanged

listItem.add(message.getHashMap());
ListView lv = (ListView)findViewById(R.id.listViewForCustomMessages);
((SimpleAdapter)lv.getAdapter()).notifyDataSetChanged();



参考
http://www.iteye.com/topic/540423,上面的例子来自这里,写的很简洁,文章中还有事件处理的代码。

Handler in Android Development

Handler是在Android系统中用来做消息分发和处理的类。在涉及到跨线程调用的时候,可以使用这个组件。
Handler is used for message dispatch and process. It’s useful to dealing with inter-thread communication.

每一个Handler绑定在一个Looper上,Looper可以理解为一个线程中的消息循环,上面有一个消息队列MessageQueue。Looper每次从消息队列取一个消息Message,然后让Handler去处理,然后再去取一个消息,继续处理这样子。
Each handler is attached to a Looper. Looper can be seen as a message loop in a thread, and in the thread, there is a MessageQueue. Looper will pick a message from the queue, and let the handler to handle the message, and then pick next message, let handler to process, ….., and so on.

系统会为每一个Activity自动创建一个Looper进行消息循环的处理。如果是自己创建的线程,默认是没有Looper的,可以通过Looper.prepare()和Looper.loop()来给调用线程加上一个消息循环。
System will create an Looper for Activity automatically. And if you create your own Thread, by default there is no Looper on it. But you can call Looper.prepare() and Looper.loop() in the thread to initialize an looper for it. See the code below:



给自定义线程添加消息循环
Add a looper to a thread

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          //Initialize the current thread as a looper. 
          //This gives you a chance to create handlers that then reference this looper, before actually starting the loop. 
          //Be sure to call loop() after calling this method, and end it by calling quit().
          Looper.prepare();

          //the handler will attach to the thread in which it was created.
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          //Run the message queue in this thread. Be sure to call quit() to end the loop.
          Looper.loop();
      }
  }



一个用Handler来进行UI更新的例子
An example to use handler update UI

From:http://www.pin5i.com/showtopic-android-handler.html

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): 让一个动作,在不同的线程中执行.

        // 它安排消息,用以下方法
        // post(Runnable)
        // postAtTime(Runnable,long)
        // postDelayed(Runnable,long)
        // sendEmptyMessage(int)
        // sendMessage(Message);
        // sendMessageAtTime(Message,long)
        // sendMessageDelayed(Message,long)
       
        // 以上方法以 post开头的允许你处理Runnable对象
        //sendMessage()允许你处理Message对象(Message里可以包含数据,)

        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

        }
    }

}



补充一点,同一个Message不能发给两个Handler,那样会报错:AndroidRuntimeException:This message is already in use。如果要广播消息,可以这么做:

// clone a message from the message want to broadcast
Message cloneMessage = Message.obtain(originalMessage);
// send the cloned message to other handlers
anotherHandler.postMessage(cloneMessage);
// send the original message to the handler where it should be posted.
originalHandler.postMessage(originalMessage);



参考 | Reference
http://developer.android.com/intl/zh-CN/reference/android/os/Handler.html
http://developer.android.com/intl/zh-CN/reference/android/os/Looper.html
http://www.pin5i.com/showtopic-android-handler.html