EthernetService
本篇内容基于Android 6.0.1,涉及到的framework类
frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetService.java frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetServiceImpl.java \frameworks\base\core\java\android\net\EthernetManager.java \frameworks\base\core\java\android\net\IEthernetManager.aidl \frameworks\base\core\java\android\net\IpConfiguration.java /frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
以太网中在一般的Android Phone中是不存在的,不过在一些智能硬件中带有以太网的模块,因此需要有一系列的 接口来访问以太网的状态信息。比如当前的以太网的模式和连接状态等
但是,EthernetService的服务接口只是在framework层可用,并没有开放给用户。实际上看过源码可以看到一些@hide标记的 api和类,这也说明它们对用户层不可用。那么我们如何去获取以太网的状态信息呢?
本篇将介绍如何在framework中EthernetService中添加这样的接口来供用户访问,这里以添加获取当前的以太网的模式为例,我们知道Android 中Service提供服务是通过Binder来支持的,而AIDL为跨进程访问提供用户访问的接口,而以太网服务的接口是通过IEthernetManager.aidl来定义的 在SystemService启动时会去注册一系列的Service到ServiceManager中去,这里我们先看EthernetService是如何启动的。
public final class SystemServer {
……
private static final String ETHERNET_SERVICE_CLASS =
"com.android.server.ethernet.EthernetService";
public static void main(String[] args) {
new SystemServer().run();
}
private void run()
{
try{
...
startOtherServices();
...
}
....
}
private void startOtherServices() {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
}
}
}
在SystemService中会去通过一个SystemServiceManager的startService方法来启动,从参数大概可以猜出来它会通过反射去创建Service实例。
@SuppressWarnings("unchecked")
public SystemService startService(String className) {
final Class<SystemService> serviceClass;
try {
serviceClass = (Class<SystemService>)Class.forName(className);
} catch (ClassNotFoundException ex) {
Slog.i(TAG, "Starting " + className);
throw new RuntimeException("Failed to create service " + className
+ ": service class not found, usually indicates that the caller should "
+ "have called PackageManager.hasSystemFeature() to check whether the "
+ "feature is available on this device before trying to start the "
+ "services that implement it", ex);
}
return startService(serviceClass);
}
//SystemServiceManager
public <T extends SystemService> T startService(Class<T> serviceClass) {
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
// Create the service.
if (!SystemService.class.isAssignableFrom(serviceClass)) {
throw new RuntimeException("Failed to create " + name
+ ": service must extend " + SystemService.class.getName());
}
final T service;
try {
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (NoSuchMethodException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (InvocationTargetException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service constructor threw an exception", ex);
}
// Register it.
mServices.add(service);
// Start it.
try {
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + name
+ ": onStart threw an exception", ex);
}
return service;
}
通过反射创建EthernetService实例,并添加到列表中管理起来。随后调用onStart启动它。我们先看看EthernetService,它有可能就是我们的要找的Binder Server。
package com.android.server.ethernet;
import android.content.Context;
import android.util.Log;
import com.android.server.SystemService;
public final class EthernetService extends SystemService {
private static final String TAG = "EthernetService";
final EthernetServiceImpl mImpl;
public EthernetService(Context context) {
super(context);
mImpl = new EthernetServiceImpl(context);
}
@Override
public void onStart() {
Log.i(TAG, "Registering service " + Context.ETHERNET_SERVICE);
publishBinderService(Context.ETHERNET_SERVICE, mImpl);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mImpl.start();
}
}
}
EthernetService的实现很简单,它并不是真正的Binder Server,它的任务很简单是就是去注册一个服务EthernetServiceImpl,注册到哪去了呢? 这还用问?当然是ServiceManager中,这里EthernetService继承自SystemService,publishBinderService正是在它里面实现的,它将服务实体 注册到ServiceManager中去。那么毋庸置疑,这里的EthernetServiceImpl就是Binder Server了。
/**
* EthernetServiceImpl handles remote Ethernet operation requests by implementing
* the IEthernetManager interface.
*
* @hide
*/
public class EthernetServiceImpl extends IEthernetManager.Stub {
//在service中实现我们添加的接口
@Override
public int getEthernetMode()
{
Log.d(TAG,"getEthernetMode=="+(mIpConfiguration.ipAssignment == IpAssignment.DHCP));
return (mIpConfiguration.ipAssignment == IpAssignment.DHCP) ? 0 : 1;
}
}
从其实现来看它的确是,因为它继承了IEthernetManager.Stub,从而有了跨进程的能力。我们看看它提供了的接口定义
/** {@hide} */
interface IEthernetManager
{
IpConfiguration getConfiguration();
void setConfiguration(in IpConfiguration config);
boolean isAvailable();
void addListener(in IEthernetServiceListener listener);
void removeListener(in IEthernetServiceListener listener);
int getEthernetConnectState();
boolean setEthernetEnabled(in boolean enable);
int getEthernetIfaceState();
//添加的服务接口
int getEthernetMode();
}
看过context的同学可能知道ContextImpl在创建的时候会去注册一些服务管理对象,这些都是被添加到静态实例中,以供多个context实例共享使用,这也是我们为什么 能够通过Context.getSystemService获取服务的原因,而EthernetService是通过EthernetManager来管理的。
registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
new CachedServiceFetcher<EthernetManager>() {
@Override
public EthernetManager createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.ETHERNET_SERVICE);
IEthernetManager service = IEthernetManager.Stub.asInterface(b);
return new EthernetManager(ctx.getOuterContext(), service);
}});
那么我们为什么不能用Context获取getSystemService来获取EthernetService的管理者从而访问其服务接口呢? 原因是Context中不允许我们这么做,对ETHERNET_SERVICE使用了@hide,这样我们就不能取到ETHERNET_SERVICE的服务, 如果我们在代码中直接引用EthernetManager也会报错,因为EthernetManager对上层是不可见的。
public abstract class Context {
2832 /**
2833 * Use with {@link #getSystemService} to retrieve a {@link
2834 * android.net.EthernetManager} for handling management of
2835 * Ethernet access.
2836 *
2837 * @see #getSystemService
2838 * @see android.net.EthernetManager
2839 *
2840 * @hide
2841 */
2842 public static final String ETHERNET_SERVICE = "ethernet";
}
这可如何是好?有一个办法,那就是通过反射,通过反射可以取到service的实例,从而进行访问service这些隐藏的api,当然这也有很大的局限性, 而且会带来可能存在的风险,比如当系统版本变动后就会带来不可预知的问题。但对于特定的硬件产品(系统确定且不会再变更)来说,这也算是一种有效的方法。为了避免可能存在的问题,我们最好添加一些先验条件来使用。
private static int getEthernetMode(Context context)
{
int result = -1;
if(/*一些校验条件*/) {
try {
String ETHERNET_SERVICE = (String) Context.class.getField("ETHERNET_SERVICE").get(null);
Class<?> ethernetManagerClass = Class.forName("android.net.EthernetManager");
Object ethernetManager = context.getSystemService(ETHERNET_SERVICE);
Field mService = ethernetManagerClass.getDeclaredField("mService");
mService.setAccessible(true);
Object mServiceObject = mService.get(ethernetManager);
Class<?> iEMgrClass = Class.forName("android.net.IEthernetManager");
Method[] methods = iEMgrClass.getDeclaredMethods();
for (Method ms : methods) {
if (ms.getName().equals("getEthernetMode")) {
result = (Integer) ms.invoke(mServiceObject);
break;
}
}
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
L.d("ethernet", "exception==>" + e.getMessage());
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}
编译
编译 framework.jar
在上述的内容中我们添加了getEthernetMode以获取以太网的模式,即是动态还是静态的。我们的改动主要在aidl和service中,因此需要编译相关的模块。 首先执行
source ./build/envsetup.sh
lunch xxx
cd frameworks/base
mm -B
编译得到 framework.jar ,它的位置在 out\target\product\xxx\system\framework\framework.jar
编译完成后我们也可以看到生成的IEthernetManager.java了,它的位置在 \out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\src\core\java\android\net\IEthernetManager.java IEthernetManager.java为IEthernetManager.aidl对应的java文件,打开它就可以看到我们定义的接口
编译ethernet-service.jar
cd frameworks/opt/net/ethernet/
mm -B
编译得到ethernet-service.jar ,它的位置在 out\target\product\xxx\system\framework\ethernet-service.jar
最后将framework.jar和ethernet-service.jar push到system/framework/目录下重启后就可以在客户程序中通过getEthernetMode使用我们提供的接口服务了。
完。