0

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.

Community
  • 1
  • 1
Decoded
  • 1,057
  • 13
  • 17
  • Did you add the extension definition to the air xml file? com.freshplanet.AirFacebook – ilkinulas Feb 01 '13 at 14:31
  • Yes the definition is there. The Extension loads perfectly, its just the Service class couldn't be found by the Android Class loader, because the 'air.' prefix is added onto all the classes for the Extension specific to 'air'. See answer #2 below! – Decoded Feb 04 '13 at 17:51

3 Answers3

1

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.

Michael
  • 3,776
  • 1
  • 16
  • 27
  • You are almost exactly right!! I've figured out a few things (with regard to AIR on Android, read below :). – Decoded Feb 04 '13 at 17:42
0

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);
Decoded
  • 1,057
  • 13
  • 17
  • 1
    Yeah, just a few things though, I'd definitely suggest that you fully scope your native classes in your manifest though. Otherwise if you try to use this in a different application the application ID will change and your class won't be found correctly. Your service doesn't **have** to live in that same package, you just have to correctly scope it in the additions. – Michael Feb 08 '13 at 06:17
  • For non-air Services, that is totally correct. But if you try to run a service that is part of a NativeExtension for AIR, the fully scoped package name doesn't match the .air prefix, and the service still wont run. so if I have com.my.company.TestService, i will still get the error that 'air.com.my.company.TestService' cant be found. – Decoded Feb 08 '13 at 23:09
  • 1
    I've updated my answer with more details as you're solution is not the best in terms of reusability of your code. – Michael Feb 11 '13 at 05:23
  • 1
    Sorry but I disagree with these rules. Rule 1, app Id can be whatever you want. It is better if it show you com.my.company but it will run without it. Rule 2: Android add air. to your package installation but you don´t need to add to your service xml entry. What makes it work for me was including tags enabled=true and exported=true like this: – Delcasda Jan 17 '14 at 16:21
  • Thanks for the addition Delcasda, can you post your android manifest for clarification? – Decoded Feb 18 '15 at 19:38
0

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.

Sagi Mann
  • 2,967
  • 6
  • 39
  • 72