Framework Ethernet模块添加接口

1,714 阅读5分钟

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使用我们提供的接口服务了。

完。