Reputation: 1135
I'm working on an AIR Native Extension that needs to start a Background Service.
I've tried different methods of starting the service.
Here is my AIR Android Manifest Section (from my -app.xml file)
//AIR android manifest section
<manifest android:installLocation="auto">
<application>
<activity android:name=".TestActivity">
<intent-filter>
<action android:name=".TestActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<service android:name=".TestService">
<intent-filter>
<action android:name="test.default.service"/>
</intent-filter>
</service>
</application>
</manifest>
The Service Class is in the same package as the Activity. The Activity runs just fine, and in the onStart() method of the Activity, i've tried starting the Service with the following Methods:
Method 1:
import android.app.Activity;
public class TestActivity extends Activity
{
@Override
protected void onStart()
{
super.onStart();
Intent intent = new Intent("test.default.service");
//clearly the classes are available because this compiles!
intent.setClass(TestActivity.this, TestService.class);
startService(intent);
}
}
Result:
Unable to start service Intent { act=test.default.service cmp=air.com.my.company/com.my.company.TestService } U=0: not found
Method 2:
import android.app.Activity;
public class TestActivity extends Activity
{
@Override
protected void onStart()
{
super.onStart();
//start a service with intent
startService(new Intent("test.default.service"));
}
}
Result:
FATAL EXCEPTION: main
java.lang.RuntimeExeption: Unable to instantiate service air.com.my.company.TestService:
java.lang.ClassNotFoundException:
Didn't find class "air.com.my.company.TestService" on path: /data/app/air.com.my.company-1.apk
The issue seems to be that "air." is being prefixed to the package name when attempting to start the service. How can I get around this mangling of the package name when running my own custom services?
I've been all over stack overflow, as well as Adobe's forums and can't seem to find a full solution to this problem.
There are a few other posts that touch on this topic, but none provide a real running solution with source.
I've cut this down to the simplest case I can to ensure there are no extra moving parts that are causing the issue.
Any official information on how an Android Service can be started from an Extension Context running in AIR would be helpful. It seems even Adobe is hush hush about this issue.
Upvotes: 0
Views: 3742
Reputation: 3610
For me, the only change was the context I pass to the native package. In my case, I have some init(context) method that my native code exposes, and I originally used it like so:
instance.init(context.getActivity());
This gave me the "Unable to find service" error. So I changed it to:
instance.init(context.getActivity().getApplicationContext());
And now it works.
Upvotes: 0
Reputation: 3871
I think you'll need to fully scope the service class in your manifest additions with the package name:
...
<service android:name="com.my.company.TestService">
Not sure it will work without that. I know normal Android prepends the class correctly, but AIR applications get the air. prefix which may cause your problem.
Update:
We actually do this with several of our native extensions, so I know it's correct.
Declaring a service with the fully scoped class name doesn't use the dot shorthand notation which is where you are getting the problem. Using the dot shorthand, Android will assume that the class is in your application package, which under AIR application is
air.com.my.company
The way you are doing it means your ANE won't work in another application with a different app ID!
Another AIR application will look by default for
air.com.other.company.TestService
So leaving the shorthand notation will mean this application won't find your service/activity:
air.com.my.company.TestService
The correct way is to use a clean package name in your native code, say com.company.nativepackage and then include this in the manifest as below:
<application>
<activity android:name="com.company.nativepackage.TestActivity">
<intent-filter>
<action android:name="TestActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<service android:enabled="true" android:exported="true" android:name="com.company.nativepackage.TestService">
<intent-filter>
<action android:name="air.com.my.company.DO_CUSTOM_ACTION"/>
</intent-filter>
</service>
</application>
This will work in any application and allow your ANE to be more portable. Hope that makes sense.
Upvotes: 1
Reputation: 1135
I've entered this question, and answered it, because it took me a significant amount of time to find out exactly what is required, and I'm hoping to save many other developers some serious headaches.
The android manifest shown above is perfectly fine. The code shown above (in both methods) is perfectly fine.
The problem lies with both the Signing Certificate and the package name for your AIR Application.
Rules for Adobe AIR Native Extensions
Rule 1.
The Code Signing Certificate package must match the application id in your app.xml
so if the certificate package is com.my.company
, then the air app id must be <id>com.my.company</id>
Rule 2.
When packaging a native extension for android using ADT, a prefix is added to your package name so it looks like air.com.my.company
.
When the Android ActivityManager tries to run your service it looks for it in air.com.my.company
. Thus, the Service class must live in the same package (including the 'air' prefix) within your java code. My TestService class now is defined as air.com.my.company.TestService
And with the following manifest, it runs just fine!
<application>
<activity android:name=".TestActivity">
<intent-filter>
<action android:name="TestActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<service android:enabled="true" android:exported="true" android:name=".TestService">
<intent-filter>
<action android:name="air.com.my.company.DO_CUSTOM_ACTION"/>
</intent-filter>
</service>
</application>
And...Heres how I invoke the service.
Intent intent = new Intent("air.com.my.company.DO_CUSTOM_ACTION");
intent.setClass(currentContext, TestService.class);
currentContext.startService(intent);
Upvotes: 0