AIDL
AIDL:Android Interface Definition Language
AIDL是一种接口描述语言,常用于进程间通信(IPC,Inter-Process Communication),编译器会根据定义的aidl文件自动生成同名的java文件,通过预先定义的接口以及Binder机制进行进程间通信。客户端通过bindService与远程服务器建立连接,远程服务器返回一个IBinder对象,客户端通过asInterface方法获取这个IBinder对象,进而可以通过IBinder对象调用远程服务器的方法。
SsoAuth.aidl(远程调用端)
// SsoAuth.aidl
package com.songzheng.cilent_server;
// Declare any non-default types here with import statements
interface SsoAuth {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
/**
* 实现Sso授权
*/
void ssoAuth(String userName, String pwd);
}
创建一个AIDL文件并且声明远程要调用的方法后Build->Make Project后会自动生成一个同名的.java文件。服务端和客户端都必须要有相同的AIDL文件,并且AIDL文件包名必须是相同的。
服务端中完成AIDL文件的编码和.Java文件的生成之后,要写一个服务,当客户端绑定服务时服务的onBind方法返回一个IBinder对象,这样服务端就可以通过这个IBinder对象远程访问服务中的方法。注意返回的IBinder对象继承自AIDL文件生成的java文件的Stub静态抽象类。
v
SinaSsoAuthService.java(远程调用端)
package com.songzheng.cilent_server;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;
/**
* Created by make on 2016/11/20.
*/
public class SinaSsoAuthService extends Service{
private SinaSsoImpl mBinder = new SinaSsoImpl();
@Override
public void onCreate() {
super.onCreate();
Log.e("SinaSsoAuthService", "Service onCreate!!!");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private class SinaSsoImpl extends SsoAuth.Stub{
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void ssoAuth(String userName, String pwd) throws RemoteException {
//Toast.makeText(getApplicationContext(), "执行Sso登录:用户名:" + userName + " 密码:" + pwd, Toast.LENGTH_SHORT).show();
Log.e("SinaSsoAuthServiec", "执行Sso登录:用户名:" + userName + " 密码:" + pwd);
}
}
}
在ssoAuth方法中我试图使用Toast显示一段提示,但是报错:java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare(),原因是非主线程中默认没有创建Looper对象,需要调用Looper的prepare方法启动Looper。
这样远程调用端的逻辑就完成了,接下来是Client。
Client有和服务端相同的AIDL文件和.java文件,并且AIDL文件包名相同。在MainActivity中设置一个Button的点击监听事件,点击按钮绑定服务并且连接成功时在ServiceConnection中的onServiceConnected方法获取远程服务器端返回的IBinder对象,通过SsoAuth.Stub.asInterface方法将IBinder对象转换成SsoAuth对象,进而调用远程服务器端的方法。这里由于Android 5.0以上版本Google出于安全角度不允许隐式绑定Service,阅读源码后发现在validateServiceIntent中会判断Component和Package是否为null,只要有一个不为null就不会进而判断SDK版本了。
官方推荐解决方案:
Intent mIntent = new Intent();
mIntent.setAction("XXX.XXX.XXX");//你定义的service的action
mIntent.setPackage(getPackageName());//这里你需要设置你应用的包名
context.startService(mIntent);
我没试,而是直接在网上找了一段代码。
MainActivity.java:(客户端)
package com.songzheng.cilent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.songzheng.cilent_server.SsoAuth;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private Button btSsoAuth;
private SsoAuth mBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btSsoAuth = (Button) findViewById(R.id.bt_ssoauth);
btSsoAuth.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mBinder == null) {
bindSsoAuthService();
} else {
doSsoAuth();
}
}
});
}
private void bindSsoAuthService() {
final Intent intent = new Intent();
intent.setAction("com.songzheng.aidl.SinaSsoAuthService");
final Intent eIntent = new Intent(createExplicitFromImplicitIntent(this, intent));
bindService(eIntent, mConnection, BIND_AUTO_CREATE);
Log.e("MainActivity", "bindSsoAuthService");
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e("onServiceConnected", "onServiceConnected");
mBinder = SsoAuth.Stub.asInterface(iBinder);
doSsoAuth();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBinder = null;
}
};
private void doSsoAuth() {
Log.e("doSsoAuth", "doSsoAuth");
try {
Log.e("doSsoAuth", "mBinder.ssoAuth()");
Log.e("doSsoAuth", mBinder.toString());
mBinder.ssoAuth("TestCount", "TestPwd");
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
/***
* Android L (lollipop, API 21) introduced a new problem when trying to invoke implicit intent,
* "java.lang.IllegalArgumentException: Service Intent must be explicit"
*
* If you are using an implicit intent, and know only 1 target would answer this intent,
* This method will help you turn the implicit intent into the explicit form.
*
* Inspired from SO answer: http://stackoverflow.com/a/26318757/1446466
* @param context
* @param implicitIntent - The original implicit intent
* @return Explicit Intent created from the implicit original intent
*/
public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
}
这样就完成了进程间通信。