Android Service完全解析(下)

转载http://blog.csdn.net/guolin_blog/article/details/9797169

 

C语言中怎么着使用宏C(和C++)中的宏(Macro)属于编写翻译器预处理的框框,属于编写翻译期概念(而非运营期概念)。上边对常碰到的宏的使用难题做了简便易行总计。

在上一篇文章中,大家上学了Android
Service相关的不在少数重庆大学内容,包括Service的为主用法、Service和Activity举行通讯、Service的绝迹情势、Service与Thread的涉嫌、以及怎样创立前台Service。以上所提到的这么些知识点,基本上涵盖了绝大部分平常支出工作中间或者使用到的Service技术。可是至于Service其实还有1个越来越高端的选择技巧没有介绍,即远程Service的用法。使用远程Service甚至能够兑现Android跨进程通信的意义,上面就让大家切实地读书一下。

关于#和#\

在C语言的宏中,#的功用是将其背后的宏参数举办字符串化操作(Stringfication),简单说正是在对它所引述的宏变量 通过轮换后在其左右各拉长2个双引号。比如下边代码中的宏:

#define WARN_IF(EXP)    do{ if (EXP)    fprintf(stderr, "Warning: " #EXP "/n"); }   while(0)

那正是说实际上应用中会出现下边所示的交替进程:

WARN_IF (divider == 0);

被替换为


do {

if (divider == 0)

fprintf(stderr, "Warning" "divider == 0" "/n");

} while(0);

诸如此类每一遍divider(除数)为0的时候便会在正儿八经错误流上输出3个提醒音信。

而##被称作连接符(concatenator),用来将多少个Token连接为四个Token。注意那里三番五次的目的是Token就行,而不一定 是宏的变量。比如您要做多个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名以内有直观的、名字上的关联。那么上边包车型客车代码就越发实用:

struct command

{

char * name;

void (*function) (void);

};

#define COMMAND(NAME) { NAME, NAME ## _command }

// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:

struct command commands[] = {

COMMAND(quit),

COMMAND(help),

...

}

COMMAND宏在这边担任三个代码生成器的功能,那样能够在任其自流程度上减小代码密度,间接地也可以减掉不留心所导致的荒唐。大家还足以n个##标志连接
n+3个Token,那几个特点也是#标记所不享有的。比如:

#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d

typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);

// 这里这个语句将展开为:

//  typedef struct _record_type name_company_position_salary;

 

关于…的使用

…在C宏中称为Variadic Macro,也正是变参宏。比如:

#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)

// 或者

#define myprintf(templt,args...) fprintf(stderr,templt,args)

先是个宏中由于并未对变参起名,大家用暗中认可的宏__VA_ARGS__来取代它。第二个宏
中,大家显式地命名变参为args,那么大家在宏定义中就能够用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出现。当上面包车型客车宏中大家不得不提供第一个参数templt时,C标准须求大家务必写成:

myprintf(templt,);

的花样。那时的轮换进度为:

myprintf("Error!/n",);

替换为:


fprintf(stderr,"Error!/n",);

那是叁个语法错误,不能够符合规律编写翻译。那一个标题一般有八个缓解办法。首先,GNU
CPP提供的化解措施允许地点的宏调用写成:

myprintf(templt);

而它将会被通过轮换变成:

fprintf(stderr,"Error!/n",);

很鲜明,那里仍旧会爆发编写翻译错误(非本例的一点情状下不会爆发编写翻译错误)。除了那种办法外,c99和GNU
CPP都援助上边包车型大巴宏定义情势:

#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)

这时,##以此一连符号充当的法力正是当__VAR_ARGS__为空的时候,消除后面包车型客车不得了逗号。那么此时的翻译进度如下:

myprintf(templt);

被转化为:


fprintf(stderr,templt);

这么一旦templt合法,将不会发出编写翻译错误。
那里列出了一部分宏使用中易于失误的地点,以及方便的选用方法。

在上篇作品中大家精通了,Service其实是运作在主线程里的,假诺直白在Service中拍卖部分耗费时间的逻辑,就会招致程序ANKoleos。

指鹿为马的嵌套-Misnesting

宏的定义不肯定要有完全的、配对的括号,但是为了幸免失误并且升高可读性,最佳制止那样使用。

 

由操作符优先级引起的标题-Operator Precedence Problem

是因为宏只是简不难单的更迭,宏的参数假如是复合结构,那么通过轮换之后恐怕鉴于各类参数之间的操作符优先级高于单个参数内部各部分之间相互成效的操作符优先级,要是大家绝不括号爱护各类宏参数,恐怕会时有发生预想不到的情况。比如:

#define ceil_div(x, y) (x + y - 1) / y

那么

a = ceil_div( b & c, sizeof(int) );

将被转正为:

a = ( b & c  + sizeof(int) - 1) / sizeof(int);

// 由于+/-的优先级高于&的优先级,那么上面式子等同于:

a = ( b & (c + sizeof(int) - 1)) / sizeof(int);

那肯定不是调用者的初衷。为了防止那种景色时有产生,应当多写多少个括号:

#define ceil_div(x, y) (((x) + (y) - 1) / (y))

让大家来做个实验求证一下啊,修改上一篇小说中开创的ServiceTest项目,在MyService的onCreate()方法中让线程睡眠60秒,如下所示:

免去多余的分店-Semicolon Swallowing

司空眼惯状态下,为了使函数模样的宏在表面上看起来像二个家常的C语言调用一样,日常状态下大家在宏的后面加上三个分号,比如上边包车型客车带参宏:

MY_MACRO(x);

而是只借使底下的图景:

#define MY_MACRO(x) {    /* line 1 */    /* line 2 */    /* line 3 */ }

//...

if (condition())

MY_MACRO(a);

else

{...}

这么会由于多出的尤其分号发生编写翻译错误。为了防止那种场馆出现同时保险MY_MACRO(x);的这种写法,大家供给把宏定义为那种方式:

#define MY_MACRO(x) do {

/* line 1 */    /* line 2 */    /* line 3 */ } while(0)

如此那般假使有限支撑总是选择分号,就不会有其余难题。

[java] view
plain

copy

Duplication of Side Effects

此处的Side
Effect是指宏在展开的时候对其参数恐怕进行数次伊娃luation(也正是取值),不过假设那么些宏参数是1个函数,那么就有恐怕被调用多次于是实现不同的结果,甚至会生出更严重的谬误。比如:

#define min(X,Y) ((X) > (Y) ? (Y) : (X))

//...

c = min(a,foo(b));

那儿foo()函数就被调用了五遍。为了缓解那一个隐秘的题材,我们应有那样写min(X,Y)这几个宏:

#define min(X,Y) ({  typeof (X) x_ = (X);    typeof (Y) y_ = (Y);    (x_ < y_) ? x_ : y_; })

({…})的功效是将内部的几条语句中最终一条的值重返,它也允许在其间宣称变量(因为它经过大括号组成了一个有个别Scope)。

  1. public class MyService extends Service {  
  2.   
  3.     ……  
  4.   
  5.     @Override  
  6.     public void onCreate() {  
  7.         super.onCreate();  
  8.         Log.d(TAG, “onCreate() executed”);  
  9.         try {  
  10.             Thread.sleep(60000);  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.     }  
  15.       
  16.     ……  
  17.   
  18. }  

再也运行后,点击一下Start Service按钮或Bind
Service按钮,程序就会阻塞住并无法开始展览其余其它操作,过一段时间后就会弹出AN昂科拉的提醒框,如下图所示。

 

图片 1

 

前边大家提到过,应该在Service中打开线程去履行耗费时间任务,那样就足以有效地幸免ANPAJERO的面世。

 

那正是说本篇文章的核心是介绍远程Service的用法,假设将MyService转换到四个长距离Service,还会不会有AN奥迪Q3的情况呢?让大家来入手尝试一下吗。

 

将五个普普通通的Service转换来远程Service其实拾贰分简单,只须求在注册Service的时候将它的android:process属性内定成:remote就能够了,代码如下所示:

[html] view
plain

copy

  1. <?xml version=”1.0″ encoding=”utf-8″?>  
  2. <manifest xmlns:android=”http://schemas.android.com/apk/res/android”  
  3.     package=”com.example.servicetest”  
  4.     android:versionCode=”1″  
  5.     android:versionName=”1.0″ >  
  6.   
  7.     ……  
  8.       
  9.     <service  
  10.         android:name=”com.example.servicetest.MyService”  
  11.         android:process=”:remote” >  
  12.     </service>  
  13.   
  14. </manifest>  

方今再也运营程序,并点击一下Start
Service按钮,你会看到控制台立时打字与印刷了onCreate()
executed的音讯,而且主界面并没有阻塞住,也不会现出AN奇骏。大致过了一分钟后,又会看到onStartCommand()
executed打字与印刷了出来。

 

怎么将MyService转换来远程Service后就不会招致程序ANENVISION了呢?那是由于,使用了远程Service后,MyService已经在其它多少个历程个中运维了,所以只会卡住该进程中的主线程,并不会影响到日前的应用程序。

 

为了证实一下MyService以往确实已经运维在其它贰个历程个中了,大家独家在MainActivity的onCreate()方法和MyService的onCreate()方法里进入一行日志,打字与印刷出个别所在的历程id,如下所示:

 

[java] view
plain

copy

  1. Log.d(“TAG”, “process id is ” + Process.myPid());  

重复重复运转程序,然后点击一下Start Service按钮,打字与印刷结果如下图所示:

留神android打字与印刷的音信,将来亟需在八个经过上面看

 

图片 2

 

可以看来,不仅仅是经过id分裂了,就连应用程序包名也分裂了,MyService中打字与印刷的那条日志,包名后边还跟上了:remote标识。

 

那既然远程Service这么好用,干脆未来大家把持有的Service都转换到远程Service吧,还省得再打开线程了。其实不然,远程Service非但倒霉用,甚至足以称得上是较为难用。一般情状下假诺能够不行使远程Service,就尽量不要接纳它。

 

上面就来看一下它的害处吧,首先将MyService的onCreate()方法中让线程睡眠的代码去除掉,然后再一次运维程序,并点击一下Bind
Service按钮,你会发现先后崩溃了!为啥点击Start
Service按钮程序就不会崩溃,而点击Bind
Service按钮就会崩溃呢?那是由于在Bind
Service按钮的点击事件之中我们会让MainActivity和MyService建立关系,可是近日MyService已经是叁个长途Service了,Activity和Service运转在四个不一致的历程个中,那时就不能够再利用古板的树立关系的法门,程序也就夭折了。

 

那么怎么着才能让Activity与2个远道Service建立关系呢?那即将动用AIDL来开始展览跨进程通讯了(IPC)。

 

AIDL(Android Interface Definition
Language)是Android接口定义语言的意思,它能够用来让某些瑟维斯与四个应用程序组件之间举行跨进度通信,从而得以兑现多个应用程序共享同叁个Service的功能。

 

下边大家就来一步步地看一下AIDL的用法到底是何许的。首先需求新建三个AIDL文件,在这几个文件中定义好Activity要求与Service举办通讯的法门。新建MyAIDLService.aidl文件,代码如下所示:

[java] view
plain

copy

  1. package com.example.servicetest;  
  2. interface MyAIDLService {  
  3.     int plus(int a, int b);  
  4.     String toUpperCase(String str);  
  5. }  

点击保存之后,gen目录下就会变动二个对应的Java文件,如下图所示:

 

 图片 3

 

然后修改MyService中的代码,在中间达成大家恰好定义好的MyAIDLService接口,如下所示:

[java] view
plain

copy

  1. public class MyService extends Service {  
  2.   
  3.     ……  
  4.   
  5.     @Override  
  6.     public IBinder onBind(Intent intent) {  
  7.         return mBinder;  
  8.     }  
  9.   
  10.     MyAIDLService.Stub mBinder = new Stub() {  
  11.   
  12.         @Override  
  13.         public String toUpperCase(String str) throws RemoteException {  
  14.             if (str != null) {  
  15.                 return str.toUpperCase();  
  16.             }  
  17.             return null;  
  18.         }  
  19.   
  20.         @Override  
  21.         public int plus(int a, int b) throws RemoteException {  
  22.             return a + b;  
  23.         }  
  24.     };  
  25.   
  26. }  

此地首先对MyAIDLService.Stub进行了贯彻,重写里了toUpperCase()和plus()那八个章程。那五个章程的法力分别是将三个字符串全体转换到大写格式,以及将八个传入的整数举办相加。然后在onBind()方法准将MyAIDL瑟维斯.Stub的兑现重返。那里怎么能够那样写吗?因为Stub其实正是Binder的子类,所以在onBind()方法中得以直接重临Stub的完成。

 

接下去修改MainActivity中的代码,如下所示:

[java] view
plain

copy

  1. public class MainActivity extends Activity implements OnClickListener {  
  2.   
  3.     private Button startService;  
  4.   
  5.     private Button stopService;  
  6.   
  7.     private Button bindService;  
  8.   
  9.     private Button unbindService;  
  10.       
  11.     private MyAIDLService myAIDLService;  
  12.   
  13.     private ServiceConnection connection = new ServiceConnection() {  
  14.   
  15.         @Override  
  16.         public void onServiceDisconnected(ComponentName name) {  
  17.         }  
  18.   
  19.         @Override  
  20.         public void onServiceConnected(ComponentName name, IBinder service) {  
  21.             myAIDLService = MyAIDLService.Stub.asInterface(service);  
  22.             try {  
  23.                 int result = myAIDLService.plus(3, 5);  
  24.                 String upperStr = myAIDLService.toUpperCase(“hello world”);  
  25.                 Log.d(“TAG”, “result is ” + result);  
  26.                 Log.d(“TAG”, “upperStr is ” + upperStr);  
  27.             } catch (RemoteException e) {  
  28.                 e.printStackTrace();  
  29.             }  
  30.         }  
  31.     };  
  32.   
  33.     ……  
  34.   
  35. }  

笔者们只是修改了ServiceConnection中的代码。能够见见,那里首先接纳了MyAIDLService.Stub.asInterface()方法将盛传的IBinder对象传换到了MyAIDLService对象,接下去就足以调用在MyAIDLService.aidl文件中定义的有着接口了。那里我们第①调用了plus()方法,并传到了3和5用作参数,然后又调用了toUpperCase()方法,并传播hello
world字符串作为参数,最终将调用方法的回来结果打印出来。

 

昨天重国民党的新生活运动行程序,并点击一下Bind Service按钮,能够看来打字与印刷日志如下所示:

 

图片 4

 

总而言之,我们确实已经打响促成跨进度通讯了,在2个进度中访问到了别的1个进度中的方法。

 

不过你也得以看出,如今的跨进度通讯其实并从未什么样实质上的效果,因为那只是在二个Activity里调用了同3个应用程序的瑟维斯里的方法。而跨进度通讯的真的含义是为了让三个应用程序去做客另三个应用程序中的Service,以达成共享Service的效益。那么上边大家自然要读书一下,如何才能在其他的应用程序中调用到MyService里的主意。

 

在上一篇小说中大家已经理解,假设想要让Activity与Service之间建立关系,须要调用bind瑟维斯()方法,并将Intent作为参数字传送递进去,在Intent里内定好要绑定的瑟维斯,示例代码如下:

[java] view
plain

copy

  1. Intent bindIntent = new Intent(this, MyService.class);  
  2. bindService(bindIntent, connection, BIND_AUTO_CREATE);  

那边在创设Intent的时候是利用MyService.class来钦点要绑定哪一个Service的,不过在另1个应用程序中去绑定Service的时候并没有MyService那几个类,那时就亟须选拔到隐式Intent了。今后修改AndroidManifest.xml中的代码,给MyService加上七个action,如下所示:

[html] view
plain

copy

  1. <?xml version=”1.0″ encoding=”utf-8″?>  
  2. <manifest xmlns:android=”http://schemas.android.com/apk/res/android”  
  3.     package=”com.example.servicetest”  
  4.     android:versionCode=”1″  
  5.     android:versionName=”1.0″ >  
  6.   
  7.     ……  
  8.   
  9.     <service  
  10.         android:name=”com.example.servicetest.MyService”  
  11.         android:process=”:remote” >  
  12.         <intent-filter>  
  13.             <action android:name=”com.example.servicetest.MyAIDLService”/>  
  14.         </intent-filter>  
  15.     </service>  
  16.   
  17. </manifest>  

这就认证,MyService能够响应带有com.example.servicetest.MyAIDLService那么些action的Intent。

 

后天再也运转一下主次,那样就把远程瑟维斯端的劳作总体形成了。

 

接下来创造1个新的Android项目,起名为ClientTest,大家就尝试在这一个顺序中国远洋运输总集团程调用MyService中的方法。

 

ClientTest中的Activity假如想要和My瑟维斯建立关联其实也简单,首先须要将MyAIDLService.aidl文件从ServiceTest项目中拷贝过来,注意要将原始的包路径一起拷贝过来,完毕后项目标构造如下图所示:

 

图片 5

 

接下来打开或新建activity_main.xml,在布局文件中也参加三个Bind
Service按钮:

[html] view
plain

copy

  1. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”  
  2.     android:layout_width=”match_parent”  
  3.     android:layout_height=”match_parent”  
  4.     android:orientation=”vertical”  
  5.      >  
  6.   
  7.    <Button   
  8.        android:id=”@+id/bind_service”  
  9.        android:layout_width=”match_parent”  
  10.        android:layout_height=”wrap_content”  
  11.        android:text=”Bind Service”  
  12.        />  
  13.   
  14. </LinearLayout>  

接下去打开或新建MainActivity,在中间参预和MyService建立关联的代码,如下所示:

[java] view
plain

copy

  1. public class MainActivity extends Activity {  
  2.   
  3.     private MyAIDLService myAIDLService;  
  4.   
  5.     private ServiceConnection connection = new ServiceConnection() {  
  6.   
  7.         @Override  
  8.         public void onServiceDisconnected(ComponentName name) {  
  9.         }  
  10.   
  11.         @Override  
  12.         public void onServiceConnected(ComponentName name, IBinder service) {  
  13.             myAIDLService = MyAIDLService.Stub.asInterface(service);  
  14.             try {  
  15.                 int result = myAIDLService.plus(50, 50);  
  16.                 String upperStr = myAIDLService.toUpperCase(“comes from ClientTest”);  
  17.                 Log.d(“TAG”, “result is ” + result);  
  18.                 Log.d(“TAG”, “upperStr is ” + upperStr);  
  19.             } catch (RemoteException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     };  
  24.   
  25.     @Override  
  26.     protected void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.         setContentView(R.layout.activity_main);  
  29.         Button bindService = (Button) findViewById(R.id.bind_service);  
  30.         bindService.setOnClickListener(new OnClickListener() {  
  31.             @Override  
  32.             public void onClick(View v) {  
  33.                 Intent intent = new Intent(“com.example.servicetest.MyAIDLService”);  

      1. intent.setPackage(“com.example.servicetest”);
  34.                 bindService(intent, connection, BIND_AUTO_CREATE);  

  35.             }  
  36.         });  
  37.     }  
  38.   
  39. }  

这一部分代码大家肯定会优秀熟练吧?没错,这和在ServiceTest的MainActivity中的代码大概是完全相同的,只是在让Activity和Service建立关系的时候大家运用了隐式Intent,将Intent的action钦命成了com.example.servicetest.MyAIDLService。

 

在当下Activity和MyService建立关联之后,大家照例是调用了plus()和toUpperCase()那多少个形式,远程的MyService会对传播的参数进行拍卖并重返结果,然后将结果打字与印刷出来。

 

这样的话,ClientTest中的代码也就全部成就了,未来运转一下这么些种类,然后点击Bind
Service按钮,此时就会去和长途的MyService建立关联,观望LogCat中的打字与印刷消息如下所示:

 

图片 6

 

不用小编说,大家都早已观看,大家的跨进程通讯功效已经圆满兑现了。

 

可是还有某个亟需说明的是,由于那是在区别的经过之间传递数据,Android对那类数据的格式帮助是不行不难的,基本上只好传递Java的骨干数据类型、字符串、List或Map等。那么一旦本人想传递二个自定义的类该怎么做吧?那就非得要让那些类去落到实处Parcelable接口,并且要给那几个类也定义三个同名的AIDL文件。那有的内容并不复杂,而且和Service关系一点都不大,所以就不再详细实行教学了,感兴趣的意中人能够协调去查看一下有关的材料。

 

好了,结合上下两篇,那便是关于Service你所需清楚的全部。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图