美文网首页面试题AIDLBinder机制
android跨进程通信AIDL使用

android跨进程通信AIDL使用

作者: migill | 来源:发表于2019-12-22 15:09 被阅读0次

    AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。

    AIDL的本质是系统提供了一套可快速实现Binder的工具。

    关键类和方法:

    • AIDL接口:继承IInterface。
    • Stub类:Binder的实现类,服务端通过这个类来提供服务。
    • Proxy类:服务器的本地代理,客户端通过这个类调用服务器的方法。
    • asInterface():客户端调用,将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。
      返回对象:
      若客户端和服务端位于同一进程,则直接返回Stub对象本身;
      若客户端和服务端位于不同一进程,返回的是系统封装后的Stub.proxy对象。
    • asBinder():根据当前调用情况返回代理Proxy的Binder对象。
    • onTransact():运行服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
    • transact():运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。


    如何使用AIDL

    1.先建立两个android项目
    创建一个BinderA项目和一个BinderB工程,BinderA项目调用BinderB中提供的登录接口,在BinderB中点击登录后,登录之后不管是否成功,BinderB都会调用BinderA中登录返回接口,这样就实现了一个双向的通信。



    2、创建一个包名用来存放aidl文件
    创建一个包名用来存放aidl文件,比如com.migill.binder,在里面新建ILoginInterface.aidl文件,如果需要访问自定义对象,还需要建立对象的aidl文件,这里我们由于使用了自定义对象LoginUser,所以,还需要创建LoginUser.aidl和LoginUser.java。注意,这三个文件,需要都放在com.migill.binder包里。而且ILoginInterface.aidl,LoginUser.aidl,LoginUser.java这三个文件在BinderA与BinderB项目中是一样的。下面描述如何写这三个文件。
    客户端中的aidl文件与服务端中的aidl文件的包名要一样,在创建客户端AIDL文件的时候是不需要把服务端提供的接口都添加进来,只需要添加需要的接口就好了

    // ILoginInterface.aidl
    package com.migill.binder;
    import com.migill.binder.LoginUser;
    interface ILoginInterface {
        // 登录
        void login();
        // 登录返回
        void loginCallback(boolean loginStatus, inout LoginUser loginUser);
    }
    

    说明:
    aidl中支持的参数类型为:

    • 基本类型(ibyte,int,long,float,double,boolean,char),String类型,CharSequence类型,List,Map。
    • 其他类型必须使用import导入,即使它们可能在同一个包里,比如这里的LoginUser,尽管它和ILoginInterface在同一个包中,但是还是需要显示的import进来。另外,接口中的参数除了aidl支持的类型,其他类型必须标识其方向:到底是输入还是输出抑或两者兼之,用in,out或者inout来表示,上面的代码我们用inout标记,其实用in就可以,因为它是输入型参数(我的理解是BinderB现在是客户端,BinderA是服务端,BinderB中的数据流入BinderA中,所以是入型参数)。

    在gen下面可以看到,AS为我们自动生成了一个代理类public static abstract class Stub extends android.os.Binder implements com.migill.binder.ILoginInterface可见这个Stub类就是一个普通的Binder,只不过它实现了我们定义的aidl接口。
    它还有一个静态方法public static com.migill.binder.ILoginInterface asInterface(android.os.IBinder obj)这个方法很有用,通过它,我们就可以在客户端中得到MyService的实例,进而通过实例来调用其方法。

    // LoginUser.aidl
    package com.migill.binder;
    parcelable LoginUser;
    
    package com.migill.binder;
    import android.os.Parcel;
    import android.os.Parcelable;
    import java.util.Locale;
    public final class LoginUser implements Parcelable {
        public String userName;
        public String userPassWord;
        public LoginUser() {
        }
        protected LoginUser(Parcel in) {
            readFromParcel(in);
        }
        public void readFromParcel(Parcel in) {
            userName = in.readString();
            userPassWord = in.readString();
        }
        public static final Creator<LoginUser> CREATOR = new Creator<LoginUser>() {
            @Override
            public LoginUser createFromParcel(Parcel in) {
                return new LoginUser(in);
            }
            @Override
            public LoginUser[] newArray(int size) {
                return new LoginUser[size];
            }
        };
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getUserPassWord() {
            return userPassWord;
        }
        public void setUserPassWord(String userPassWord) {
            this.userPassWord = userPassWord;
        }
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(userName);
            dest.writeString(userPassWord);
        }
        @Override
        public String toString() {
            return String.format(Locale.ENGLISH, "[ %s, %s ]", userName, userPassWord);
        }
    }
    

    3.BinderA项目
    MyService.java中的loginCallback()中发送了一个广播,广播在LoginActivity中接收。
    有个问题就是有可能你的service只想让某个特定的apk使用,而不是所有apk都能使用,这个时候,你需要重写Stub中的onTransact方法,根据调用者的uid来获得其信息,然后做权限认证,如果返回true,则调用成功,否则调用会失败。对于其他apk,你只要在onTransact中返回false就可以让其无法调用MyService中的方法,这样就可以解决这个问题了。

    public class MyService extends Service {
        private static String TAG = "migill_A";
        private static final String PACKAGE_SAYHI = "com.migill.binder.b";
        @Override
        public IBinder onBind(Intent intent) {
            return new ILoginInterface.Stub() {
                @Override
                public void login() throws RemoteException {
                }
                @Override
                public void loginCallback(boolean loginStatus, LoginUser loginUser) throws RemoteException {
                    Log.e(TAG, "loginStatus: " + loginStatus + " / LoginUser: " + loginUser.toString());
                    sendLoginCallBackBrodcast(loginStatus, loginUser);
                }
                //在这里可以做权限认证,return false意味着客户端的调用就会失败,比如下面,只允许包名为com.migill.binder.a的客户端通过,
                //其他apk将无法完成调用过程
                public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                        throws RemoteException {
                    String packageName = null;
                    String[] packages = MyService.this.getPackageManager().
                            getPackagesForUid(getCallingUid());
                    if (packages != null && packages.length > 0) {
                        packageName = packages[0];
                    }
                    Log.e(TAG, "onTransact: " + packageName);
                    if (!PACKAGE_SAYHI.equals(packageName)) {
                        return false;
                    }
                    return super.onTransact(code, data, reply, flags);
                }
            };
        }
    
        private void sendLoginCallBackBrodcast(boolean loginStatus, LoginUser loginUser) {
            Intent intent = new Intent("com.migill.binder.a.LOGINCAKKBACK_STATE");
            intent.putExtra("loginStatus", loginStatus);
            intent.putExtra("loginUser", loginUser);
            sendBroadcast(intent);
        }
    }
    

    AndroidManifest.xml中添加service标签

         <!--
                     代表在应用程序里,当需要该service时,会自动创建新的进程。
                     android:process=":remote"
                     是否可以被系统实例化
                     android:enabled="true"
                     代表是否能被其他应用隐式调用
                     android:exported="true"
            -->
            <service
                android:name=".service.MyService"
                android:enabled="true"
                android:exported="true"
                android:process=":remote">
                <intent-filter>
                    <!-- 激活 MyService 唯一name,不能重名 -->
                    <action android:name="BinderA_Action" />
                </intent-filter>
            </service>
    

    LoginActivity绑定BinderB提供的服务,在点击QQ按钮的时候,调用BinderB中提供的登录接口。在LoginActivity定义了一个广播接受者,用于接收BinderB返回的登录结果

    public class LoginActivity extends Activity {
        private static String TAG = "migill_A";
        private boolean isStartRemote; // 是否开启跨进程通信
        private ILoginInterface iLogin; // AIDL定义接口
        LoginCallbackReceiver loginCallbackReceiver;
        TextView tvLogincallback;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            requestWindowFeature(Window.FEATURE_NO_TITLE); // 隐藏标题
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN); // 设置全屏
    
            setContentView(R.layout.activity_login);
            tvLogincallback = findViewById(R.id.tv_logincallback);
            initBindService();//初始化
            RegisterBroadcast();
        }
        public void RegisterBroadcast() {
            IntentFilter filter = new IntentFilter();
            filter.addAction("com.migill.binder.a.LOGINCAKKBACK_STATE");
            loginCallbackReceiver = new LoginCallbackReceiver();
            registerReceiver(loginCallbackReceiver, filter);
        }
        // 点击事件
        public void startQQLoginAction(View view) {
            if (iLogin != null) {
                try {
                    // 调用Server方法
                    iLogin.login();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            } else {
                Toast.makeText(this, "请安装QQ应用...", Toast.LENGTH_SHORT).show();
            }
        }
        public void initBindService() {
            Intent intent = new Intent();
            // 设置Server应用Action
            intent.setAction("BinderB_Action");
            // 设置Server应用包名(5.1+要求)
            intent.setPackage("com.migill.binder.b");
            // 开始绑定服务
            bindService(intent, conn, BIND_AUTO_CREATE);
            // 标识跨进程绑定
            isStartRemote = true;
        }
        // 服务连接
        private ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iLogin = ILoginInterface.Stub.asInterface(service);
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (isStartRemote) {
                // 解绑服务,一定要记得解绑服务,否则可能会报异常(服务连接资源异常)
                unbindService(conn);
            }
            unregisterReceiver(loginCallbackReceiver);
        }
        private void LoginCallback(boolean loginStatus, LoginUser loginUser) {
            Log.e(TAG, "LoginCallback loginStatus: " + loginStatus + " / LoginUser: " + loginUser.toString());
            tvLogincallback.setText("登录结果: " + (loginStatus==true?"登录成功":"登录失败") + "\n" +
                    "LoginUser: " + loginUser.toString());
        }
        private final class LoginCallbackReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                boolean loginStatus = intent.getBooleanExtra("loginStatus", false);
                LoginUser loginUser = (LoginUser) intent.getParcelableExtra("loginUser");
                LoginCallback(loginStatus, loginUser);
            }
        }
    }
    

    4.BinderB项目
    MyService.java中在login()中启动了MainActivity。

    public class MyService extends Service {
        private static String TAG = "migill_B";
        private static final String PACKAGE_SAYHI = "com.migill.binder.a";
        @Override
        public IBinder onBind(Intent intent) {
            return new ILoginInterface.Stub() {
                @Override
                public void login() throws RemoteException {
                    Log.e(TAG, "BinderB_MyService");
                    // 单项通信有问题,真实项目双向通信,双服务绑定
                    serviceStartActivity();
                }
                @Override
                public void loginCallback(boolean loginStatus, LoginUser loginUser) throws RemoteException {
                }
                //在这里可以做权限认证,return false意味着客户端的调用就会失败,比如下面,只允许包名为com.migill.binder.a的客户端通过,
                //其他apk将无法完成调用过程
                public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                        throws RemoteException {
                    String packageName = null;
                    String[] packages = MyService.this.getPackageManager().
                            getPackagesForUid(getCallingUid());
                    if (packages != null && packages.length > 0) {
                        packageName = packages[0];
                    }
                    Log.e(TAG, "onTransact: " + packageName);
                    if (!PACKAGE_SAYHI.equals(packageName)) {
                        return false;
                    }
                    return super.onTransact(code, data, reply, flags);
                }
    
            };
        }
        /**
         * 在Service启动Activity,需要配置:.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         */
        private void serviceStartActivity() {
            Intent intent = new Intent(this, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
    }
    

    AndroidManifest.xml中添加service标签

       <!--
                     代表在应用程序里,当需要该service时,会自动创建新的进程。
                     android:process=":remote"
                     是否可以被系统实例化
                     android:enabled="true"
                     代表是否能被其他应用隐式调用
                     android:exported="true"
            -->
            <service
                android:name="com.migill.binder.b.service.MyService"
                android:enabled="true"
                android:exported="true"
                android:process=":remote_service">
                <intent-filter>
                    <!-- 激活 MyService 唯一name,不能重名-->
                    <action android:name="BinderB_Action" />
                </intent-filter>
            </service>
    

    BinderA调用了BinderB提供的登录接口,BinderB的登录接口执行启动MainActivity,在BinderB项目的MainActivity中绑定了BinderA中提供的服务。在点击登录的时候,在调用BidnerA中登录返回接口。

    public class MainActivity extends Activity {
        private static String TAG = "migill_B";
        // 模拟用户名和密码 的值
        private final static String NAME = "migill";
        private final static String PWD = "123";
        private EditText nameEt;
        private EditText pwdEt;
        private boolean isStartRemote; // 是否开启跨进程通信
        private ILoginInterface iLogin; // AIDL定义接口
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            requestWindowFeature(Window.FEATURE_NO_TITLE);  // 隐藏标题
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);    // 设置全屏
            setContentView(R.layout.activity_main);
            nameEt = findViewById(R.id.nameEt);
            pwdEt = findViewById(R.id.pwdEt);
            initBindService();
        }
    
        public void initBindService() {
            Intent intent = new Intent();
            // 设置Server应用Action
            intent.setAction("BinderA_Action");
            // 设置Server应用包名(5.1+要求)
            intent.setPackage("com.migill.binder.a");
            // 开始绑定服务
            bindService(intent, conn, BIND_AUTO_CREATE);
            // 标识跨进程绑定
            isStartRemote = true;
        }
    
        // 服务连接
        private ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。
                //返回对象:
                //若客户端和服务端位于同一进程,则直接返回Stub对象本身;
                //若客户端和服务端位于不同一进程,返回的是系统封装后的Stub.proxy对象。
                iLogin = ILoginInterface.Stub.asInterface(service);
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        // 点击事件
        public void startLogin(View view) {
            final String name = nameEt.getText().toString();
            final String pwd = pwdEt.getText().toString();
            if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)) {
                showToast("请输入用户名或密码...");
                return;
            }
            // 模拟登录状态加载...
            final ProgressDialog progressDialog = new ProgressDialog(this);
            progressDialog.setTitle("登录");
            progressDialog.setMessage("正在登录中...");
            progressDialog.show();
            new Thread(new Runnable() {
                @Override
                public void run() {
    
                    SystemClock.sleep(1000);
    
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                boolean isLoginSuccess = false;
                                if (NAME.equals(name) && PWD.equals(pwd)) {
                                    showToast("QQ登录成功!");
                                    isLoginSuccess = true;
                                    finish();
                                } else {
                                    showToast("QQ登录失败!");
                                    progressDialog.dismiss();
                                }
                                LoginUser loginUser = new LoginUser();
                                loginUser.userName = name;
                                loginUser.userPassWord = pwd;
                                iLogin.loginCallback(isLoginSuccess, loginUser);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }).start();
    
        }
        private void showToast(String text) {
            Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (isStartRemote) {
                // 解绑服务,一定要记得解绑服务,否则可能会报异常(服务连接资源异常)
                unbindService(conn);
            }
        }
    }
    

    总结:我们要先安装BinderB项目,这样BinderB就会在ServiceManager中进行注册。BinderA就可以在ServiceManager中通过名字查找,获取Binder对象。

    相关文章

      网友评论

        本文标题:android跨进程通信AIDL使用

        本文链接:https://www.haomeiwen.com/subject/xbrgnctx.html