Date post: | 16-Apr-2017 |
Category: |
Documents |
Upload: | yonatan-levin |
View: | 213 times |
Download: | 4 times |
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 :)