系统服务与 ServiceManager
Android Binder 机制是安卓应用运行的基础,一个应用的运行需要无法避免地和系统提供的Binder做交互。Android 系统以服务(Service)的方式暴露出很多Binder对象,准确的说我们拿到的是Binder代理对象(BinderProxy),真正的Binder对象运行于安卓系统进程中(system_process)。我们的应用以夸进程的方式调用系统提供的各种服务,通常以Context.getSystemService()的方式获取系统服务,常见的有 ActivityManager, AlarmManager, InputMethodManager, ConnectivityManager, LayoutInflater等等,其中有一部分是普通对象,大部分是对系统Binder对象的封装。那应用又是如何拿到系统的Binder对象的呢?如果我们要拿到其他应用进程的Binder对象一般会使用ServiceConnection连接其他进程的Service拿到IBinder。然而系统的IBinder是用ServiceManager暴露给应用进程的。下面以获取InputMethodManager为例分析应用是如何获取系统IBinder对象的。
通过下面的代码可以拿到InputMethodManager:
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
调用的是Context的实现类ContextImpl:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
然后看SystemServiceRegistry的getSystemService方法:
/**
* Gets a system service from a given context.
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
可以看到是在SYSTEM_SERVICE_FETCHERS中取到ServiceFetcher,然后调用fetcher.getService(ctx)拿到的,事实上SYSTEM_SERVICE_FETCHERS是一个Map:HashMap<String, ServiceFetcher<?>>
并且是SystemServiceRegistry类的静态变量,在类首次加载的时候初始化好:
static {
registerService(Context.INPUT_METHOD_SERVICE, InputMethodManager.class,
new StaticServiceFetcher() {
@Override
public InputMethodManager createService() {
return InputMethodManager.getInstance();
}});
//省略其他。。。。。
}
这样我们就可以通过Context.INPUT_METHOD_SERVICE拿到对应的ServiceFetcher,看一下ServiceFetcher是啥:
/**
* Base interface for classes that fetch services.
* These objects must only be created during static initialization.
*/
static abstract interface ServiceFetcher {
T getService(ContextImpl ctx);
}
以及他的子类StaticServiceFetcher:
static abstract class StaticServiceFetcher implements ServiceFetcher {
private T mCachedInstance;
@Override
public final T getService(ContextImpl unused) {
synchronized (StaticServiceFetcher.this) {
if (mCachedInstance == null) {
mCachedInstance = createService();
}
return mCachedInstance;
}
}
public abstract T createService();
}
以及CachedServiceFetcher
static abstract class CachedServiceFetcher implements ServiceFetcher {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
if (service == null) {
service = createService(ctx);
cache[mCacheIndex] = service;
}
return (T)service;
}
}
public abstract T createService(ContextImpl ctx);
}
结合看来最终会调用createService拿到对应的服务类:InputMethodManager.getInstance(),接下来看InputMethodManager的getInstance方法:
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
这里可以看到,通过ServiceManager.getService(Context.INPUT_METHOD_SERVICE)获取了一个系统提供的Binder对象并转化成远程调用接口(IInterface),我们在操作InputMethodManager的时候最终都会通过这个Binder对象远程过程调用(RPC)系统的Binder对象,最终实现与系统的交互。了解过Binder的同学应该很熟悉这段代码,进程间远程调用也是通过这种方式实现的。下面逐行分析:
1.ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
ServiceManager类getService方法:
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
这里的sCache是一个Map,如果cache中有这个Binder对象就直接返回了,如果没有就调用getIServiceManager().getService(name)来获取
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
可以看到sServiceManager本身是一个Binder对象,对应的是系统服务ServiceManagerService,通过它可以拿到所有系统注册的服务,然而这个Binder对象是通过BinderInternal.getContextObject()获取的:
/**
* Return the global "context object" of the system. This is usually
* an implementation of IServiceManager, which you can use to find
* other services.
*/
public static final native IBinder getContextObject();
注释已经解释的很清楚了:
返回系统的全局“上下文对象”。这通常是IServiceManager的实现类,可以使用它来查找其他服务。
拿到Binder对象后转换为远程调用接口:service = IInputMethodManager.Stub.asInterface(b);
和普通的Binder对象一样需要转换成IInterface对象才能正常调用远程方法,
public static com.android.internal.view.IInputMethodManager asInterface(android.os.IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.android.internal.view.IInputMethodManager))) {
return ((com.android.internal.view.IInputMethodManager)iin);
}
return new com.android.internal.view.IInputMethodManager.Stub.Proxy(obj);
}
上面的代码也很简单,如果是在当前进程(system_process)直接返回本身,如果是应用进程则返回一个代理对象。
然后使用获取的IInputMethodManager对象构造InputMethodManager:
sInstance = new InputMethodManager(service, Looper.getMainLooper())
最后再看一下InputMethodManager的showSoftInput方法:
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
if (mServedView != view && (mServedView == null
|| !mServedView.checkInputConnectionProxy(view))) {
return false;
}
try {
return mService.showSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
}
return false;
}
}
这里的mService就是IInputMethodManager,一个Binder对象,可以看到,启动软键盘最终是一次远程过程调用。不仅如此,启动Activity等等都需要远程调用。
>> 转载请注明来源:系统服务与 ServiceManager●非常感谢您的阅读,欢迎订阅微信公众号(右边扫一扫)以表达对我的认可与支持,我会在第一时间同步文章到公众号上。当然也可点击下方打赏按钮为我打赏。
●另外也可以支持一下我的副业,扫描右方代购二维码加我好友,不买看看也行。朋友在荷兰读医学博士,我和他合作经营的代购,欧洲正规商店采购,正品保证。
免费分享,随意打赏
OnClickListener
老哥您好 请问背景里的动画是如何实现的
Linmin Qiu
使用这个js库:jscanvas-nest.min.js
paozhuanyinyu
大神,你的开源项目InputmethodHolder中issues5中说到的监听键盘收起,不知道现在hook IInputConnectionWrapper成功了吗?
pqpo
现在很多方案都是通过对布局的监听来实现软键盘的监听,其实已经可以满足大部分场景需求了。反而通过 hook 容易出现问题