2012年6月29日金曜日

NDK & OpenCV

Android NDK をc¥にインストール
cgwin をインストール
(最近はcgwinはいらないようだ)
  make,gccを指定
/home/(user)/.bashrcの最後に
export ANDROID_NDK_ROOT=/cygdrive/c/android-ndk
   export PATH=$PATH:$ANDROID_NDK_ROOT

OpenCV(バイナリ)とAndroid projectは同じフォルダ、かつスペースの無いフォルダ名。すなわちMy Documentの下はだめ。

プロジェクトのjni/Andorid.mk修正
 
include ../OpenCV/share/openCV/OpenCV.mk <<下をこれに変える
#include ../includeOpenCV.mk
#ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
#    #try to load OpenCV.mk from default install location
#    include $(TOOLCHAIN_PREBUILT_ROOT)/user/share/OpenCV/OpenCV.mk
#else
#    include $(OPENCV_MK_PATH)
#endif

cygwinウィンドウ またはコマンドウインドウでもよい
プロジェクトのTOPフォルダへ移動
C:\android-ndk\ndk-build

  /obj/local/armeabi-v7a/libgnustl_static.a: No such file: Permission denied
 このエラーが出たら
  chmod 777 obj/local/armeabi-v7a/libgnustl_static.a
   で再度ndk-build

\OpenCV-2.3.1\libs\armeabi-v7a内のライブラリはbuild時に参照するが、
apkインストール時には重複エラーになるので、名前を変えておく。
必ずbuild前には戻すこと。

注意)
cソースのJNIEXPORT void JNICALL文の関数名にプロジェクト名とクラス名が含まれているので、これらを変更したときは、ndk-buildをし直す必要がある。

例)
JNIEXPORT void JNICALL Java_com_androidgroup_nyartoolkit_ARToolkitDrawer_decodeYUV420SP(JNIEnv * env, jobject obj, jintArray rgb, jbyteArray yuv420sp, jint width, jint height, jint type)

2012年6月11日月曜日

他のアプリを起動する


アプリを起動するとき

            Intent intent1 = new Intent();
            intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            // パッケージ名, クラス名をセット
            intent1.setClassName(pac,clas);
            startActivity(intent1);

pac : パッケージ名
clas : クラス名
(GetIntentCalssNameというアプリで調べる)

標準の設定アプリの場合はこれだけ

           Intent intent1 = new Intent("android.settings.SETTINGS");
           startActivity(intent1);

パワーオンでアプリ起動


レシーバークラス StartReceiver.java

public class StartReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context arg0, Intent arg1) {

 Toast toast=Toast.makeText(arg0,"Boot...",Toast.LENGTH_LONG);
      toast.show();
     
     Intent i = new Intent(arg0, Activity.class);  //起動するアクティビティ指定
     i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     arg0.startActivity(i);
   
   
  }
}

Manifest設定

・ユーザパーミッション

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

・レシーバー設定

        <receiver android:name="com.widebright.tabmanager.StartReceiver"
        android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
       </receiver>





2012年6月8日金曜日

リリース前確認事項


apk 証明書取得

Google MAP APIキー取得

ソース難読化

MACアドレス認証ルーチン

Home/Backボタン処理確認

ライセンス条項添付




ソース難読化

逆コンパイルでソースを盗まれないように難読化する

ADT18以前
project.propetiesに追加 
   proguard.config=proguard.cfg

ADT18以降
project.propetiesに追加 

   proguard.config=android-sdk/tools/proguard/proguard-android.txt:proguard-project.txt
         (proguard.cfgは無く、共用ファイルと個別ファイルに分かれた)



Dalvik format faild with error1 が出る場合は、SDKのバグを修正

       \android-sdk\tools\proguard\bin\proguard.bat
       最後の行を以下のようにする(%1 %2 %3 %4 %5 %6 %7 %8 %9の部分)
       call %java_exe% -jar "%PROGUARD_HOME%"\lib\proguard.jar %1 %2 %3 %4 %5 %6 %7 %8 %9

2014/5/12追記
apacheのライブラリを使うときは以下を、proguard.project.txtに追加する

# Ignore warning
-dontwarn org.apache.**

リリース用証明書とパッケージ作成

証明書発行とパッケージ作成

   eclipseで対象プロジェクト、右クリック
   Android Tools - Export Signed Application Package

 証明書は新規作成か、既存のものを使用 xxxx.keystoreができる

 証明書付パッケージ xxxx.apkができる

 (Google MAP APIを使っている場合は、この証明書を先に作って、APIキーを入手してソースに埋め込む必要がある)


インストール

 コマンドツール  adb install -r xxxxx.apk
 Batファイルにしておくとクリックでインストールできる  install.bat等





画面キャプチャ

AndroidをUSBケーブルでPCに接続

コマンドツールの場合

   ddmsと入力

  Deviceメニュー、Screen captureをクリック


eclipseの場合

  DDMSのDeviceタグの、Screen captureアイコンをクリック


リアルタイム表示

   java -jara asm.jar

   Android Screen Monitor (asm.jar)は以下からダウンロード
   http://code.google.com/p/android-screen-monitor/





Video再生



private MediaController mc;
private VideoView videoView;
private ProgressDialog waitDialog;
 
 String url;   //ここに再生したいビデオのURLを入れる


        try {
            //ビデオビューの生成
            videoView = (VideoView) findViewById(R.id.videoview);
            videoView.requestFocus();
            mc = new MediaController(this);
            videoView.setMediaController(mc);
            mc.show(0); //常にコントローラ表示 ==なぜか効かない!

            //プログレスダイアログ(くるくる回る)
            waitDialog = new ProgressDialog(this);
            waitDialog.setMessage("読込中... しばらくお待ちください");
            waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            waitDialog.show();

            //ローディング終了
            videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
       public void onPrepared(MediaPlayer arg0) {
      // 読込終わったらプログレスダイアログ終了
            if(waitDialog != null){
                waitDialog.dismiss();
                waitDialog = null;
            }
      }
    });

            //エラー
            videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
       public boolean onError(MediaPlayer arg0, int arg1, int arg2) {
             if(waitDialog != null){
                 waitDialog.dismiss();
                 waitDialog = null;
             }
        return false;
       }
     });
         
             
            //再生終了処理
            videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
               public void onCompletion(MediaPlayer arg0) {
                 finish();
              }            
            });

          
            //動画の再生      
            videoView.setVideoURI(Uri.parse(url));
            videoView.start();
         
        } catch (Exception e) {
            android.util.Log.e("",e.toString());
            if(waitDialog != null){
                waitDialog.dismiss();
                waitDialog = null;
            }
        }


Layout xml

        <VideoView
        android:id="@+id/videoview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
       />


     

2012年6月7日木曜日

Webビュー


        //Webビューの生成
        WebView webView=(WebView) findViewById(R.id.webview);
        WebSettings settings=webView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setSavePassword(false);
        settings.setSaveFormData(false);
        settings.setSupportZoom(true);
        settings.setBuiltInZoomControls(true);

String startURL = "http://www.yahoo.co.jp";   
       //Web表示
        webView.loadUrl(startURL);


リンク(http://xxxx)をクリックしたとき、標準ブラウザで表示されてしまうので、以下を設定

        webView.setWebViewClient(new WebViewClient(){

              //URLジャンプ前に呼ばれる(デフォルトでは標準ブラウザに表示してしまう)
    // (相対パスなら、同じWebView内で表示される)
        @Override
            public boolean shouldOverrideUrlLoading(WebView view,String url) {

                //view.loadUrl(url); //このWebViewでそのまま表示するとき
          
                //他のActivityへ移るとき
            Intent intent = new Intent(Activity.this, 起動するクラス.class);
            intent.putExtra("url", url);
           
        // アクティビティ起動
            startActivity(intent); 
                
                return true; //他のアプリ、Activity起動のとき、true
                    //同じWebView内ならfalse
            }



Layout xml

<WebView
     android:id="@+id/webview"
     android:layout_width="fill_parent"  
     android:layout_height="wrap_content"  
     android:gravity="center"
     android:layout_weight="1"
  />

CSVファイル

CSVファイルのRead/Write

opencsvのライブラリを使用
http://java.akjava.com/library/opencsv

  ダウンロードしたsrc/auフォルダを、Eclipseの対象プロジェクトのsrcにコピーする。


読み出しプログラム例


FileInputStream input = openFileInput("CSVファイル名");
InputStreamReader ireader=new InputStreamReader(input, "UTF-8");
CSVReader reader = new CSVReader(ireader,',','"',0);

String[] csv; //1行分のデータ格納用

//1行づつ最後まで読み出し
while((csv = reader.readNext()) != null)
 {
              1行毎の処理
}

書き込みは、opencsvの上記サイトに下のような例があるが、自分でカンマを付けて、OutputStreamでも良い。

CSVWriter writer = new CSVWriter(new PrintWriter(System.out));
writer.writeNext(test);
writer.flush()

FTPファイル送信




FTPファイル送信サンプル

変数定義

    String sHost = "192.168.11.27"; //ftp server IP
    String sUser = "user";              //ユーザ名
    String sPass = "pass";             //パスワード
    String sRemotePath = "/";       //サーバーパス
    FTPClient ftpCli;



転送ファイル指定、送信

    FTPFile ftpFile = new FTPFile();
    ftpFile.setName("ファイル名");
    uploadFTP(ftpFile);




送信ルーチン

    //FTP送信
private void uploadFTP(FTPFile ftpFile) {
//3.1からこれを入れないとエラー(android.os.NetworkOnMainThread Exception)
   StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());

   int    nPort = 21;
   int    nTimeout = 100*1000;

   try {

           ftpCli = new FTPClient();
           // タイムアウト設定
           ftpCli.setDefaultTimeout(nTimeout);
           // 接続
           ftpCli.connect(sHost,nPort);
           if (FTPReply.isPositiveCompletion(ftpCli.getReplyCode()) == false) {
               // 接続エラー
               throw new Exception(
                       new StringBuffer("FTP接続エラー Code=")
                       .append(ftpCli.getReplyCode())
                       .toString()
               );
           }

           // ソケットタイムアウト設定
           ftpCli.setSoTimeout(nTimeout);

           // ログイン
           if (ftpCli.login(sUser, sPass) == false) {
               // 認証エラー
               throw new Exception(
                       new StringBuffer("FTP認証エラー Code=")
                       .append(ftpCli.getReplyCode())
                       .toString()
               );
           }

           // ファイル転送モード設定
           ftpCli.setFileType(FTP.ASCII_FILE_TYPE);
           ftpCli.enterLocalPassiveMode();
           ftpCli.setDataTimeout(nTimeout); // タイムアウト設定

           // ディレクトリ移動
           if (ftpCli.changeWorkingDirectory(sRemotePath) == false) {
               throw new Exception(
                       new StringBuffer("Failed changeWorkingDirectory() ")
                       .append(sRemotePath)
                       .append(" Code=")
                       .append(ftpCli.getReplyCode())
                       .toString()
               );
           }

           // アップロード

           FileInputStream fos = openFileInput(ftpFile.getName());
           ftpCli.storeFile(ftpFile.getName(), fos);
           fos.close();
       
           // 完了処理
           ftpCli.disconnect();

       } catch (SocketException e) {
        showDialog(Activity.this,"sエラー",e.toString());
       } catch (IOException e) {
        showDialog(Activity.this,"iエラー",e.toString());
       } catch (Exception e) {
        showDialog(Activity.this,"eエラー",e.toString());      
           }
}
 
//ダイアログの表示
    private static void showDialog(Context context,String title,String text) {
        AlertDialog.Builder ad=new AlertDialog.Builder(context);
        ad.setTitle(title);
        ad.setMessage(text);
        ad.setPositiveButton("OK",null);
        ad.show();
    }




2012年6月6日水曜日

MAP表示


地図表示サンプル

    private MapView         mapView;//Mapビュー
    private MapController   mapCtrl;//Mapコントローラ
    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.setting);              

       //Mapビューの生成
        mapView=(MapView) findViewById(R.id.Map); //(this,API_KEY);
        mapView.setBuiltInZoomControls(true);
      
        mapCtrl=mapView.getController();

        //中心の緯度、経度を指定
        mapCtrl.setCenter(new GeoPoint(
                (int)(36.24831*1E6),
                (int)(139.533294*1E6))); 
    
        mapCtrl.setZoom(16); 

}

Layout xml (API Keyをあらかじめ取得しておく)

        <com.google.android.maps.MapView
          android:id="@+id/Map"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:enabled="true"
          android:clickable="true"    
          android:apiKey="05fjECm****************ZgcHR6SzVdUmQ"
            />

Manifestへ追加

<application>の中へ
        <uses-library android:name="com.google.android.maps"/>  

target はGoogle APIにする。



データベース・アクセス




    private final static String PDB_NAME ="product.db";//DB名
    private final static String PDB_TABLE="product";   //テーブル名
    private final static int    PDB_VERSION=1;      //バージョン
    private static SQLiteDatabase pdb;      //データベースオブジェクト


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
   
        //データベースオブジェクトの取得
        PDBHelper pdbHelper=new PDBHelper(this);
        pdb=pdbHelper.getWritableDatabase();

  }


    //DB検索
    private void readDB(String id) throws Exception {
    //idで検索
        Cursor c=pdb.query(PDB_TABLE,new String[]{"id","Name","Category","Price"},
            "id='" + id + "'",null,null,null,null);
        if (c.getCount()==0) throw new Exception();
        c.moveToFirst(); //最初に一致したもの
                               //順番に取り出す場合は、c.moveToNext(); で次のレコードへ

        //DBから取り出し
        String id   =c.getString(0);
        String name   =c.getString(1);
    String category   =c.getString(2);
        String price   =c.getString(3);

        c.close();
    }    

   //DBへの書き込み
    private void writeDB(String id,String Name,String Cate,String Pr) throws Exception {
        ContentValues values=new ContentValues();
        values.put("id",id);
        values.put("Name",Name);
        values.put("Category",Cate);
        values.put("Price",Pr);

        //idの同じレコードを更新
        int colNum=db.update(DB_TABLE,values,"id='" + id + "'" ,null);

        //新しいidなら追加
        if (colNum==0) {
        db.insert(DB_TABLE,"",values);
        ((TextView) findViewById(R.id.Message)).setText("1レコード追加しました");
        }else{
        ((TextView) findViewById(R.id.Message)).setText("1レコード更新しました");
        }
    }



//データベースヘルパーの定義
private static class PDBHelper extends SQLiteOpenHelper {
    //データベースヘルパーのコンストラクタ
    public PDBHelper(Context context) {
        super(context,PDB_NAME,null,PDB_VERSION);
    }
   
    //データベースの生成
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table if not exists "+
            PDB_TABLE+"(id text primary key,Name text,Category text,Price text)");
    }

    //データベースのアップグレード
    @Override
    public void onUpgrade(SQLiteDatabase db,
        int oldVersion,int newVersion) {
        db.execSQL("drop talbe if exists "+PDB_TABLE);
        onCreate(db);
    }
}

位置情報の取得

現在位置を取得するサンプル


//起動時、画面復帰時に位置確認
//画面復帰時にも位置確認をするため、onCreateでなく、onResumeに書く
@Override
    public void onResume(){
    super.onResume();

         //ロケーションマネージャの設定
        LocationManager lm=(LocationManager)getSystemService(Context.LOCATION_SERVICE);
        //10秒ごとに10m変化を通知
        //GPSとネットワーク どちらでも可能。GPSの方が精度が高い
        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER,10000,10,this);
//     lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,10000,10,this);
//最新の位置情報を取得
        Location location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
getLoc(location,);
   }




//位置が変わったら再取得する
public void onLocationChanged(Location location) {
getLoc(location);
}

//位置情報取得有効化
public void onProviderEnabled(String provider) {

}
//位置情報取得無効化
public void onProviderDisabled(String provider) {
   showDialog(Activity.this,"Map","位置情報取得 無効");
       }

//位置情報状態変更
public void onStatusChanged(String provider,
   int status,Bundle extras) {

}



//位置確認
private void getLoc(Location location){

          if (location !=null){
      //緯度と経度の取得
double cLat = location.getLatitude();
double cLong = location.getLongitude();
          }else {
errDialog("現在位置が取得できませんでした。");
          }

}


@Override
public void onStart() {
   super.onStart();
}

@Override
public void onStop() {
   super.onStop();
   //ロケーションマネージャー停止
   lm.removeUpdates(this);
}

protected boolean isRouteDisplayed() {
   return false;
}


Manifestの設定


    //ネットワーク許可
  <uses-permission android:name="android.permission.INTERNET" />
  //GPS
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  //エミュレータ用  
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>





MACアドレスの取得方法

MACアドレスの取得方法


        WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        String macAddress = wifiInfo.getMacAddress();

Manifestに以下を追加

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

2012/6/21追記
無線LANが、電源ON時にOFFになっていると、MACアドレスは取得できないことが判明。
デバイス識別として他に使えるのはシリアル番号くらいしかなさそう。

String serialId = android.os.Build.SERIAL;
試してないが、今度やってみよう。

Home,Backキーが押されたときの処理

Backキーが押されたとき


Backキーが押されると、Activityが終了してしまう。誤操作を防止したいので・・・・
Backキーを禁止するには、


   //BACKキー禁止
   @Override
   public boolean dispatchKeyEvent(KeyEvent event) {
       if (event.getAction()==KeyEvent.ACTION_DOWN) {
           switch (event.getKeyCode()) {
           case KeyEvent.KEYCODE_BACK:
               return true; //trueでActivityを終了しない
           }
       }
       return super.dispatchKeyEvent(event);
   }

終了していいか確認する


    //BACKキーで終了確認
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction()==KeyEvent.ACTION_DOWN) {
            switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_BACK:
                // 終了していいか、ダイアログで確認
             showDialog(WBsetActivity.this,"終了","終了してもよろしいですか");
                return true; 
            }
        }
        return super.dispatchKeyEvent(event);
    }
  //ダイアログ

    private void showDialog(Context context,String title,String text) {
        AlertDialog ad=new AlertDialog.Builder(this)
        .setTitle(title)
        .setMessage(text)
        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton) {
        finish(); //終了
        }
        })
        .setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton) {
       
        }
        })

        .show();
    }







HOMEキーが押されたとき

HOMEキーが押されたことはキャッチできるが、HOME画面に戻るのは阻止できない。
Activityが中断されたまま残るのが困るときはどうする?
強制的にActivityを終了させるしかない。

    @Override
    public void onCreate(Bundle savedInstanceState) {

//HOMEキーが押されたときのレシーバ設定
HomeButtonReceive m_HomeButtonReceive = new HomeButtonReceive();
IntentFilter iFilter = new IntentFilter();
iFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
this.registerReceiver(m_HomeButtonReceive, iFilter);

}

    //HOMEボタンで終了する。
public class HomeButtonReceive extends BroadcastReceiver{
@Override
public void onReceive(Context arg0, Intent arg1){
Toast.makeText(getApplicationContext(), "終了" ,Toast.LENGTH_SHORT).show();
finish();
}
}