本文学习拜读自罗升阳老师的《Android系统源代码情景分析》
涉及Binder
一 概述
Service组件是Android应用程序的四大组件之一,不过与Activity组件不一样,他主要来处理与用户界面无关的业务逻辑。由于Service组件不直接与用户交互,因此,它涉及的业务逻辑一般都是计算型的,适合在后台运行。
与Activity组价的启动方式类似,Service组件的启动方式也分为显示和隐式两种,对于隐式启动的Service组件来说,我们只需要知道他的组件名称;而对于显示启动的Service组件来说,我们需要知道他的类名称。
Service组件可以被Activity组件启动,也可以被其他的Service组件启动。同时,它既可以在启动它的Activity组件或者Service组件所在的应用程序中启动,也可以在一个新的应用程序进程中启动。当一个Service组件被一个Activity组件或者另外一个Service组件启动时,我们可以将他们绑定起来,以便启动者可以方便的得到它的访问接口。
二 实例
此次,我们开发一个名称为Counter的Android应用程序,它由一个Service组件CounterService和一个Activity组件Counter组成。其中CounterService组件实现了一个计数器服务,它是由Counter组件启动起来的,并且与Counter组件绑定在一起。
CounterService组件在内部使用一个异步任务(AsyncTask)来实现一个计数器。这个计数器每隔一秒就将内部的的计数加一,并且实时的将这个计数显示在Counter组件的用户界面上,由于计数器需要不停的执行加数功能,因此,我们将它放在一个后台线程中执行,避免Counter组建不能及时的响应应用用户界面事件。
应用程序目录结构如下:
Android/packages/apps/Counter
------AndroidManifest.xml
------Android.mk
------src
------com/android/counter
------ICounterCallback.java
------ICounterService.java
------CounterService.java
------Counter.java
------res
------layout
------main.xml
------values
------Strings.xml
------drawable
------icon.png
ICounterCallback.java
该文件定义了一个计数器回调接口ICounterCallback,它只有一个成员函数count,用来将CounterService组件的当前计数实时的更新到Counter组件的用户界面上。
package com.android.counter;
public interface ICounterCallback{
void count(int val);
}
ICounterService.java
该文件定义了一个计数器接口ICounterService,他有两个成员函数,startCounter()用来启动计数器,stopCounter()用来停止计数器,可以指定计数器的初始值,以及更新用户界面的回调接口。
package com.android.counter;
public interface ICounterService {
public void startCounter(int inniVal, ICounterCallback callback);
public void stopCounter();
}
CounterService.java
package com.android.counter;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class CounterService extends Service implements ICounterService {
private final static String LOG_TAG = "com.android.counter.CounterService";
private boolean stop = false;
private ICounterCallback counterCallback = null;
private final IBinder binder = new CounterBinder();
public class CounterBinder extends Binder {
public CounterService getService() {
return CounterService.this;
}
}
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(LOG_TAG, "Counter Service Created");
}
public void startCounter(int initVal, ICounterCallback callback) {
counterCallback = callback;
AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer> (){
@Override
protected Integer doInBackground(Integer... vals) {
Integer initCounter = vals[0];
stop = false;
while(!stop){
publishProgress(initCounter);
try{
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
initCounter++;
}
return initCounter;
}
@Override
protected void onProgressUpdate(Integer... values){
super.onProgressUpdate(values);
int val = values[0];
counterCallback.count(val);
}
@Override
protected void onPostExecute(Integer val){
counterCallback.count(val);
}
};
task.execute(initVal);
}
public void stopCounter() {
stop = true;
}
}
CounterService 是应用程序Counter的一个Service组件,因此,它必须要从Service类继承下来。CounterService组件同时实现了计数器接口ICounterService,用来提供计数器服务。在CounterService组件启动时,它的成员函数onCreate就会被调用;而在
CounterService组件被绑定时,它的成员函数onBinder就会被调用。
CounterService组件有一个成员变量binder,它指向一个类型为CounterBinder的本地对象。当CounterService组件被绑定时,即它的成员函数onbinder被调用时,它就会将内部的Binder本地对象binder返回给绑定者,以便绑定者可以通过这个Binder本地对象来获得它的一个计数器访问接口。注意这里之所以要返回一个Binder本地对象给绑定者,是因为绑定者与CounterService组件可能是运行在两个不同的应用程序进程中的。不过,在这个应用程序实例中,启动CounterService组件的Counter组件与CounterService组件是运行在同一个应用程序进程中的。
CounterService组件的成员函数 startCounter和stopCounter分别用来启动和停止内部的计数器,其中,成员函数startCounter
使用一个异步任务task来启动一个定时器,成员函数stopCounter通过将CounterService组件的成员变量stop设置为true来停止
这个计数器。
当计数器开始执行时,异步任务task的成员函数 doInBackground()就会被调用。在调用过程中,每隔1秒就会将上一次的计数加一并且调用异步任务task的成员函数publishProgress()将当前计数发布出去,这会导致异步任务task的成员函数onProgressUpdate被调用,后者主要是使用CounterService组件内部的一个ICounterCallback接口将计数器的当前计数更新到用户界面上。
当计数器被停止执行时,异步任务task的成员函数onPostExecute()就会被调用,他主要是使用CounterService组件内部的一个
ICounterCallback接口将计数器的最终计数更新到用户界面上。注意在异步任务task的三个成员函数中,只有一个成员函数
doInBackground是在后台线程中执行的,其余两个成员函数 onProgressUpdate和onPostExecute都是在应用程序Counter的主线程中执行的,因此,他们可以用来更新用户界面。
Counter.java
package com.android.counter;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class Counter extends Activity implements OnClickListener, ICounterCallback {
private final static String LOG_TAG = "haoran.ma.counter.Counter";
private Button startButton = null;
private Button stopButton = null;
private TextView counterText = null;
private ICounterService counterService = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
startButton = (Button)findViewById(R.id.button_start);
stopButton = (Button)findViewById(R.id.button_stop);
counterText = (TextView)findViewById(R.id.textview_counter);
startButton.setOnClickListener(this);
stopButton.setOnClickListener(this);
startButton.setEnabled(true);
stopButton.setEnabled(false);
Intent bindIntent = new Intent(Counter.this, CounterService.class);
bindService(bindIntent,ServiceConnection , Context.BIND_AUTO_CREATE);
Log.i(LOG_TAG, "Counter Activity Created");
}
@Override
public void onDestroy() {
super.onDestroy();
unbindService(ServiceConnection );
}
@Override
public void onClick(View v) {
if(v.equals(startButton)) {
if(counterService != null) {
counterService.startCounter(0,this);
startButton.setEnabled(false);
stopButton.setEnabled(true);
}
}else if(v.equals(stopButton)) {
if(counterService != null) {
counterService.stopCounter();
startButton.setEnabled(true);
stopButton.setEnabled(false);
}
}
}
@Override
public void count(int val) {
String text = String.valueOf(val);
counterText.setText(text);
}
private ServiceConnection ServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
counterService = ((CounterService.CounterBinder)service).getService();
Log.i(LOG_TAG, "Counter Service Connected");
}
public void onServiceDisconnected(ComponentName className) {
counterService = null;
Log.i(LOG_TAG, "Counter Service Disconnected");
}
};
}
Counter是应用程序Counter的根Activity组件,因此,它必须要从类Activity继承下来。Countert同时实现了计数器回调接口
ICounterCallback,以便可以实时地更新用户界面上的计数器。
Counter组件是应用程序Counter的主界面,它上面有两个按钮 Start Counter和Stop Counter,以及一个文本框。其中按钮
Strat Counter和Stop Counter分别用来启动和停止CounterService组件中的计数器,文本框用来实时显示这个计数器的当前计数器,文本框用来实时显示这个计数器的当前计数。
Counter组件被启动时,它的成员函数onCreate就会被调用,这时候它就会调用其父类ContextWrapper的成员函数bindService
来启动CounterService组件。当CounterService组件启动起来之后,Countrer组件内部的ServiceConnection对象serviceConnection的成员函数onServiceConnected()就会被调用。这时候Counter组件就会获得CounterService组件中的一个Binder本地对象。由于Counter组件和CounterService组件试运行在同一个应用程序中的,因此,ServiceConnection对象serviceConnection的成员函数onServiceConnected()可以安全地将前面获得的一个Binder本地对象转换为一个类型为CounterBinder的对象,接着再调用它的成员函数getService来获得一个计数器接口。有了这个计数器接口之后,Counter组件就相当于将CounterService组件绑定起来了。
当点击用户界面上的Star Counter按钮时,Counter组件就会调用内部的一个计数器接口counterService的成员函数startCounter()来启动CounterService组件中的计数器,并且将自己的一个ICounterCallback接口传递给CounterService组件,以便CounterService组件可以将内部的计数器的当前计数器更新到它的用户界面的文本框中,如它的成员函数count()所示。
当点击用户界面上的 Stop Counter按钮时,Counter组件就会调用内部的一个计数器counterService的成员函数stopCounter()
来停止 CounterService 组件中的计数器。
由于Counter组件在启动时绑定了 CounterService组件,因此,当它被销毁时,即它的成员函数 onDestory()被调用时,他就必须调用其父类ContextWrapper的成员函数 unbinderService来解绑CounterService组件。解绑成功后,Counter组件内部的
ServiceConnection对象serviceConnection的成员函数onServiceDisconnected就会被调用,用来执行清理工作。
main.xml
<?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"
android:gravity="center">
<LinearLayout
android:layout_width ="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10px"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="4px"
android:text="@string/counter"
android:gravity="center"
android:id="@+id/textView">
</TextView>
<TextView
android:id="@+id/textview_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/init_counter">
</TextView>
</LinearLayout>
<LinearLayout
android:layout_width ="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/button_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/start" />
<Button
android:id="@+id/button_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/stop" />
</LinearLayout>
Strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Counter</string>
<string name="counter">Counter:</string>
<string name="init_counter">0</string>
<string name="start">Start Counter</string>
<string name="stop">Stop Counter</string>
</resources>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.counter">
<application
android:icon="@mipmap/icon"
android:label="@string/app_name">
<activity
android:name=".Counter"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".CounterService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := My_Counter
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
# Use the folloing include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))
build/target/product/core.mk 中添加 My_Counter
mmm packages/apps/Counter/