Thursday, June 17, 2010

Plugins

I blogged 2 years ago that Android is a surprisingly modern application platform, I even said that it was the most modern application platform. This is partly due to Android's advanced component features. These features are not always evident to the naked eye so I present here a standard exercise to demonstrate them.

Click here to download the example program.

In this exercise we will implement a plugin framework for an example program. Plugins are components that connect to the main application over a uniform interface and are dynamically deployable. The download package contains two Android applications. The first, pluginapp is the example application that will use the plugins. The second, plugin1 is a plugin package that contains two plugins for pluginapp. Plugin1 package is not visible among the executable applications, it does not expose any activity that may be launched by the end user. If deployed, however, its plugins appear in pluginapp. Plugin deployment/undeployment is dynamic, if you uninstall plugin1 package while pluginapp is running, pluginapp notices the changes and the plugins disappear from the plugin list.




In order to implement these functionalities, the following features of the Android framework were used.


  • Each plugin is a service. Plugin1 exposes two of them. In order to identify our plugins, I defined a specific Intent (aexp.intent.action.PICK_PLUGIN) and I attached an intent filter listening to this intent in plugin1's AndroidManifest.xml. In order to select a particular plugin, I abused the category field again. One plugin can be accessed therefore by binding a service with an intent whose action is aexp.intent.action.PICK_PLUGIN and whose category equals to the category listed in AndroidManifest.xml of the package that exposes the plugin service.
  • Plugins are discovered using the Android PackageManager. Pluginapp asks the PackageManager to return the list of plugins that are bound to aexp.intent.action.PICK_PLUGIN action. Pluginapp then retrieves the category from this list and can produce an intent that is able to bindthe selected plugin.
  • Pluginapp updates the plugin list dynamically by listening to ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED intents. Whenever any of these intents are detected, it refreshes the plugin list.
Even though Android is not as sophisticated as OSGi when it comes to component framework, it has every necessary element. It has a searchable service registry, packages can be queried for the services they expose and there are deployment events.

30 comments:

K. said...

I've been looking for an example of this for months ... finally found one :)

Thanks a lot!!!!

Great work, keep it going ;)

Greetingz,
Koen<

Justin said...

I've been thinking of doing a similar thing with my Android application. The one major hurdle to overcome before I commit is figuring out how to debug the plug-ins as they run. Have you figured this out?

Also, do you know if the plug-ins will have access to the same SharedPreferences and run in the same VM as the core piece?

Gabor Paller said...

Justin, I would use just the Android log facility to debug the plugins.

These plugins exist in a different APK therefore you can't use SharedPreferences.

Justin said...

Gabor,

Thanks for the reply. After doing some looking I found that you can debug the plugins by going to the DDMS tab in Eclipse and selecting the Debug Process button. Apparently you can debug more than one process at the same time. Just thought I would share for anyone looking into it.

Ronak said...

Nice idea. It would be great if the plugin can display UI as well. The other thing is that I need to install the plugin apk. People may not like to install too many plugins so is there any way to achieve this without even installing apk?

Gabor Paller said...

Ronak, please look at this post.

kNedl said...

Hi.

I tried to implement your example to my code, but I'm getting errors. I just want to load some external functions.

For example:
My plugin1 has function:
public String startMeasuring(){return "startFirst";}
My plugin2 function:
public String startMeasuring(){return "startFirst";}

When I load my main application, the application has stored wich plugin first to use and then runs plugins function startMeasuring().

Do I have to wait some time to run the function?

Thank you.

Gabor Paller said...

kNedl, what is the error that you are getting?

kNedl said...

@gabor: I think i have some issues with permissions. Can somebody define logic behind this.

Actually when I added aexp.plugin.IBinaryOp.aidl and in my plugin used the same package structure it worked. I think I'm not seeing something in authors code...

But if I want to add another application with another plugin it just removes the first plugin and installs new one because of the same package name in manifest. But if I change package name i get permission error:

java.lang.SecurityException: Not allowed to bind to service Intent { act=aexp.intent.action.PICK_PLUGIN cat=[aexp.intent.category.UMO123] }
10-28 14:28:45.010: ERROR/plugin(18961): at android.app.ContextImpl.bindService(ContextImpl.java:1098)
10-28 14:28:45.010: ERROR/plugin(18961): at android.content.ContextWrapper.bindService(ContextWrapper.java:370)
10-28 14:28:45.010: ERROR/plugin(18961): at si.pd.RouteMonitoringActivity.bindOpService(RouteMonitoringActivity.java:293)
10-28 14:28:45.010: ERROR/plugin(18961): at si.pd.RouteMonitoringActivity.access$40(RouteMonitoringActivity.java:283)
10-28 14:28:45.010: ERROR/plugin(18961): at si.pd.RouteMonitoringActivity$13.onClick(RouteMonitoringActivity.java:1526)
10-28 14:28:45.010: ERROR/plugin(18961): at com.android.internal.app.AlertController$AlertParams$3.onItemClick(AlertController.java:934)
10-28 14:28:45.010: ERROR/plugin(18961): at android.widget.AdapterView.performItemClick(AdapterView.java:282)
10-28 14:28:45.010: ERROR/plugin(18961): at android.widget.AbsListView.performItemClick(AbsListView.java:1144)
10-28 14:28:45.010: ERROR/plugin(18961): at android.widget.AbsListView$PerformClick.run(AbsListView.java:2639)
10-28 14:28:45.010: ERROR/plugin(18961): at android.widget.AbsListView$1.run(AbsListView.java:3432)
10-28 14:28:45.010: ERROR/plugin(18961): at android.os.Handler.handleCallback(Handler.java:587)
10-28 14:28:45.010: ERROR/plugin(18961): at android.os.Handler.dispatchMessage(Handler.java:92)
10-28 14:28:45.010: ERROR/plugin(18961): at android.os.Looper.loop(Looper.java:132)
10-28 14:28:45.010: ERROR/plugin(18961): at android.app.ActivityThread.main(ActivityThread.java:4028)
10-28 14:28:45.010: ERROR/plugin(18961): at java.lang.reflect.Method.invokeNative(Native Method)
10-28 14:28:45.010: ERROR/plugin(18961): at java.lang.reflect.Method.invoke(Method.java:491)
10-28 14:28:45.010: ERROR/plugin(18961): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
10-28 14:28:45.010: ERROR/plugin(18961): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
10-28 14:28:45.010: ERROR/plugin(18961): at dalvik.system.NativeStart.main(Native Method)


Thank you,
Toni

Gabor Paller said...

Toni, have you exported the service? (android:exported="true" in the service tag)

O.Shevchenko said...

It works for me only after i have commented out 'new' for 'services' in the fillPluginList
//services = new ArrayList>();

and moved the 'new' to 'services'declaration.
private ArrayList> services = new ArrayList>();

Nathaniel Ryckman said...

Any ideas about how to download and install Plugin1 from a user's phone?

Would the .apk file just have to be stored outside of the Android Market?

Thanks for your input.

Gabor Paller said...

Nathaniel, the most evident solution is to download the plugin from Android Market - that opens a lot of possibilities provided by the Market, including paid plugins.

If this is not suitable for you for whatever reason, you can download and install the APK package programmatically.

Nathaniel Ryckman said...

Thanks Gabor :-). Yeah, option number two will probably be the way to go for me.

I don't think my plugins have enough functionality as stand alone apps to upload them to the market, and I would prefer to avoid bundling them together in order to keep the flexibility I need.

Great tutorial by the way :-)! Your blog examples seems to be the archetype of Android plugin development.

nda888 said...

How can I make IBinaryOp.aidl & IBinaryOp.java

Gabor Paller said...

nda888, I don't understand. The aidl tool should compile the .aidl file into the .java file. That tool is automatically invoked by the makefile (or by Eclipse).

nda888 said...

Hi Gabor Paller!

I'm writing an application with sound plugin + animation plugin following your introduction.

But I have a problem, in my IBinaryOp.aidl I defined:

interface IBinaryOp {
void op(in Context context);
}

But it doesn't understand type "Context"

How can I do it understand.

Gabor Paller said...

nda888, the aidl syntax allows just a limited set of types to be passed over the IPC mechanism. Here is an introduction. You can extend that limited set by making your objects Parcelable but IMHO neither Context, nor any of its popular child classes (e.g. Activity) are parcelable. Why do you want to pass a context? It will be useless in the context of another application process.

nda888 said...

Hi Gabor!
Thank you for your answering.

And this is my project.But I don't know why it doesn't have sound + animation effect.

I thought a long time, but I can't find the reason.

http://www.mediafire.com/?g7xyp9v087u5chk

nda888 said...

Hi Gabor!
I sent my project , but I can't find the reason why it doesn't have sound + animation effect. Can you help me, Gabor?

Cricket Live Streaming said...

Very useful info for newbie android developers. Thank you very much.

-----------

Dhanaiah

Best of Android | Android News | Best Android Apps | Android Games | Android Devices

ada said...

Hi,
Could you give me more information about how to develop a application with plugin? I am new in android,I try to make my application extendable.How can I connect you?

Thanks a lot.

Stan said...

It's a bit unclear how plugins get discriminated by category. I could understand the logic if you call intent's addCategory in the host's onListItemClick, passing a category string from the prebuilt arraylist. But you just putExtra with the category name, and plugins do not check it anyhow. Does the system do it behind the scene somehow? The only reference to the categories reside in the plugin's manifest, so I can't grasp how it works without intent.addCategory in the host. I'd really appreciate if you clarify the matter.

Gabor Paller said...

Stan, please observe fillPluginList() method in pluginapp/PluginApp.java. This method explicitly looks for packages that listen to ACTION_PICK_PLUGIN action and uses the category to distinguish among them. This is just a marking that Android offers and we use to discover packages that satisfy certain criterias.

Gabor Paller said...

ada, I have my linkedin page's link in the upper right corner.

Anonymous said...

Thank you for the answer, Gabor, but it looks like my question was not quite clear to you, my apologies. The question is not about selecting packages from globally available ones, but about the way how we invoke a specific plugin from the list in onListItemClick. How a plugin (identified by a category) can be invoked via an intent without this plugin's category being passed via intent.addCategory? I see you add the category into the intent's extra by putExtra, but I'm not sure if this is equivalent to addCategory. Could you please clarify this?

Gabor Paller said...

Anonymous, you are referring to these lines in PluginApp.java:

Intent intent = new Intent();
intent.setClassName(
"aexp.pluginapp",
"aexp.pluginapp.InvokeOp" );
intent.putExtra( BUNDLE_EXTRAS_CATEGORY, category );
startActivity( intent );

Here we just start the InvokeOp activity that presents two text fields and invokes the selected service plugin with the values of those text fields when the invoke operation button is pressed. Hence the category (that really identifies the plugin) is just passed as a parameter to the InvokeOp activity. In InvokeOp, however, the category is correctly used to select the plugin service:
opServiceConnection = new OpServiceConnection();
Intent i = new Intent( PluginApp.ACTION_PICK_PLUGIN );
i.addCategory( category );
bindService( i, opServiceConnection, Context.BIND_AUTO_CREATE);

Anonymous said...

Which plug in I need to install on my Android Tablet to view Stand Alone DVR cameras.
I can connect to DVR but can not open the files and see the pictures.

Thank you very much

Nguyễn Văn Quốc said...

How to write language plugin in android? THanks

Anonymous said...

I can't download the code. Server under maintenance. Please help. Thanks.