Ipc: aidl sexy, not a curse

Preview:

Citation preview

Build a sustainable app with an IPC mechanism

IPC: AIDL is Sexy, not a curse

Yonatan Levin

> 10M users

Ruby, Golang, Python, Android,

iOS

52 cities

> 1000 members

Largest StudyJam

in the World

AndroidAcademy

What are we going to do?

Learn how to talk

➔ Became best fellows with IPC➔ Best friends with Binder➔ Fall in love with AIDL

Goal:

Make our app run smooth in the background while performing multiple long tasks (like

playing music) unrelated to the UI

DarthVaderActivity

@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.darth_vader_activity); findViewById(R.id.iv_dva_build_death_star).setOnClickListener(this); findViewById(R.id.iv_dva_interact_with_luke).setOnClickListener(this);}

EmpireService<service android:name=".EmpireService" android:enabled="true" android:process=":remote"></service>

Why separate process?● GC not affecting your app

● Crashing not affecting UI

● Less chance to be killed:

○Process serving others has at least the ranking of those it serve

○Process with Service > Process with Background activities

● New Heap Space

● Smaller cold start + resource efficiency

● Because we can! :)

Why and when not?●If you don’t have driver licenses●Memory leaks●Two process●Keep it simple, stupid

https://github.com/parahall/AIDLServiceExample.git

Try this at home

IPC?

Inter-process communication (IPC) is a framework for the exchange of signals and data across multiple processes

Linux legacy

Android

Why Binder?

Performance

Security Stability Memory

Benefit for free●Thread migration●Identifying senders to receivers●Unique object-mapping across process●AIDL!!!!●Bult-in support for marshalling●Local execution mode (no IPC) if same process

We are already using binder today

●LifeCycle callbacks (onResume, OnDestroy) invoked by ActivityManagerService

●Intents

●Content Provider

1

IPC using Intentgit checkout IPCwithIntent

DarthVaderActivity@Overridepublic void onClick(View v) { Intent intent = new Intent(this, EmpireService.class); switch (v.getId()) { case R.id.iv_dva_build_death_star: intent.putExtra(COMMAND_TYPE, EmpireService.EmpireServiceCommands.BUILD_DEATH_STAR); break; case R.id.iv_dva_interact_with_luke: intent.putExtra(COMMAND_TYPE, EmpireService.EmpireServiceCommands.FIND_LUKE); break; } mStartedCommandTime = System.currentTimeMillis(); startService(intent);}

DarthVaderActivity

public class EmpireServiceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) {

Log.d(TAG,"Time took: " +String.valueOf(System.currentTimeMillis()- mStartedCommandTime));

Toast.makeText(DarthVaderActivity.this, "Death Star deployed and ready for your command, my lord", Toast.LENGTH_LONG).show(); }}

EmpireServicepublic int onStartCommand(Intent intent, int flags, int startId) { EmpireServiceCommands command = (EmpireServiceCommands) intent.getExtras() .get(COMMAND_TYPE);

switch (command) { case BUILD_DEATH_STAR:

..do some hard work... Intent jobDoneIntent = new Intent(EMPIRE_SERVICE_ACTION); jobDoneIntent.putExtra("result", new DeathStar(270000, 270000, "THIS IS THE BIG GUN")); sendBroadcast(jobDoneIntent); break; case FIND_LUKE: break; } stopSelf(); return START_NOT_STICKY;}

Pain?

Not really OOPAsync onlyLoosely-definedNot so fastHigh Level Abstraction of Binder

2

IPC using Messengergit checkout IPCwithMessenger

A reference to a Handler that can be sent to a remote process via an Intent

Messages sent by the remote process via the messenger are delivered to the local handler

Relatively fast

Overview

DarthVaderActivitypublic void onClick(View v) { Intent intent = new Intent(this, EmpireService.class); Messenger messenger = new Messenger(new DarthVaderHandler(this)); intent.putExtra("ImperialMessenger", messenger); switch (v.getId()) { case R.id.iv_dva_build_death_star: intent.putExtra("Command type", EmpireService.EmpireServiceCommands.BUILD_DEATH_STAR); break; case R.id.iv_dva_interact_with_luke: intent.putExtra("Command type", EmpireService.EmpireServiceCommands.FIND_LUKE); break; } startService(intent);}

DarthVaderActivityprivate static class DarthVaderHandler extends Handler {

private final WeakReference<DarthVaderActivity> clientRef;

public DarthVaderHandler(DarthVaderActivity client) { this.clientRef = new WeakReference<>(client); }

...//Handle message}

DarthVaderActivityprivate static class DarthVaderHandler extends Handler {

@Override public void handleMessage(Message msg) { Bundle data = msg.getData(); DarthVaderActivity client = clientRef.get(); if (client != null && msg.what == EmpireService.CALLBACK_MSG && data != null) {

Toast.makeText(client, "Death Star deployed and ready for your command, my lord", Toast.LENGTH_LONG).show(); } }}

EmpireService

public int onStartCommand(Intent intent, int flags, int startId) { Messenger messenger = intent.getParcelableExtra("ImperialMessenger"); EmpireServiceCommands command = (EmpireServiceCommands) intent.getExtras() .get("Command type");

… more code coming...

EmpireServiceswitch (command) { case BUILD_DEATH_STAR:

..do some hard work... if (messenger != null) { Message message = Message.obtain(); message.what = CALLBACK_MSG; Bundle data = new Bundle(1); data.putParcelable("result", new DeathStar(270000, 270000, "THIS IS THE BIG GUN")); message.setData(data); try { messenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } } break; }

But what if...We want sync calls?

Full OOPBusiness Transparent

3

IPC using AIDLgit checkout IPCwithAIDL

Android Interface Definition Language (AIDL)Our business operation on top of Binder objectJava-like interfaceDefined in a separate .aidl fileAIDL Generate code of real java interface

.AIDL interface

interface IStarWars { String buildDeathStar(out DeathStar deathStar); oneway void findLuke(in IEmpireServiceResponseListener listener);}

DeathStar.aidlpackage com.academy.android.aidlserviceexample;// Declare DeathStar so AIDL can find it and knows that it implements// the parcelable protocol.parcelable DeathStar;

IEmpireServiceResponseListener.aidlinterface IEmpireServiceResponseListener { void onResponse(String response);}

IStarWars.Stub/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.academy.android.aidlserviceexample.IStarWars {

@Overridepublic java.lang.String buildDeathStar( com.academy.android.aidlserviceexample.DeathStar deathStar) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_buildDeathStar, _data, _reply, 0); … not really necessary to dig in

Interface directional flagin means that the object is transferred from client to

service and only used for inputs. If any changes are made to the bar object in the service then they won’t reflect in the client.

out indicates that the object has no relevant data and will be populated by the service and returned as a response.

inout means that the data gets copied, i.e., if any changes are made to bar in the service then that’ll also reflect in the client’s bar object.

AIDL Data Types

All primitives and arrays of primitives

FileDescriptorSerializable/

Parcelable****

MapBundleListSparseArray

Sync Call - Death Star

Let the fun begin!

IStarWarsImplementationpublic class IStarWarsImplementation extends IStarWars.Stub {

public String buildDeathStar(DeathStar deathStar) throws RemoteException {

...doing hard work..

deathStar.setBFG("BIG GUN IS VERY VERY");deathStar.setHeight(270000);deathStar.setWidth(270000);return "Death Star deployed and ready for your command, my

lord";

}…other interface methods

EmpireServicepublic class EmpireService extends Service {

private IStarWarsImplementation service;

@Override public void onCreate() { super.onCreate(); service = new IStarWarsImplementation(); }

EmpireService@Overridepublic IBinder onBind(Intent intent) { return service;}

@Overridepublic boolean onUnbind(Intent intent) { return super.onUnbind(intent);}

@Overridepublic void onDestroy() { this.service = null; super.onDestroy();}

DarthVaderActivitypublic class DarthVaderActivity extends Activity implements ServiceConnection {

@Overrideprotected void onResume() { super.onResume(); final boolean isServiceBounded = super.bindService(new Intent(this, EmpireService.class), this, BIND_AUTO_CREATE); if (!isServiceBounded) { Log.w(TAG, "Failed to bind to service"); }}

… more methods

DarthVaderActivity@Overridepublic void onServiceConnected(ComponentName name, IBinder

service) { this.service = IStarWars.Stub.asInterface(service);}

@Overridepublic void onServiceDisconnected(ComponentName name) { this.service = null;}

DarthVaderActivity@Overridepublic void onClick(View v) { try { switch (v.getId()) { case R.id.iv_dva_build_death_star:

DeathStar deathStar = new DeathStar();final String reply = service.buildDeathStar(deathStar);String buildDeathStar = String .format("%s and it have %s", reply, deathStar.getBFG());Toast.makeText(this, buildDeathStar, Toast.LENGTH_LONG).show();break;

} } catch (RemoteException e) { e.printStackTrace(); }}

ASync Call - Find a luke

.AIDL interfaceinterface IStarWars { String buildDeathStar(out DeathStar deathStar); oneway void findLuke(in IEmpireServiceResponseListener listener);}

interface IEmpireServiceResponseListener { void onResponse(String response);}

IStarWarsImplementation...other AIDL interface implementation methods

@Overridepublic void findLuke(IEmpireServiceResponseListener listener) throws RemoteException {

..Doing very hard job through couple episodes…

listener.onResponse("I'm your father, Luke!");}

DarthVaderActivityIEmpireServiceResponseListener.Stub mListener = new IEmpireServiceResponseListener.Stub() { @Override public void onResponse(final String response) throws RemoteException { //Other process. We should run on UI Thread in order to interact with UI mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(DarthVaderActivity.this, response, Toast.LENGTH_LONG) .show(); } }); }};

DarthVaderActivity@Overridepublic void onClick(View v) { try { switch (v.getId()) { case R.id.iv_dva_interact_with_luke: service.findLuke(mListener); break; } } catch (RemoteException e) { e.printStackTrace(); }}

Binder Overhead:0 ms

What did we get?

Services running on separate processEasy expandable communicationGreat performanceFun :)

levinyon@gmail.comfacebook.com/levin.yonatan