2017-10-09

概述

  所谓Android系统服务其本质就是一个通过AIDL跨进程通信的小Demo的延伸而已。按照 AIDL 跨进程通信的标准创建一套程序,将服务端通过系统进程来运行实现永驻内存,在其它程序中就可以通过约定好的方式来建立通信了。而所谓回调,本质上也是一个 AIDL 跨进程通信,只不过是将回调的服务端放在系统服务通信的客户端中而已。

  本实例我们模拟一个灯光管理功能。设备中有一盏灯,我们定义一个系统服务用于统一控制灯的亮灭操作,客户端可以发送控制灯光的请求给服务端,也可以接收来自服务端的反馈信息。系统环境为 Android 4.4 。

  本文所述的知识,是本人参考了很多网上的前辈们的文章才学习到的,在这里将本人对于系统服务及添加回调接口的理解书以成文,以期能将知识分享出去。

创建一个系统服务

  一般不会直接将新建的代码文件混入Android原生代码目录中,这里我们的习惯是创建一个自己的组织或者公司的名称的目录专门用于存放这些自定义代码,作为演示,本例程中的代码都放置于自建的 manufactor 目录内。

撰写基础代码

  首先定义一个 AIDL 文件,此处为简化实例,仅定义一个接口。

 ./frameworks/base/core/java/com/manufactor/ILightManager.aidl

package com.manufactor; interface ILightManager {
boolean setLight(boolean isOpen);
}

  其次是定义暴露给其它程序使用的Manager接口类 LightManager.java 这个类的定义方式,尤其是构造函数可以说是有固定格式的,按照下列代码中的格式来书写即可。

 ./frameworks/base/core/java/com/manufactor/LightManager.java

package com.manufactor; import android.util.Log;
import android.content.Context;
import android.os.RemoteException; import com.manufactor.ILightManager; public class LightManager { private ILightManager manager; public LightManager(Context ctx, ILightManager manager){
this.manager = manager;
} public boolean setLight(boolean isOpen){
try{
//将点灯请求发送到服务端处理。
return manager.setLight(isOpen);
}catch(RemoteException e){
e.printStackTrace();
}
return false;
}
}

  再然后是创建服务端的程序 LightService.java 。一般而言,服务端里写的代码就是真正的干活的代码了。但是在这里,我们仅是添加一个打印作为演示就够了。

 ./frameworks/base/services/java/com/manufactor/server/LightService.java
package com.manufactor.server; import android.content.Context;
import android.util.Log; import com.manufactor.ILightManager; public class LightService extends ILightManager.Stub { private static final String TAG = "LightService"; private Context mContext; public LightService(Context ctx){
/*
可根据自己的实际情况来决定是否需要传入 Context 参数。
*/
mContext = ctx;
} @Override
public boolean setLight(boolean iso){
Log.d(TAG, "setLight:"+iso);
return true;
}
}

  至此,创建一个系统服务的第一步就已经完成了。万事俱备,只欠东风。接下来就要将服务端在系统进程中注册,并且要让 LightService 与 LightManager 产生联系。

在系统中注册

  将定义接口的 AIDL 添加到编译队列中。

 ./frameworks/base/Android.mk

在 framework-base 模块中将 aidl 文件的路径添加进去,参照 mk 文件中已有的添加 aidl 的写法即可。

  在 Context 中添加用于识别这个服务的标识符。这条其实不重要,但是为了规范起见,还是应该不辞麻烦地写上的。

 ./frameworks/base/core/java/android/content/Context.java

  然后是注册成为系统服务,开机自动运行。

 ./frameworks/base/services/java/com/android/server/SystemServer.java

这个类里有一个内部类 ServerThread 。注册的动作一般写在这个内部类的   public void initAndLoop()  方法中。其实就是参照文件中已有的注册服务的写法,在其后面添加上自己的服务注册代码即可。系统中几乎所有的服务都是在这启动的,如果你写的服务对其它服务有依赖关系,那么就应该考虑代码放置的先后顺序问题。在本实例中,我们没有依赖其它服务,因此不需要考虑放置位置。

上图中黄色底纹处就用到了我们在上一步中定义的标识符。

如此一来,我们的灯光管理服务就会随着系统的启动而运行了。下面还有最后一步:将 LightService 与 LightManager “建立联系”。

在 ContextImpl 类的静态初始化块中实现。

./frameworks/base/core/java/android/app/ContextImpl.java

如此一来,在其它程序中,通过 mContext.getSystemServer() 时,传入灯光管理的标识符,就可以得到 LightManager 类的对象了,从而也就可以与灯光管理的服务端进行通信了。

编译,将编译产物 framework.jar  framework2.jar  services.jar 推到设备的 /system/framework/ 目录下,重启系统即可。

对于某些源代码,可能还需要先执行一下  make update-api 命令后才可完成编译工作。

至此,整个的系统级服务就已经完成了,下面我们再写一个测试程序来测试一下是否可以工作。

测试APK

创建一个Activity,有一个按钮,点击它,即与灯光管理服务发消息。下面列出主要代码

 public class MainActivity extends Activity {

     private static final String TAG = "LightTestAPK";

     private LightManager lm;

     @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //取得实例。
lm = (LightManager)getSystemService(Context.LIGHTSERVICE);
if(lm == null) {
throw new RuntimeException("LightService doesn't working!");
}
} public void click(View v) {
boolean ret = lm.setLight(false);
Log.d(TAG, "setLight result:"+ret);
}
}

Android.mk中要引用 framework JAVA库才能编译通过。

LOCAL_JAVA_LIBRARIES := framework

将APK安装进设备中,运行它,并监听日志即可证明灯光管理服务的运行状态。

实现系统服务的回调

  根据上述方式添加的系统服务,它并不是一个“全双工”通信方式的程序,服务端一般不能主动联系客户端。那么要如何实现这一功能呢?我们可以尝试给它添加一个回调功能。

前面概述中也讲到过,所谓回调其实就是另外创建多一个 AIDL 通信程序,将服务端放到灯光管理程序中的客户端里去,将回调的客户端放到灯光管理程序的服务端中去而已。

  首先先定义用于回调的接口。这个接口用于外部程序注册回调时作为参数来使用。

 ./frameworks/base/core/java/com/manufactor/LightListener.java

package com.manufactor; public interface LightListener {
void onStatusChange(boolean isOpen);
}

  其次,定义用于描述上述接口的 AIDL 类。这个 AIDL 的作用就是用于灯光管理程序中的服务端与客户端之间进行回调的跨进程通信

 ./frameworks/base/core/java/com/manufactor/ILightListener.java

package com.manufactor; interface ILightListener {
void onStatusChange(boolean isOpen);
}

  再然后便是创建一个类用于实现上面定义的 AIDL 接口。这个类在灯光管理程序中的客户端中创建实例,由于它是 AIDL 的子类,因此可以作为参数传递给灯光管理程序的服务端,在服务端中保留一个“句柄”,供服务端回调用。

 ./frameworks/base/core/java/com/manufactor/LightListenerCallback.java
package com.manufactor; import com.manufactor.ILightListener;
import com.manufactor.LightListener; public class LightListenerCallback extends ILightListener.Stub { private LightListener listener; public LightListenerCallback(LightListener lstn){
listener = lstn;
} @Override
public void onStatusChange(boolean isOpen){
if(listener != null){
listener.onStatusChange(isOpen);
}
}
}

  至此,回调接口已经全部定义好了。接下来就是将定义好的回调接口应用到现有的系统服务程序中去了。

  先来改造一下 ILightManager.aidl 添加一个注册回调的接口。在前面描述的 ILightManager.aidl 中添加多一个接口即可:

void setOnListener(ILightListener lstn);

  然后便是在 LightManager.java 中添加暴露给外部应用调用的注册回调的方法:

 ./frameworks/base/core/java/com/manufactor/LightManager.java

public void setOnListener(LightListener lstn){
try{
LightListenerCallback callback = new LightListenerCallback(lstn);
manager.setOnListener(callback);
}catch(RemoteException e){
e.printStackTrace();
}
}

外部应用在调用这个方法进行回调方法注册时,就可以像为Button注册点击事件监听一样,将回调方法对象作为参数传入即可。

  最后一步便是在 LightService.java 中处理传递过来的回调类“句柄”。这里的改造主要是实现了在 ILightManager.aidl 中新增的注册回调的方法,以及模拟了一个灯泡状态改变的事件,用于调用回调接口,以将消息通知到客户端中去。在这里,为了能实现一个服务端支持多个客户端同时注册事件监听,要把接收到的回调接口“句柄”用一个集合类来管理。并且,还应该实现反注册回调监听的功能,但是这里为了简化实例,就不做反注册的功能了。

 ./frameworks/base/service/java/com/manufactor/server/LightService.java

public class LightService extends ILightManager.Stub { private static final String TAG = "LightService"; private Context mContext;
private ArrayList<ILightListener> lList = new ArrayList<ILightListener>(); public LightService(Context ctx){
/*
可根据自己的实际情况来决定是否需要传入 Context 参数。
*/
mContext = ctx;
//模拟状态发生改变的事件。
new Thread(){
public void run(){
while(true){
try{
Thread.sleep(2000);
}catch(Exception e){ }
//通知客户端,灯泡状态发生改变。
for(ILightListener l:lList){
try{
l.onStatusChange(true);
}catch(RemoteException e){
e.printStackTrace();
}
}
}
}
}.start();
} @Override
public boolean setLight(boolean iso){
Log.d(TAG, "setLight:"+iso);
return true;
} @Override
public void setOnListener(ILightListener lstn){
/*
将注册的回调通过集合来管理,可以实现多客户端
同时监听的功能。
*/
if(!lList.contains(lstn)){
lList.add(lstn);
}
}
}

  如此一来,在系统服务中回调的功能就全部做好了。我们再在测试APK中添加测试方法。还是在上面添加系统服务时的测试APK的基础之上添加测试回调的代码。

编译,推入设备中,监听日志,运行后可以看到如下图所示的日志:

由打印可以看到,正序传递消息没有问题,服务端回调功能也运行正常。

当然,其实服务端与客户端通信的功能完全可以通过发送广播或者其它进程间通信的方式来实现 。喜欢哪种用哪种,我只是觉得这种回调的方式挺有趣的~

代码: https://pan.baidu.com/s/1kUPnJBD

添加一个Android框架层的系统服务与实现服务的回调的更多相关文章

  1. android 框架层 常用类介绍

    名称 功能描述 示意图 activitymanager 管理应用程序的周期并提供常用的回退功能 window manager 窗口管理者 content provider 用于访问另一个的数据,或者共 ...

  2. tensorflow 添加一个全连接层

    对于一个全连接层,tensorflow都为我们封装好了. 使用:tf.layers.dense() tf.layers.dense( inputs, units, activation=None, u ...

  3. 进入第一个Android应用界面

    前话 距离上次学习Android已经过去了半年了,这半年我干嘛去了? 嘛相信大家也没兴趣了解,简单来说就是我学习了周边的知识技术,最后终于转回Android. 感觉开发一个Android需要很多知识吧 ...

  4. Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...

  5. Android应用程序框架层和系统运行库层日志系统源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6598703 在开发Android应用程序时,少 ...

  6. Android Native层异步消息处理框架

     *本文系作者工作学习总结,尚有不完善及理解不恰当之处,欢迎批评指正* 一.前言 在NuPlayer中,可以发现许多类似于下面的代码: //============================== ...

  7. 手把手带你打造一个 Android 热修复框架

    本文来自网易云社区 作者:王晨彦 Application 处理 上面我们已经对所有 class 文件插入了 Hack 的引用,而插入 dex 是在 Application 中,Application ...

  8. Android环境下通过C框架层控制WIFI【转】

    本文转载自:https://blog.csdn.net/edw200/article/details/52192631 本人是从事Linux嵌入式开发的,安卓wifi控制在安卓JAVA层已经做得非常成 ...

  9. Android 应用框架层 SQLite 源码分析

    概述   Android 在应用框架层为开发者提供了 SQLite 相关操作接口,其归属于android.database.sqlite包底下,主要包含SQLiteProgram, SQLiteDat ...

随机推荐

  1. 实战Asp.Net Core:部署应用

    1.前言 某一刻,你已经把 .Net Core 的程序写好了.接下来,还可以做什么呢?那就是部署了. 作为一名开发工程师,如果不会部署自己开发的应用,那么这也是不完整的.接下来,我们就来说说,如何部署 ...

  2. 开启Tomcat的manager页面访问

    如何进入Tomcat的manager页面 一张图解决! 找到conf目录下的tomcat-users.xml文件,打开. <role rolename="admin-gui" ...

  3. k8s

    https://www.cnblogs.com/sheng-jie/p/10591794.html

  4. 通用权限管理系统多语言开发标准接口 - java,php 调用标准接口程序参考

    1:公司里有多个业务系统,需要进行统一重构,有PHP的.有Java的.有.NET的,甚至还有 Delphi 的. 2:公司里有多个数据库系统,有mysql的.有sqlserver的.还有oracel的 ...

  5. win10 再次重装系统

    去年经历了一次硬盘损坏,一蹶不振,伤了元气, 生产环境的系统一直没有好好的维护,我个人也是,有时一闪而过的窗口总让我觉得有什么不对,现在终于出现问题,XNA项目突然无法编译 提示: 严重性 代码 说明 ...

  6. c++ 入门之深入探讨拷贝函数和内存分配

    在c++入门之深入探讨类的一些行为时,说明了拷贝函数即复制构造函数运用于如下场景: 对象作为函数的参数,以值传递的方式传给函数. 对象作为函数的返回值,以值的方式从函数返回 使用一个对象给另一个对象初 ...

  7. hana-banach定理

    1.  x1不是X除开G以外所有的空间 2.如果极大元不是全空间的话,根据前面的讨论,还可以延拓,这就和极大矛盾了

  8. Windows之系统自带截屏快捷键

    Windows之系统自带截屏快捷键 现在我们都习惯了使用QQ截屏,但是有时候电脑没有网络,也就意味着无法登陆QQ,在这个时候再有截屏的需求时,我们就束手无策了. 截取全屏 现在我说以个Windows系 ...

  9. 【Python3练习题 014】 一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如6=1+2+3。编程找出1000以内的所有完数。

    a.b只要数字a能被数字b整除,不论b是不是质数,都算是a的因子.比如:8的质因子是 2, 2, 2,但8的因子就包括 1,2,4. import math   for i in range(2, 1 ...

  10. css 图片文字垂直居中

    先来看张图片 相信很多css新手遇到过这种问题,就是当图片和文本显示在一行的时候,效果很奇葩,文字和图片没法对齐, 这时我们需要做的是: 1,先给块级元素设置 display: inline-bloc ...