Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Thursday, October 2, 2014

iOS and Android - Communication between JavaScript and Native

Many of us work on hybrid applications where the core part of the functionality is written in HTML / JavaScript, loaded on a WebView. And we write native code to perform things that JS cannot do, like accessing the Gallery, writing files to the system, creating a native database, transacting with it and much more. For the application to perform seemlessly we need a bridge to call JS methods from native code and Native methods from JavaScript. PhoneGap plugins work on the same principle. In this post, I will cover ways on both Android and iOS to communicate with JavaScript.

Let us first create an HTML file, which we will load on the UI Webview and will use to interact with native
<html>
  <head>
    <script type="text/javascript">
      function loadURLInIframe(url)
      {
        document.getElementById("myFrame").src = url
      }
      function sayHello()
      {
        nativeInterface.say("Hello");
      }
</script>
</head>
  <body style="width:100%; height:100%; margin:0px;">
    <iframe id="myFrame" src="about:blank" style="width:100%; height:400px;"></iframe>
    <a onclick="sayHello()" href="#">Say Hello to Native</a>
  </body>
</html>

In the HTML code above, we have one iFrame with a blank URL. We've defined a method in JS named "loadURLInIframe", which accepts one parameter "URL" that will change the URL of the iFrame. We'd call this method from the native code. There is another span, which calls the other JS method "sayHelloToNative", which we will use to call the native methods.

Now let us write the native code to communicate with the JavaScript in the HTML above. We will start with the easier one i.e. Android. Android provides a very easy mechanism to communicate with the JS loaded on the WebView. Let us first look at a way to invoke JS method from native Java.
webView.loadUrl("javascript:loadURLInIframe(' " + "http://techiepulkit.blogspot.in" + " ');");

What are we doing here? We asked webview to loadUrl("javascript:"), which means that we are asking webView to execute a JS method. The name of the function is followed by ":" and then the parameter in brackets. Note the single quote (') on both sides of the parameter. This is important to pass a string parameter. That's it. The JS method will get called and it will load the URL in the iFrame.

Now, coming to the second part, calling native method from JavaScript. For JavaScript to be able to call native methods, we need to attach a JavaScript interface to the Webview. Let us see, how.

First, create a new class that would contain the methods to be invoked from Native.
public class JSInterfaceManager
{
    @JavascriptInterface
    public String say(String message)
        {
            Log.d("MyLogs", "Message from JS: " + message);
        }
}
In the class above, we have created one method "say" which accepts one parameter as String. Notice the @JavascriptInterface declaration on top of the method. This declaration exposes the method to JavaScript. Let us now associated this class with the interface.
webView.addJavascriptInterface(new JSInterfaceManager(), "nativeInterface");

Now that the JSInterfaceManager has been associated with the Webview, a new Object with the name "nativeInterface" will be available to the JavaScript now. Now when you click on the span "Say hello to native" in the HTML loaded in Webview, it will call nativeInterface.say method, which will invoke the say method inside the JSInterfaceManager class. Now each time you click on that span, you will get a log saying "Message from JS: Hello" in the Logcat. Note that you can also return a value in the native method and JS method will be able to receive it. That is it! We have now covered the 2 way communication with JS on Android.

Let us now repeat the same steps for iOS

First we will see calling JS method from ObjectiveC. The syntax to call a JS method from ObjectiveC looks as follows:
NSString *jsScript = [NSString stringWithFormat:@"loadURLInIframe('%@')", @"http://techiepulkit.blogspot.in"];
[webView stringByEvaluatingJavaScriptFromString: jsScript];

Pretty much similar to how it worked on Android. The only notable difference is that we don't need to prefix "javascript:" in the script to invoke the JS Method. Once this code is executed, it will load the URL in the iFrame. Simple ain't it! Well the second part i.e. calling native methods from JS is not that simple. Let's see how that works.

Before iOS 7, there was no direct way of calling native methods from JavaScript, all we had were workarounds like changing the URL of the Webview and listening the change on the delegate method. This was both slow and tedious. With iOS7, there came JavaScriptCore. JavaScript core allows the developers to create a JSContext and use it for direct communication. There are multiple uses of JavaScriptCore but I'd cover only the communication with WebView. In principle, this works quite similar to the way JavaScriptInterface on Android does but the implementation in native is quite different.
We will first define a Protocol as follows:
@protocol MyJSExport <JSExport>
  - (NSString *)say:(NSString *)message;
@end
};
We have defined a protocol which implements JSExport interface. Implementing it makes all the methods and properties in the protocol visible to JS. Now we will create a class "MyNativeInterface" that implements this protocol and has the actual implementation of the method.
@interface NativeInterface : NSObject
-- Any method not visible to JS can be defined here.
@end

@implementation NativeInterface
  - (NSString *)say:(NSString *)message
{
    NSString *printMessage = [NSString stringWithFormat:@"Message from JS: %@", message];
    NSLog(printMessage);
}
@end
Now, we have the class implementation ready and we just need to hook it with the Webview's context.
//First get the JSContext from JS.
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//
MyNativeInterface *nativeInterface = [[NativeInterface alloc] init];
context["nativeInterface"] = nativeInterface

So, we created an instance of the class that exposes the methods to JS and set it as nativeInterface on Webview's JSContext. This would mean that the JS now has a variable named "nativeInterface" and it can call it's methods. So, now when the user clicks on the span, it would invoke native method, which will log the message sent by JS. And that's it, now we have covered 2 way communication between JS and ObjectiveC on iOS as well.

Before I conclude, I would like to share a few tips, which you should keep in mind while working with JavaScriptCore:
  • JSContext can be used to invoke JS methods also. Using [context evaluateJavascript], you can call JS methods but it is erratic in nature. The application crashes randomly when using this approach so one should continue using [webView stringByEvaluatingString]
  • We generally tend to set webview's URL to "about:blank" before destroying it to force it to release memory but when used with JavaScriptCore, it works the other way round. It doesn't release the instance of Webview at all and with each instantiation, it keeps accumulating memory.
  • Do not forget to set the context references to nil on destroy. A strong reference of these objects is created and is important to be destroyed. In this case, context["nativeInterface"] = nil should be called
  • When calling native methods from JS, always wrap the call in setTimeout of 0. This ensures that the calling is done in a thread safe manner
  • When calling methods with multiple arguments, the function name gets modified when exposed to iOS. So, a method -(void) doSomething (NSString *)param withOption:(NSString *), the methodName that gets exposed will be doSomethingWithOption(arg1, arg2) instead of doSomething.

The JavaScriptCore is still in nascent stages on iOS and has a lot of quirks, still it is the fastest and the most convenient way to communicate with JS. The best part is that we are able to reuse the same approach on iOS and Android. In this post, we have just scratched the surface of JavaScriptCore and the possibilities are immense.

Do pass on any feedback or questions that you may have here.

Sunday, September 21, 2014

SQLite Full Text Search on Mobile devices (FTS3)

When we talk about Full Text Search, the libraries that come to mind are Solr, ElasticSearch etc. However, all of these are back end libraries and require the indexes of the files to be created first.

When we talk about searching inside the mobile apps (without hitting the server), either these libraries do not have the client side implementations or are too heavy to be included in an app.

This is where Full Text Search engine of SQLite (FTS3) comes to rescue. FTS3 provides a lot of methods to perform full text search on the database using SQL statements. Although, both iOS and Android support SQLite out of the box, however, even using the same with libraries like SQLCipher is quite easy and doesn't require embedding huge libraries into the app either. I am not going to cover the differences between FTS3 and FTS4 as all that information is available on the link I share below.

All the methods provided by FTS3 with examples are listed here http://www.sqlite.org/fts3.html
. I recommend that you do keep referring to the link if anything is not clear as I will not cover the definition in detail but only the basics of FTS3 and share a code snippet for Android.

The FTS3 and FTS4 extension modules allows users to create special tables with a built-in full-text index, "FTS tables". The full-text index allows the user to efficiently query the database for all rows that contain one or more words also known as "tokens", even if the table contains many large documents.

To create a virtual table, the following statement can be used.
    CREATE VIRTUAL TABLE enrondata1 USING fts3(content TEXT);
This makes the table eligible for Full text query

When the WHERE clause of the SELECT statement contains a sub-clause of the form " MATCH ?", FTS is able to use the built-in full-text index to restrict the search to those documents that match the full-text query string specified as the right-hand operand of the MATCH clause.

The fast full text query looks as follows:     SELECT * FROM mail WHERE subject match 'database';

FTS3 and FTS4 provides three special auxiliary functions that are very useful to the developers:"snippet", "offsets" and "matchinfo". As the SQLite portal states: "The purpose of the "snippet" and "offsets" functions is to allow the user to identify the location of queried terms in the returned documents. The "matchinfo" function provides the user with metrics that may be useful for filtering or sorting query results according to relevance."

In this post, I am going to talk about the Offsets method only as that is the one I found to be most effective if you have to perform a full text search and fetch an excerpt. Although, the snippets function seems to be the one to fetch excerpts, it actually doesn't work as per its name.
The offsets() function returns a text value containing a series of space-separated integers. For each term in each phrase match of the current row, there are four integers in the returned list. Each set of four integers is interpreted as follows:
Integer Interpretation
0 - The column number that the term instance occurs in (0 for the leftmost column of the FTS table, 1 for the next leftmost, etc.).
1 - The term number of the matching term within the full-text query expression. Terms within a query expression are numbered starting from 0 in the order that they occur.
2 - The *byte offset* of the matching term within the column.
3 - The *size* of the matching term in bytes.
Important thing to note here is that the offset is a byte offset and not a character offset.
More details on the same can be read here: http://www.sqlite.org/fts3.html#section_4_1

Let us see an example of Offsets function being used in Android.

First we will create a new virtual table:
database.execSQL("CREATE VIRTUAL TABLE mail USING fts3(subject, body);");
ContentValues contentValues = new ContentValues();
contentValues.put("subject", "Subject");
contentValues.put("body", textContent);
database.insert("mail", null, contentValues);

Now that the table is ready and has one record, we will write the FTS query as follows:
Cursor myCursor = database.rawQuery("SELECT offsets(mail), body FROM mail WHERE mail MATCH 'Republic';", null);

Here along with the offsets, I am also fetching the full text so that I can extract excerpts from the same.
myCursor.moveToFirst(); // Move cursor to first location
String[] offsets = myCursor.getString(0).split(" "); // Split to " " to read integers
String text = myCursor.getString(1); //Store complete body in a variable
byte[] textBytes = text.getBytes(); // Convert text to bytes
ByteArrayInputStream ba = new ByteArrayInputStream(textBytes);
int i = 0;
ArrayList results = new ArrayList();
int textLength = textBytes.length;
ExcerptFinder excerptFinder = new ExcerptFinder(ba); // Provide the stream containing text bytes to ExcerptFinder
while (i < offsets.length)
{
  //Term and column index are ignored because we've searched for a single term only.
  int startOffset = Integer.parseInt(offsets[i + 2]);// Find the start index of searched term
  int endOffset = startOffset + Integer.parseInt(offsets[i + 3]);// Find the end index of searched term

if(startOffset < 0)
{
    startOffset = 0;
}

if(endOffset >= textLength - 1)
{
     endOffset = textLength - 1;
}
  String excerpt = excerptFinder.readFullWords(startOffset, endOffset);
  results.add(excerpt);
  i += 4;
}

So, here we extract excerpts for the searched term by first identifying its start index and end index in bytes. The same process continues for all the results for the row. In this example, we are working with a single row of data, otherwise there would be one more loop for the rows. Since we need to run forwards and backwards in the ByteArray Stream, the ExcerptFinder class I created extends the RandomAccessStream class provided at: RandomAccessStream.java

The relevant code in the Excerpt Finder class goes as follows:
public final String readFullWords(int start, int end) throws IOException
{
  int startOffset = start;
   int endOffset = end;
  byte[] singleByte = new byte[1];
  int currentCharacter = 0;
  int i = 0;
  int spaceCount = 0;
  while(currentCharacter != RETURN_DELIMITER && spaceCount < NUMBER_OF_WORDS)
  {
    startOffset = start - i;
    seek(startOffset);
    if(startOffset <= 0)
    {
      startOffset = -1;
      break;
    }
    read(singleByte, 0, 1);
    currentCharacter = singleByte[0];
    if(currentCharacter == 32)
    {
      spaceCount++;
    }
    i++;
  }
  currentCharacter = 0;
  i = 0;
  spaceCount = 0;
  int readBytes = 0;
  while(currentCharacter != RETURN_DELIMITER && spaceCount < NUMBER_OF_WORDS)
  {
    endOffset = end + i;
    seek(endOffset);
    readBytes = read(singleByte, 0, 1);
    if(readBytes < 0)
    { //
      endOffset = endOffset - 1;
      break;
    }
  currentCharacter = singleByte[0];
  if(currentCharacter == 32)
  {
    spaceCount++;
  }
  i++;
    }
  seek(startOffset + 1);
  byte[] result = new byte[endOffset - startOffset - 1];
  readFully(result, result.length);
  return new String(result);
}

In the code above, the rule for an excerpt is defined by a RETURN delimiter or NUMBER_OF_WORDS before and after the term. The logic can be tweaked to anything that you want. The key here is to play with the bytes offsets returned by the FTS3 query. Although, we are performing operations at a byte level, the code executes much faster then a RegEx performed on plain text. The search can be made faster by simply fetching a predefined number of bytes before and after the search term without worrying about the number of words.

FTS can be very handy when it comes to providing offline search to mobile apps. You can create the DB at the backend and simply download it on the app to run the FTS query on it. This concludes the post and I look forward to the comments.

If you need the source for the Android app to help you get started, feel free to email me.

Friday, February 7, 2014

Android: Overriding the default contextual action bar for text selection in WebView (Android 4.1+)

We had to create an Android app where user annotates using the text selected in an HTML page. We used the standard Webview component of Android to render the web page.

By default Android's Webview displays the standard ActionBar with Copy, Paste and other buttons when the text is selected. I wanted to either disable the Contextual Action Bar (CAB) or display a CAB with my own set of buttons. Android Webview doesn't provide ability to do either of these.

People had commented on various StackOverflow posts to either use LongPress listener or use registerContextMenu. If we setup the LongClick listener, the selection stops working and registerContextMenu doesn't work in the desired manner either.

After delving deep into the Webview code, I found out that WebViewClassic class was invoking startActionMode method of the Webview. This is all we needed. I created a new class by subclassing the Webview class and overrided the startActionMode method. After that, I just used the extended Webview instead of the default one and that was it.

The overridden method looks like this:

        public ActionMode startActionMode(ActionMode.Callback callback)
          {
                 actionModeCallback = new SelectActionModeCallback();
                return super.startActionMode(actionModeCallback);
          }



As you can note in the code above, instead of sending the ActionMode.Callback sent by Webview classic, I simply created a custom ActionMode.Callback with desired items and sent it to the subclass. This ensured that the selection worked perfectly fine and my custom CAB showed up in place of Android's default menu, each time text was selected. Works like a charm.

However, there is a small caveat in this solution. The CAB doesn't get closed by itself as the Android's default CAB and I had to write code to close it at appropriate places. Hope this helps someone.

Friday, January 25, 2013

Android: Invoking a URL Scheme Intent from a WebView

A few days back, I was working on a project where one of our PhoneGap Android apps needed to invoke another application using the custom URI scheme.

We were able to invoke the other app, when clicking on a link from the mobile browser, but the same did not work from our app.

We figured out that the issue was with the Webview as it was not able to recognize the custom URI.

Solution: After a lot of research, I was able to get to a fix.

The Android Webview component allows using a WebView client. The Webview client exposes a method and I just overrided it as follows: public boolean shouldOverrideUrlLoading(WebView view, String url)
{
                if(url.toLowerCase().startsWith("http") || url.toLowerCase().startsWith("https") || url.toLowerCase().startsWith("file"))
                {
                                view.loadUrl(url);
                }
                else
                {
                                try
                                {
                                                Uri uri = Uri.parse(url);
                                                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                                                                startActivity(intent);
                                }
                                catch (Exception e)
                                {
                                                Log.d("JSLogs", "Webview Error:" + e.getMessage());;
                                }
                }
    return (true);
}

That's it, once you configure the code as above, any URL scheme inside the HTML page would work like a charm. What I simply did was to tell the Webview that if the URL is HTTP, HTTPS or FILE, you handle it, otherwise fire an intent and let the OS handle the same. Simple, yet no one blog has this solution clearly articulated, hence this post.

AIR Android Native Extensions: Part 6: Preventing Android activity from being recreated on orientation change and relaunch.

In my previous post, I explained how to package XHDPI and XLarge resources with the AIR app. In this post, I would cover how to prevent Android activities from being recreated on orientation change or resumption of app.

In the process, we'd see how to compile an AIR android app with a different Android SDK.

First, let's understand what the issue is. There are a lot of times, where you need to create native android activities. For example, if you want to use the Android WebView component to launch the web pages in your app, you'd need to create a new activity for the same.

By default, Android recreates the activity on various configuration changes, such as SoftKeyBoard open or Orientation change. To prevent the recreation of activity, we used to add the android:configChanges property to activity's declaration in the manifest:

<activity android:name="com.webview.Webview"
android:configChanges="keyboardHidden|orientation" >
</activity>

As you can notice above, we have added an attribute android:configChanges and the values for it are keyboardHidden|orientation. This much was sufficient to prevent the activity from getting recreated in Android below 3.2. However, these values alone are unable to prevent the activity from being killed in Android 3.2 and above.

Problem: Android introduced a new configuration property "screenSize" in Android 3.2, hence you need to add this as well as in the android:configChanges attribute like this:

<activity android:name="com.webview.Webview"
android:configChanges="keyboardHidden|orientation|screenSize" >
</activity>

This would tell Android to not recreate the activity on screenSize changes. However, you won't be able to use this property with the default android SDK AIR uses as it would not recognize this property and throw an error.

Solution: Updating Android SDK. No, the platformsdk compiler argument, as explained in my previous post, would not help here.

So, we'd need to replace the Android SDK in the AIR SDK's library folder. The old Android SDK JAR is at the following location:
{AIR_SDK_FOLDER}\lib\android\lib\resources\android-res.jar


You'd need to copy the JAR file from the android SDK folder, rename it as "android-res.jar" and paste it at the location above, replacing the old one. The Android SDK JAR, for say, Android 4.0, would be present at "platforms/android-15/android.jar". You can download the SDK for a particular version from the android website.

Once the above is done, you'd be able to successfully package the application with screenSize attribute added to the activity declaration.

Unfortunately, even after doing this, the Android activity may get recreated on resume on certain devices. Fortunately, adding a few lines to your manifest can fix this, see below:

<android>
<colorDepth>16bit</colorDepth>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
<application android:enabled="true">
<activity android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity> </application>
</manifest>
]]>
</manifestAdditions>
</android>

As you'd notice above, I've added an activity node under the application node and assigned it a property android:launchMode="singleTop". This is what does the trick. The main thing is the launchMode, that we have set to singleTop. But, to set that we'd need to add the lines emboldened as well.

Well, this would ensure that your activity would never recreate unless you want it to. Hope this helps someone banging head against the wall, trying to figure out what to do. Like what I was doing sometime back, till I figured this out.

Do keep watching this space for my last article in this series: Launching AIR's main activity from other Android activities and passing information.

P Android Native Extensions Part 2: Accessing R file variables from native extensions

In the previous post, I covered how to bundle the resources with the ANE. In this post I would cover how to access them from the native code, when it runs with the native extension.

Let us understand the issue first. When creating Android native applications or libraries, we use several resources such as images, layout XMLs, ids, strings. At the time of compilation Android generates an R.java file which provides the native code access to these resources. Examples:

R.id.myLayout
R.layout.mywebviewlayout
R.strings.close_button

This works perfectly fine when you run the app natively. However, when we run this code as an extension from the AIR application, the app crashes. A look at the DDMS log would tell you that a null pointer exception occurred at the place you were accessing the R file.

So, what is happening here? Let me explain.

When you compile an Android application, Android runs through all the resources used by the app and assigns them a unique integer id. If you open the R.java file from the gen folder of your app, you'd find something like this:
public static final class drawable
{
    public static final int ic_action_search=0x7f020000;
    public static final int ic_launcher=0x7f020001;
}
public static final class id
{
    public static final int menu_settings=0x7f070001;
    public static final int openwebview=0x7f070000;
}
public static final class layout
{
    public static final int activity_main=0x7f030000;
    public static final int rla_activity_main=0x7f030001;
}

Now, when you exported this code as a JAR, everywhere in the code where the R class was used, the references were replaced by their values.

Take a look at the following code:

    setContentView(R.layout.rla_activity_main);

In your JAR, the code would have compiled like this:

    setContentView(2130903041);

So, R.layout.rla_activity_main has been replaced with it's int value 2130903041

Now, what does AIR do that we don't find this resource, even if it is bundled with the application? Let us see.

Your AIR application has it's own resources plus it would have resources from your native extension and any other native extensions that you use. So, when compiling the final APK, AIR generates it's own R file. The Android SDK would once again loop through all the resources collectively and assign unique integer ID to each of them. Let's have a look at the same values defined in AIR's R.java file:

public static final class drawable
{
    public static final int ic_action_search=2130968576;
    public static final int op_action_some=213137980;
    public static final int ic_launcher=20979292;
}
public static final class id
{
    public static final int menu_settings=2131296275;
    public static final int openwebview=2131296260;
}
public static final class layout
{
    public static final int activity_main=2130837582;
    public static final int rla_activity_main=2130837585;
    public static final int someother_activity_two=2476689229;
}

As you can see, the IDs assigned to each item have changed. And this is where the issue is. Your native code is :
  setContentView(2130903041);

So, it is looking for rla_activity_main with the id 2130903041, however, AIR has assigned it a different id i.e. 2130837585. So, when you run the extension within the AIR app, it tries to find for the resource with if 2130837585, which is not present, hence the null exception.

Solution: AIR to rescue. The FREContext object that we use in our extensions exposes a function named getResourceId("resource_name"). This function would always give you the correct IDs mapped with the AIR's R file.

So, instead of using:
    setContentView(R.layout.rla_activity_main);

use:
     setContentView(extensionContext.getResourceId("layout.rla_activity_main"));

You can follow the same approach for all the resources,

For colors, use extensionContext.getResourceId("color.colorName") For id, use extensionContext.getResourceId("id.my_resource_id")

Also, do note that the extensionContext object is the extension's FREContext and not Android application context. Although, this works without fail, but at times accessing resources via extensionContext can slow down the application majorly. I would cover ways to speed up access to Android resources through the extension contxt. Stay tuned.

Hope this would save someone a few hours of googling atleast. Thanks and do check out the next article in the series. AIR Android Native Extensions: Part 3: Speeding up access of resources in Native extensions

Thursday, January 24, 2013

AIR Android Native Extensions - Issues and Solutions

In the last few months, I have worked on a lot of native extensions for AIR applications, both Android and iOS. I faced a lot of issues and there was little help available on the web and even if it was available, it was all scattered and not easy to understand.

In this post, I would focus primarily on the issues that I faced when creating Android native extensions. The issues range from trying to bundle resources into a native extension to accessing resources and so on. Some of the issues would have been real show stoppers if they were not resolved. The idea is to share my experience to help others save time.

I assume that the readers are familiar with basics of native extensions and are versed with compiling ANEs.

Let me first list down the issues I am going to talk about in this series of blogs. Series, not to make a single post too lengthy.
  1. Exporting assets / resources with the extension
  2. Accessing R file variables from an extension
  3. Speeding up access of resources in Native extensions
  4. Using third party JARs in your native code
  5. Exporting xLarge assets with the APK
  6. Preventing Android activity from being recreated on orientation change and relaunch
  7. Launching AIR's main activity from other Android activities and passing information


Exporting assets / resources with the extension and common errors
When we create a native extension for Android which contains a view, we generally use layout XMLs to design the layout and some images for buttons and icons and these files are placed in the res folder. By default, you'd tend to export these files along with the JAR and create the ANE. However, the final APK doesn't have any of these assets and your application would crash

To bundle the files along with the APK, we do not need to export the res folder along with the JAR. However, to bundle it with the ANE, we need to copy it separately alongside the JAR and make some change in the way you compile the ANE.

I generally follow the folder structure as follows:
[build]
  extension.xml
  extension.swc
  compile.bat
  [platform]
    [android-arm]
      res
      library.swf
      extension.jar

As you'd notice above, I have created an android-arm folder which has the res folder. The res folder contains the resources for the native extensions. We can name the folder anything, but it's important to keep the three items i.e. res folder, library.swf and extension.jar.
Once we've prepared the folder structure, you can execute a command line script as follows to compile the extension:

adt -package -target ane extension.ane extension.xml -swc extension.swc -platform Android-ARM -C ./platform/Android-ARM/ .

Note: Please note the space and dot (" ." ). It is supposed to be like this only. You can add iPhone platform in the similar way. However, the XIBs in iPhone need to be compiled first.

Doing the above would ensure the resources are bundled with the app. However, please ensure that the you keep unique names for your resource files so that they don't conflict with resources from other extensions. This is essential, otherwise AIR SDK would give errors when compiling when the final app. for example, by default when you create an eclipse project, it creates a file "strings.xml" in res/values folder. Rename the file to "yourproject-strings.xml" or anything unique you can think of. Similarly, the variable names inside the resource files must also be unique. So, if extension 1 defines are resource with id "webview", extension 2 should not use the same name, else you won't be able to compile the final APK. I hope this turns out to be useful for some. Do check out Part 2:Accessing R file variables from an extension.