2012年9月22日土曜日

Allowing Other Apps to Start Your Activity

The previous two lessons focused on one side of the story: starting another app's activity from your app. But if your app can perform an action that might be useful to another app, your app should be prepared to respond to action requests from other apps. For instance, if you build a social app that can share messages or photos with the user's friends, it's in your best interest to support the ACTION_SEND intent so users can initiate a "share" action from another app and launch your app to perform the action.

To allow other apps to start your activity, you need to add an<intent-filter> element in your manifest file for the corresponding <activity> element.

When your app is installed on a device, the system identifies your intent filters and adds the information to an internal catalog of intents supported by all installed apps. When an app calls startActivity() orstartActivityForResult(), with an implicit intent, the system finds which activity (or activities) can respond to the intent.

Add an Intent Filter


In order to properly define which intents your activity can handle, each intent filter you add should be as specific as possible in terms of the type of action and data the activity accepts.

The system may send a given Intent to an activity if that activity has an intent filter fulfills the following criteria of the Intent object:

Action
A string naming the action to perform. Usually one of the platform-defined values such as ACTION_SEND orACTION_VIEW.

Specify this in your intent filter with the <action> element. The value you specify in this element must be the full string name for the action, instead of the API constant (see the examples below).

Data
A description of the data associated with the intent.

Specify this in your intent filter with the <data> element. Using one or more attributes in this element, you can specify just the MIME type, just a URI prefix, just a URI scheme, or a combination of these and others that indicate the data type accepted.

Note: If you don't need to declare specifics about the data Uri (such as when your activity handles to other kind of "extra" data, instead of a URI), you should specify only the android:mimeType attribute to declare the type of data your activity handles, such as text/plain or image/jpeg.

Category
Provides an additional way to characterize the activity handling the intent, usually related to the user gesture or location from which it's started. There are several different categories supported by the system, but most are rarely used. However, all implicit intents are defined with CATEGORY_DEFAULT by default.

Specify this in your intent filter with the <category> element.

In your intent filter, you can declare which criteria your activity accepts by declaring each of them with corresponding XML elements nested in the <intent-filter> element.

For example, here's an activity with an intent filter that handles the ACTION_SEND intent when the data type is either text or an image:

<activity android:name="ShareActivity">      <intent-filter>          <action android:name="android.intent.action.SEND"/>          <category android:name="android.intent.category.DEFAULT"/>          <data android:mimeType="text/plain"/>          <data android:mimeType="image/*"/>      </intent-filter>  </activity>

Each incoming intent specifies only one action and one data type, but it's OK to declare multiple instances of the<action>, <category>, and <data> elements in each <intent-filter>.

If any two pairs of action and data are mutually exclusive in their behaviors, you should create separate intent filters to specify which actions are acceptable when paired with which data types.

For example, suppose your activity handles both text and images for both the ACTION_SEND andACTION_SENDTO intents. In this case, you must define two separate intent filters for the two actions because aACTION_SENDTO intent must use the data Uri to specify the recipient's address using the send or sendto URI scheme. For example:

<activity android:name="ShareActivity">      <!-- filter for sending text; accepts SENDTO action with sms URI schemes -->      <intent-filter>          <action android:name="android.intent.action.SENDTO"/>          <category android:name="android.intent.category.DEFAULT"/>          <data android:scheme="sms" />          <data android:scheme="smsto" />      </intent-filter>      <!-- filter for sending text or images; accepts SEND action and text or image data -->      <intent-filter>          <action android:name="android.intent.action.SEND"/>          <category android:name="android.intent.category.DEFAULT"/>          <data android:mimeType="image/*"/>          <data android:mimeType="text/plain"/>      </intent-filter>  </activity>

Note: In order to receive implicit intents, you must include the CATEGORY_DEFAULT category in the intent filter. The methods startActivity() and startActivityForResult() treat all intents as if they contained theCATEGORY_DEFAULT category. If you do not declare it, no implicit intents will resolve to your activity.

For more information about sending and receiving ACTION_SEND intents that perform social sharing behaviors, see the lesson about Receiving Content from Other Apps.

Handle the Intent in Your Activity


In order to decide what action to take in your activity, you can read the Intent that was used to start it.

As your activity starts, call getIntent() to retrieve the Intent that started the activity. You can do so at any time during the lifecycle of the activity, but you should generally do so during early callbacks such asonCreate() or onStart().

For example:

@Override  protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);        setContentView(R.layout.main);        // Get the intent that started this activity      Intent intent = getIntent();      Uri data = intent.getData();        // Figure out what to do based on the intent type      if (intent.getType().indexOf("image/") != -1) {          // Handle intents with image data ...      } else if (intent.getType().equals("text/plain")) {          // Handle intents with text ...      }  }

Return a Result


If you want to return a result to the activity that invoked yours, simply call setResult() to specify the result code and result Intent. When your operation is done and the user should return to the original activity, callfinish() to close (and destroy) your activity. For example:

// Create intent to deliver some kind of result data  Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");  setResult(Activity.RESULT_OK, result);  finish();

You must always specify a result code with the result. Generally, it's either RESULT_OK or RESULT_CANCELED. You can then provide additional data with an Intent, as necessary.

Note: The result is set to RESULT_CANCELED by default. So, if the user presses the Back button before completing the action and before you set the result, the original activity receives the "canceled" result.

If you simply need to return an integer that indicates one of several result options, you can set the result code to any value higher than 0. If you use the result code to deliver an integer and you have no need to include theIntent, you can call setResult() and pass only a result code. For example:

setResult(RESULT_COLOR_RED);  finish();

In this case, there might be only a handful of possible results, so the result code is a locally defined integer (greater than 0). This works well when you're returning a result to an activity in your own app, because the activity that receives the result can reference the public constant to determine the value of the result code.

Note: There's no need to check whether your activity was started with startActivity() orstartActivityForResult(). Simply call setResult() if the intent that started your activity might expect a result. If the originating activity had called startActivityForResult(), then the system delivers it the result you supply to setResult(); otherwise, the result is ignored.

instruction sets supports By NDK

The latest release of the NDK supports the following instruction sets:

  • ARMv5TE, including Thumb-1 instructions (see docs/CPU-ARCH-ABIS.html for more information)
  • ARMv7-A, including Thumb-2 and VFPv3-D16 instructions, with optional support for NEON/VFPv3-D32 instructions (see docs/CPU-ARM-NEON.html for more information)
  • x86 instructions (see docs/CPU-X86.html for more information)
  • MIPS instructions (see docs/CPU-MIPS.html for more information)

ARMv5TE machine code will run on all ARM-based Android devices. ARMv7-A will run only on devices such as the Verizon Droid or Google Nexus One that have a compatible CPU. The main difference between the two instruction sets is that ARMv7-A supports hardware FPU, Thumb-2, and NEON instructions. You can target either or both of the instruction sets — ARMv5TE is the default, but switching to ARMv7-A is as easy as adding a single line to the application'sApplication.mk file, without needing to change anything else in the file. You can also build for both architectures at the same time and have everything stored in the final .apk. Complete information is provided in the CPU-ARCH-ABIS.HTML in the NDK package.

Using the NDK

The Android framework provides two ways to use native code:

  • Write your application using the Android framework and use JNI to access the APIs provided by the Android NDK. This technique allows you to take advantage of the convenience of the Android framework, but still allows you to write native code when necessary. If you use this approach, your application must target specific, minimum Android platform levels, see Android platform compatibility for more information.
  • Write a native activity, which allows you to implement the lifecycle callbacks in native code. The Android SDK provides the NativeActivity class, which is a convenience class that notifies your native code of any activity lifecycle callbacks (onCreate(), onPause(), onResume(), etc). You can implement the callbacks in your native code to handle these events when they occur. Applications that use native activities must be run on Android 2.3 (API Level 9) or later.

    You cannot access features such as Services and Content Providers natively, so if you want to use them or any other framework API, you can still write JNI code to do so.

Getting Started with the NDK

Once you've installed the NDK successfully, take a few minutes to read the documentation included in the NDK. You can find the documentation in the <ndk>/docs/ directory. In particular, please read the OVERVIEW.HTML document completely, so that you understand the intent of the NDK and how to use it.

If you used a previous version of the NDK, take a moment to review the list of NDK changes in the CHANGES.HTML document.

Here's the general outline of how you work with the NDK tools:

  1. Place your native sources under <project>/jni/...
  2. Create <project>/jni/Android.mk to describe your native sources to the NDK build system
  3. Optional: Create <project>/jni/Application.mk.
  4. Build your native code by running the 'ndk-build' script from your project's directory. It is located in the top-level NDK directory:
    cd <project>  <ndk>/ndk-build  

    The build tools copy the stripped, shared libraries needed by your application to the proper location in the application's project directory.

  5. Finally, compile your application using the SDK tools in the usual way. The SDK build tools will package the shared libraries in the application's deployable .apk file.

2012年9月21日金曜日

JNI Examples for Android

[*]Java interface

We start by defining a Java class JNIExampleInterface, which will provide the interface to calling the native functions, defined in a native (C++) library. The native functions corresponding to Java functions will need to have matching call signatures (i.e. the count and types of the arguments, as well as return type). The easiest way to get the correct function signatures in the native library is to first write down their Java prototypes, and then use the javah tool to generate the native JNI header with native function prototypes. These can be cut and pasted into the C++ file for implementation.

The Java functions which are backed by the corresponding native functions are declared in a usual way, adding a native qualifier. We also want to demonstrate how we could do the callbacks, i.e. calling the Java code from native code. That leads to the following high-level view of our interface class:

<JNIExampleInterface.java>=  package org.wooyd.android.JNIExample;    import android.os.Handler;  import android.os.Bundle;  import android.os.Message;  import org.wooyd.android.JNIExample.Data;    public class JNIExampleInterface {      static Handler h;      <Example constructors>      <Example native functions>      <Example callback>  }  

One valid question about this definition is why we need a Handler class attribute. It turns out that it will come in handy in situations, when the native library wants to pass some information to the Java process through a callback. If the callback will be called by a native thread (for extended discussion see "Calling Java functions" section [->]), and then will try to modify the application's user interface (UI) in any way, an exception will be thrown, as Android only allows the thread which created the UI (the UI thread) to modify it. To overcome this problem we are going to use the message-passing interface provided by Handler to dispatch the data received by a callback to the UI thread, and allow it to do the UI modifications. In order for this to work, we are going to accept a Handler instance as an argument for non-trivial constructor (reasons for keeping trivial one will become apparent later), and save it in a class attribute, and that's pretty much the only task for the constructor:

<Example constructors>= (<-U)      public JNIExampleInterface() {}      public JNIExampleInterface(Handler h) {          this.h = h;      }  

To illustrate various argument-passing techniques, we define three native functions:

  • callVoid(): takes no arguments and returns nothing;
  • getNewData(): takes two arguments and constructs a new class instance using them;
  • getDataString(): extracts a value from an object, which is passed as an argument.

<Example native functions>= (<-U)      public static native void callVoid();      public static native Data getNewData(int i, String s);      public static native String getDataString(Data d);  

The callback will receive a string as an argument, and dispatch it to the Handler instance recorded in the constructor, after wrapping it in a Bundle:

<Example callback>= (<-U)      public static void callBack(String s) {          Bundle b = new Bundle();          b.putString("callback_string", s);          Message m = Message.obtain();          m.setData(b);          m.setTarget(h);          m.sendToTarget();      }  

We also need a definition of a dummy Data class, used purely for illustrative purposes:

<Data.java>=  package org.wooyd.android.JNIExample;    public class Data {      public int i;      public String s;      public Data() {}      public Data(int i, String s) {          this.i = i;          this.s = s;      }  }  

After the source files Data.java and JNIExampleInterface.java are compiled, we can generate the JNI header file, containing the prototypes of the native functions, corresponding to their Java counterparts:

$ javac -classpath /path/to/sdk/android.jar \          org/wooyd/android/JNIExample/*.java  $ javah -classpath . org.wooyd.android.JNIExample.JNIExampleInterface  

Native library implementation

At a high level, the Java library (consisting, in this case, of a single source file JNIExample.cpp) will look like that:

<JNIExample.cpp>=  <JNI includes>  <Miscellaneous includes>  <Global variables>  #ifdef __cplusplus  extern "C" {  

AndroidでPDFを作成してみる

実現方法としては・・・

  1. 既存ライブラリを活用する
  2. 自前でスクリプトを書く
  3. PDFを生成してくれるWebサービスを利用する
  4. アプリからインテントを使い生成する

僕自身、全て試したわけではありませんが、手っ取り早いのは(1)既存のライブラリを活用する事だと思います。実装が一筋縄ではありませんが・・・


(2)に関しては、PDFの仕様を理解していないとイバラの道になると思います。

でも仕様を理解することは勉強にもなるし、時間に余裕のある人はこちらをオススメします。(日本語訳の公式リファレンスが5、6千円で売ってます。英語のリファレンスはWebで無料でダウンロードできます)

(3)に関しては2chで仕入れた情報ですが、面倒臭そうなので調べていません…

(4)調べたんですが、今のところ無さそうです。。。(もしあれば教えてください)


既存のライブラリを利用してみる

これより先はライブラリの利用方法について説明しますが、フリーのライブラリを利用する際、

いくつか注意する事があります。

  1. ライブラリが利用しているjavaパッケージ
  2. ライブラリが利用しているJDKのバージョン(JDK6を使ってたらアウト)
  3. 日本語等の出力
  4. ライセンス

これらすべての条件を満たすライブラリとなると、かなり数が限られてきそうです。

特にライセンスは重要です。調べれば分かることですが、GPL関連のライセンスだと利用するのが難しいでしょう。

うろ覚えですが、「GPLライセンスライブラリを利用する際の条約の一つとして、アプリに組み込まれたライブラリが改変された場合はソースコードを開示しなければいけない。Androidではこの条約を満たすことが出来ないから、結局ソースコードを開示する必要がある」どこかのブログに書いてあった気がします。。。回避策としては、PDFに関連する処理だけを抜き出し、別アプリで公開後、インテント経由で利用するということが考えられます。でもユーザーにアプリインストール促す必要もあり、いろいろ面倒ですね。。。


なんとなく使えそうなライブラリ

java PDF ライブラリ」でググルと結構ヒットしますが、どれもコレも一筋縄ではいきません。

java用のPDFライブラリ
ライブラリ packageのサポート JDKバージョン 日本語出力 ライセンス
iText × ×
iText for Android ×
PDF Box ×
Android PDF Write ×


iText

iTextはドキュメントも豊富で、ブログ等で使い方も紹介されているぐらいメジャー?なライブラリのようですが、

awt等のAndroidではサポートされないパッケージを使ってます。

実際にiTextを実装する際、この事に気づくまで1週間ぐらいハマってしまいました。

しかもGPLの派生ライセンスを採用しています。


iText for Android

iTextAndroidで使えるようにしてるらしいですが、どうやらまだ未完成のようです。

実際試しましたが、結局ビルドが通りませんでした。


PDF Box

こちらは試してませんが、やっぱりパッケージ関連の問題で無理なようです。

http://www.kaede-software.com/2011/04/pdfbox.html


Android PDF Write

恐らくつい最近に公開されたAndroid用のライブラリですが、外国の方が作られたライブラリなので日本語等の出力が無理なようです。英語のドキュメントのみ生成するのであれば、十分利用出来ます。


色々なライブラリを調べた結果、「androidで利用出来るjavaPDFライブラリ」は今のところ無さそうです・・・


NDKを利用する

それからかなり悩んだのですが、javaが無理ならCのライブラリはどうだ?と色々探してみたところ、

libHaru free PDF library」を見つけました。

ドキュメント等は全て英語のなのですが、製作者は日本の方なので、日本語の出力も可能です。

ライセンスは zlib/libpngライセンスなので、商用・非商用を問わずアプリに組み込めそうです。

ダウンロード・ドキュメントはこちら

http://libharu.org/wiki/Main_Page


これよりNDKの環境構築とビルドに関して説明していきます。環境を整えるのが結構面倒ですが、それに見合った価値はあると思います。

(とりあえず動きを確認したい場合は、ページ最下部にプロジェクトをダウンロードできるURLを貼りつけています)


NDKの開発環境を整える

環境構築に関しては、分かりやすく解説されているサイトが多数存在しますので、頑張って調べてください。

僕はこちらのサイトを参考にさせていただきました。

http://www.usefullcode.net/2010/12/android_sdk_inst05.html

http://d.hatena.ne.jp/bs-android/20090324/1237864333


ネィティブモジュールを呼び出す準備

クラスファイルにネィティブを使うことを宣言します。

static {      System.loadLibrary("CreatePDF");  }  private native int createPDF(String fpath);  


exportボタンを押すと、SyncTaskが起動してPDFを作成するようにします。

@Override  public void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);      setContentView(R.layout.main);            Button b = (Button)this.findViewById(R.id.Button01);      b.setOnClickListener(new OnClickListener() {                    @Override          public void onClick(View v) {                            switch (v.getId()) {              case R.id.Button01:                                    export();                  break;              default:                  break;              }          }      });  }  

プログレスダイアログを表示して、ネイティブに処理を任せる。

private void export() {            final ProgressDialog mProgress =          new ProgressDialog(this);      mProgress.setProgressStyle(ProgressDialog.STYLE_SPINNER);      mProgress.setMessage(getResources().getString(R.string.wait));      mProgress.setCancelable(false);            Runnable successRun = new Runnable() {          public void run() {                            Toast.makeText(getApplicationContext(),                      getResources().getString(R.string.success),                      Toast.LENGTH_SHORT).show();              mProgress.dismiss();              setResult(RESULT_OK);          };      };      Runnable errorRun = new Runnable() {          public void run() {                            Toast.makeText(getApplicationContext(),                      getResources().getString(R.string.error),                      Toast.LENGTH_SHORT).show();              mProgress.dismiss();          };      };                  mProgress.show();      ExportSyncTask task = new ExportSyncTask(              this.getApplicationContext(),              successRun, errorRun);      task.execute();  }    private String getSavePath(String catStr) throws IOException {            String pathSd = new StringBuilder()          .append(Environment.getExternalStorageDirectory().getPath())          .append("/")          .append(this.getPackageName())          .toString();      File filePathToSaved = new File(pathSd);            if (!filePathToSaved.exists() && !filePathToSaved.mkdirs()) {          throw new IOException("FAILED_TO_CREATE_PATH_ON_SD");      }            return pathSd + "/" + catStr;  }    public int exportPDF () throws FileNotFoundException, IOException {            String savePath;      savePath = getSavePath("helloPDF.pdf");      createPDF(savePath);        return 0;  }    private class ExportSyncTask extends AsyncTask<Void, Void, Integer> {        private Handler mHandler;      private Handler mHandler2;      private Thread mSuccessT;      private Thread mErrorT;            public ExportSyncTask(Context context, final Runnable r1, final Runnable r2) {                    mHandler = new Handler();          mHandler2 = new Handler();                    mSuccessT = new Thread(new Runnable() {              public void run() {                  mHandler.post(r1);              }          });                    mErrorT = new Thread(new Runnable() {              public void run() {                  mHandler2.post(r2);              }          });      }            @Override      protected Integer doInBackground(Void... arg0) {                    try {              exportPDF();          } catch (FileNotFoundException e) {              Log.e("PDF_sampleActivity", e.toString());              return -1;          } catch (IOException e) {              Log.e("PDF_sampleActivity", e.toString());              return -1;          }                    return 0;      }            @Override      protected void onPostExecute(Integer result) {                    if (result == null || result < 0) {                            mErrorT.start();          } else {                            mSuccessT.start();          }      }  }  


マニフェストパーミッションを追加しておきます。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  


「CreatePDF」はこれから作成するライブラリの名前になります。

javad側の処理はこれで完了です。

ここで一旦ビルドしてください。

ソースコードダウンロードする

AndroidでlibHaruPDFを使うには、ソースコードコンパイルしてAndroid用の共有ライブラリを作成する必要があります。

libHaruPDFはlibpngライブラリーを使用しておりますので、libHaruPDFとlibpngのソースコードダウンロードしましょう。

libHaruPDF

http://libharu.org/wiki/Downloads

(Latest Versionをダウンロードして下さい)


libpng

http://www.libpng.org/pub/png/libpng.html

(バージョン1.2.46のsource codeよりダウンロードして下さい)


ではeclipseにフォルダを作って、ファイルを移動しましょう。

jniフォルダを作り、その中にlibharu,libpng,pdftestの3フォルダを作ります。

フォルダ構成は次のようになってると思います。(*.c,*.hのみ移動させます。その他のmakeとか必要ないです)

top    src    gen    assets    libs    res    jni    --libharu      --include        --hpdf_annotation.h        --hpdf_catalog.h        --hpdf_conf.h        --...      --hpdf_annotation.c      --hpdf_array.c      --hpdf_binary.c      --...    --libpng      --<*.hと*.cを全て>    --pdftest  

pdftestフォルダにCreatePDFとヘッダファイルを配置します。

こちらのブログを参考にヘッダファイルを作成します。(「JNIヘッダファイルの生成」の項を参照)

http://d.hatena.ne.jp/bs-android/20090324/1237864333

次にCreatePDF.cを作成してください。

下記URLのサンプルコードを試してみます。(コピペでは動きません)

http://libharu.org/wiki/Documentation/Examples

/*   * << Haru Free PDF Library 2.0.0 >> -- font_demo.c   *   * Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>   *   * Permission to use, copy, modify, distribute and sell this software   * and its documentation for any purpose is hereby granted without fee,   * provided that the above copyright notice appear in all copies and   * that both that copyright notice and this permission notice appear   * in supporting documentation.   * It is provided "as is" without express or implied warranty.   *   */    #include <sample_pdf_PDFsampleActivity.h>  #include <stdlib.h>  #include <stdio.h>  #include <string.h>  #include <setjmp.h>  #include "hpdf.h"  #include <android/log.h>    jmp_buf env;    static char* jstr2cp(JNIEnv *e, jstring str);  static void jstrfree(JNIEnv *e, jstring str, char *cp);  static void log_print(char *str);    #ifdef HPDF_DLL  void  __stdcall  #else  void