LiveWallpaperをつくる -デバッグ編-
LiveWallpaperの動作確認用のActivityを用意して開発するためのメモ。
前回、LiveWallpaperを作りましたが、デバッグや動作確認を行うのが結構めんどいため、Androidアプリとして開発を行えるようにします。
手順
MainActivityの作成
Activity継承クラス、MainActivityを作成します。
また、描画をSerfaceViewに行うため、SerfaceViewを設定したlayout/main.xmlを準備します。
- MainActivity.java
package com.ttshrk.livewallpaper_example; import android.app.Activity; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Bundle; import android.os.Handler; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { private final Handler mHandler = new Handler(); private SurfaceView mSurfaceView; boolean mVisible = true; private int mMillisPerFrame; private final Runnable mDrawer = new Runnable() { public void run() { drawFrame(); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFormat(PixelFormat.TRANSLUCENT); setContentView(R.layout.main); mMillisPerFrame = 1000 / 30; mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView1); // 描画開始 mHandler.postDelayed(mDrawer, 0); } public void drawFrame() { final SurfaceHolder holder = mSurfaceView.getHolder(); Canvas c = null; try { c = holder.lockCanvas(); if (c != null) { // draw something update(); c.drawColor(Color.BLACK); draw(c); } } finally { if (c != null) holder.unlockCanvasAndPost(c); } mHandler.removeCallbacks(mDrawer); if (mVisible) { mHandler.postDelayed(mDrawer, mMillisPerFrame); } } void update() { } void draw(Canvas canvas) { } }
SerfaceViewに描画を行うだけのActivityです。
タイトルの表示をけして、画面いっぱいに表示しています。
onCreate、update、drawに確認を行いたいモジュールを設定してください。
(widthやheightなど必要なプロパティは適宜設定してください。)
次にレイアウトxmlです。
- layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <SurfaceView android:id="@+id/surfaceView1" android:layout_height="fill_parent" android:layout_width="fill_parent"></SurfaceView> </LinearLayout>
これも、画面いっぱいにSurfaceViewを貼り付けただけです。
AndroidManifest.xmlの修正
AndroidManifest.xmを修正します。
Androidアプリとして起動できるよう、ApplicationにMainActivityを設定します。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ttshrk.livewallpaper_example" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="7" /> <!--<uses-feature android:name="android.software.live_wallpaper" />--> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <!-- <application android:label="@string/app_name" android:icon="@drawable/icon" android:debuggable="true"> <service android:label="@string/app_name" android:name=".ExampleWallpaer" android:permission="android.permission.BIND_WALLPAPER"> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" /> </service> </application> --> </manifest>
とりあえず、Wallpaperサービスを残してあります。
これも、applicationタグを追加しただけです。
だいたい以上です。
描画を行う部分をクラスにして設定すれば壁紙をあぷりとして実行できると思います。
おまけ
Handler#delayPostのdelay値は結構いい加減なので、FPSを一定にしたいときは、以下のようにするといいと思います。
- MainActivity.java 抜粋
public void drawFrame() { final SurfaceHolder holder = mSurfaceView.getHolder(); Canvas c = null; try { c = holder.lockCanvas(); if (c != null) { // draw something update(); c.drawColor(Color.BLACK); draw(c); } } finally { if (c != null) holder.unlockCanvasAndPost(c); } mHandler.removeCallbacks(mDrawer); if (mVisible) { long delay = delay(); mHandler.post(mDrawer, delay); } } private long delay() { long currentDelay = mMillisPerFrame - (int) mMilliTimeMesure.getElapsed(); mMilliTimeMesure.reset(); // 遅延は無視 if (currentDelay < 0) return 0L; return currentDelay; }
1フレームごとにかかった経過時間を測定して、自前でsleep処理を行います。
IS01でも、30FPSくらいなら問題なく動作します。
MilliTimeMeasureですが、こんな感じの経過時間を取得するだけのクラスを用意します。
- MilliTimeMeasure.java
public class MilliTimeMeasure { private long lastTime; public MilliTimeMeasure() { reset(); } private long getCurrent() { return System.currentTimeMillis(); } public void reset() { lastTime = getCurrent(); } public long getElapsed() { return getCurrent() - lastTime; } }
サンプルコード
livewallpaper_example2.zip
LiveWallpaperをつくる
LiveWallpaperを作るメモ。
Handlerを利用してメッセージを処理する(描画を行う)サンプルです。
参考サイト
Android Developers CubeLiveWallpaper
http://developer.android.com/intl/ja/resources/samples/CubeLiveWallpaper/index.html
概要
実装
WallpaperService継承クラス作成
WallpaperServiceを継承するクラスを作成します。
このクラスがLiveWallpaperサービスの主な実装となります。
- ExampleWallpaer.java
package com.ttshrk.livewallpaper_example; import android.os.Handler; import android.service.wallpaper.WallpaperService; import android.service.wallpaper.WallpaperService.Engine; public class ExampleWallpaer extends WallpaperService { private final Handler mHandler = new Handler(); @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); } @Override public Engine onCreateEngine() { // TODO return null; } }
このクラスの中のWallpaperService::onCreateEngineが描画を行うインスタンス(後述)を返します。
このEngineクラスのインスタンスは複数作成されることがあるため、静的プロパティの扱いに注意が必要です。(壁紙に設定してあるLiveWallpaperを設定画面のプレビューで表示したときなど。)
WallpaperService.Engineを実装する
壁紙の描画を行うクラスExampleWallpaer.ExampleEngineを作成します。
このクラスは、WallpaperService.Engineを継承します。
- WallpaperService.java
public class ExampleWallpaer extends WallpaperService { private final Handler mHandler = new Handler(); @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); } @Override public Engine onCreateEngine() { return new ExampleEngine(); } class ExampleEngine extends Engine { Context context; int width; int height; boolean mVisible; PointF timePoint = new PointF(); Paint timePaint; float mMillisPerFrame; SimpleDateFormat simpleDateFormat; String timeString; private final Runnable mDrawer = new Runnable() { public void run() { drawFrame(); } }; ExampleEngine() { context = ExampleWallpaer.this.getApplicationContext(); width = getWallpaperDesiredMinimumWidth() / 2; height = getWallpaperDesiredMinimumHeight(); mMillisPerFrame = 1000f / 30f; simpleDateFormat = new SimpleDateFormat("HH:mm:ss"); timePaint = new Paint(); timePaint.setAntiAlias(true); timePaint.setColor(Color.WHITE); timePaint.setTextSize(80); timePaint.setTextAlign(Paint.Align.RIGHT); timePaint.setAlpha(0xcf); timePoint.set(width, height * 3 / 5); timeString = ""; } @Override public void onCreate(SurfaceHolder surfaceHolder) { super.onCreate(surfaceHolder); } @Override public void onDestroy() { super.onDestroy(); mHandler.removeCallbacks(mDrawer); } @Override public void onVisibilityChanged(boolean visible) { mVisible = visible; if (visible) { drawFrame(); } else { mHandler.removeCallbacks(mDrawer); } } @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { super.onSurfaceChanged(holder, format, width, height); drawFrame(); } @Override public void onSurfaceCreated(SurfaceHolder holder) { super.onSurfaceCreated(holder); } @Override public void onSurfaceDestroyed(SurfaceHolder holder) { super.onSurfaceDestroyed(holder); mVisible = false; mHandler.removeCallbacks(mDrawer); } @Override public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) { drawFrame(); } /* * Store the position of the touch event so we can use it for drawing * later */ @Override public void onTouchEvent(MotionEvent event) { timePoint.set(event.getX(), event.getY()); super.onTouchEvent(event); } void drawFrame() { final SurfaceHolder holder = getSurfaceHolder(); Canvas c = null; try { c = holder.lockCanvas(); if (c != null) { update(); c.drawColor(Color.BLACK); draw(c); } } finally { if (c != null) holder.unlockCanvasAndPost(c); } // Reschedule the next redraw mHandler.removeCallbacks(mDrawer); if (mVisible) { mHandler.postDelayed(mDrawer, (int) mMillisPerFrame); } } void update() { timeString = simpleDateFormat.format(new Date()); } void draw(Canvas canvas) { canvas.drawText(timeString, timePoint.x, timePoint.y, timePaint); } } }
ポイントは以下の通りです。
- Handler
ExampleWallpaer#mHandlerにメッセージ(mDrawer)をPostして描画を行います。Handlerクラスは受け取ったメッセージを独立したスレッドで順次処理していくため、サービスの実行を阻害せずに描画を行ってくれます。
- ExampleEngine#mDrawer
描画を行うRnnbable(メッセージ)です。
- ExampleEngine#onVisibilityChanged
壁紙が表示、非表示されたタイミングで呼ばれます。
このイベントで、描画の開始や停止を設定します。
- ExampleEngine#drawFrame
1frameあたりの処理を行います。また、次に行う描画処理の登録も行います。
AndroidManifest.xml
AndroidManifest.xmlにLiveWallpaperの設定を作成します。
- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ttshrk.livewallpaper_example" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="7" /> <uses-feature android:name="android.software.live_wallpaper" /> <application android:label="@string/app_name" android:icon="@drawable/icon" android:debuggable="true"> <service android:label="@string/app_name" android:name=".ExampleWallpaer" android:permission="android.permission.BIND_WALLPAPER"> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" /> </service> </application> </manifest>
ポイントは以下の通りです。
- live_wallpaperの設定
uses-featureにandroid.software.live_wallpaperを設定します。
<uses-feature android:name="android.software.live_wallpaper" />
- serviceの登録
ExampleWallpaerをサービスとして登録します。
- wallpaperのxmlファイルを設定
壁紙の設定を記述したXMLファイルを、meta-data指定します。
<meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" />
リソースファイル作成
壁紙の設定を記述したXMLファイルを作成します。
これはライブ壁紙の設定画面の一覧に表示されます。
<?xml version="1.0" encoding="utf-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" android:thumbnail="@drawable/icon" android:description="@string/service_description" />
stringリソース設定。
- res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Livewallpaper Example</string> <string name="service_description">壁紙の説明</string> </resources>
以上です。
実行すると壁紙に時刻が表示されます。また、タッチしたポイントに表示が移動します。
サンプルコード
livewallpaper_example.zip
PhoneGapでSencha Touchを使っためも
PhoneGapでSencha Touchを使ったときに、PhoneGapのJavascriptブリッジを有効にする方法が少し特殊だったため、メモしておきます。あわせてSench(Ext JS)風味の書き方ものせておきます。
参考サイト
Sencha Touch
http://www.extjs.co.jp/
Sencha Touchドキュメント
http://www.extjs.co.jp/deploy/touch/docs/
Tutorial:A Sencha Touch MVC application with PhoneGap
http://www.sencha.com/learn/Tutorial:A_Sencha_Touch_MVC_application_with_PhoneGa
PhoneGap
http://www.phonegap.com/home/
概要
PhoneGapでは、onDevideReadyイベント後にJavascriptブリッジが有効になるため、以下のようにコーディングしていると思います。
- body onloadに登録する例
<script type="text/javascript"> funciton onLoad() { document.addEventListener("deviceready", onDeviceReady, false); } function onDeviceReady() { } </script> ... <body onload="onLoad();"> ... </body>
ただ、PhoneGapでSencha Touchを利用する場合、上記の方法ではうまくいかないようです。そのためここでは、PhoneGap.jpの初期化後にSencha Touchのアプリケーションクラスを初期化するにします。おおまかな手順は以下のようになります。
- Sencha Touchのアプリケーションクラス(Ext.Application)の作成
- onDevicereadyイベントでSencha Touchのアプリケーションクラスの初期化を行うコードを追加
- Viewクラス(Ext.Panel)の作成
- PhoneGapのJavascriptブリッジクラスを使ってみるクラスを追加
サンプルコード
Sencha Touchのアプリケーションクラス(Ext.Application)の作成
Sencha Touchのアプリケーションクラスを作成します。
view、controller、modelをサポートした、MVCアプリケーションとして利用できますが、ここではviewのみ設定します。
- js/sample.js
/** * アプリケーションクラス登録 */ Ext.regApplication({ name: 'app', launch: function() { this.launched = true; this.mainLaunch(); }, mainLaunch: function() { console.log('start mainLaunch'); if (!device || !this.launched) {return;} console.log('mainLaunch'); // ページView生成。後の手順で宣言している「Ext.app.views.Viewport」です this.views.viewport = new this.views.Viewport(); } });
mainLaunchメソッドでdevice変数のチェックを行っていますが、スマートフォンのときのみ有効な変数です。デスクトップで動作の確認を行いたい場合は、この部分をコメントアウトすると動作します。
onDevicereadyイベントでSencha Touchのアプリケーションクラスの初期化を行うコードを追加
PhoneGapの初期化が終わってからSencha Touchのアプリケーションクラスを初期化します。
- index.html
<!DOCTYPE html> <html> <head> <title></title> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <link rel="stylesheet" type="text/css" href="css/sencha-touch.css"> <script type="text/javascript" charset="utf-8" src="js/sencha.js"></script> <script type="text/javascript" charset="utf-8" src="js/phonegap.js"></script> <script type="text/javascript" charset="utf-8" src="js/sample.js"></script> <script type="text/javascript"> document.addEventListener("deviceready", app.mainLaunch, false); </script> </head> <body></body> </html>
onDevicereadyイベントにapp.mainLaunchを登録します。
Viewクラス(Ext.Panel)の作成
ヘッダとフッダのパネルのあるページです。
- js/sample.js
/** * View */ app.views.Viewport = Ext.extend(Ext.Panel, { tabletStartupScreen: 'images/startup.png', phoneStartupScreen: 'images/startup.png', icon: 'icon.png', glossOnIcon: false, initComponent: function() { var me = this; var panel = new Ext.Panel({ // Ext.Panelクラスを拡張する fullscreen: true, cls: 'cards', componentCls: 'bodycls', // cssのclassを指定できる centered: true, layout: { type: 'vbox', align: 'stretch' }, defaults: { flex: 1 }, html: [ '<p>hello sencha touch</p>', '<button onclick="vibrate();">vibrate</button>' ], // ツールバーなどのdock item定義 dockedItems: [ { dock : 'top', xtype: 'toolbar', // Ext.Toolbarクラスを指定 title: 'HELLO SENCHA TOUCH', cls : 'topcls', }, new Ext.Toolbar({ // Ext.Tollbarを直接生成してもよい dock : 'bottom', title: 'copylight hoge', cls : 'bottomcls', }), ], }); // パネル構築 panel.doComponentLayout(); } });
以下の部分がツールバーに囲まれた部分に表示されます。
html: [ '<p>hello sencha touch</p>', '<button onclick="vibrate();">vibrate</button>' ],
PhoneGapのJavascriptブリッジクラスを使ってみるクラスを追加
PhoneGap APIのnavigator.notification.vibrateを使ってみます。
js/sample.js
/** * 振動 */ Ext.app.Vibrate = Ext.extend(Object, { name: 'vibrate', vibrateCount: 0, vibrateTime: 0, constructor: function(config) { var me = this; Ext.apply(me, config); // configのキーと値をインスタンスのフィールドとして設定する }, play: function() { console.log("vibrate play"); navigator.notification.vibrate(this.vibrateTime); } }); /** * 振動呼び出し */ function vibrate() { var v = new Ext.app.Vibrate({'vibrateTime':200}); v.play(); }
Ext.extendを使って、Objectの拡張クラスを作成します。Ext.applyを利用して、パラメータの設定を行います。
app.views.Viewportの以下の部分で呼び出しています。
<button onclick="vibrate();">vibrate</button>
以上です。起動すると、以下のように表示されると思います。
ContentProviderでアプリ内のファイルを公開するサンプル
ContentProviderを使って、asset内のファイルをGMailに添付ファイルとして渡すサンプルです。
アプリ内のassets配下のファイルをIntent経由でほかのアプリに渡したい場合も動作します。
当時、ContentProviderがよくわかっていないこともあり、プロジェクトのassets配下のファイルが添付できなくてかなり悩みましたが、ContentProviderのリファレンスにちゃんと書いてあった・・・。
概要
Intent経由でアプリからGMailを起動したときに、ContentProviderを使って添付ファイルを渡します。
実装するものは以下のとおりです。
- AssetFileProvider
コンテンツプロバイダ。
アプリ内のAssetファイルを渡します。insert,update,deleteなどは実装しません。
- FileProvider
コンテンツプロバイダ。
SDカードなどのファイルを渡します。insert,update,deleteなどは実装しません。
- AndroidManifest.xmlの編集
コンテントプロバイダをAndroidManifest.xmlに登録します。
- その他リソース
res/layout/main.xml
res/values/strings.xml
画像ファイル
- CPSampleの作成
メインActivityクラス、CPSampleにGmailを起動するコードを追加します。
実装
AssetFileProviderの作成
アプリ内のAssetファイルを返す、ContentProviderを実装します。
onCreate、getType、insert、update、deleteは使用しません。
ここではAssetファイルを返すにはopenAssetFileを実装します。
- FileContentProvider.java
package com.ttshrk.provider; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import android.content.ContentProvider; import android.content.ContentValues; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.util.Log; public class AssetFileProvider extends ContentProvider { public static final String AUTHORITY = "com.ttshrk.provider.AssetFile"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); public static final String TAG = "AssetFileProvider"; @Override public boolean onCreate() { return false; } @Override public String getType(Uri uri) { return null; } @Override public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { Log.i(TAG, "open Asset file"); try { return getContext().getAssets().openFd("sample_01.gif"); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "ERROR: " + e); throw new FileNotFoundException(e.getMessage()); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
FileProviderの作成
SDカードなどのファイルを返す、ContentProviderを実装します。
onCreate、getType、insert、update、deleteは使用しません。
ここではopenFileを実装します。
- FileProviderr.java
package com.ttshrk.provider; import java.io.File; import java.io.FileNotFoundException; import java.net.URI; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.util.Log; public class FileProvider extends ContentProvider { public static final String AUTHORITY = "com.ttshrk.provider.File"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); public static final String TAG = "FileProvider"; @Override public boolean onCreate() { return false; } @Override public String getType(Uri uri) { return null; } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { Log.i(TAG, "open file"); URI path = URI.create("file:///sdcard/sample_02.jpg"); ParcelFileDescriptor parcel = ParcelFileDescriptor.open(new File(path), ParcelFileDescriptor.MODE_READ_ONLY); return parcel; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
AndroidManifest.xmlの編集
providerを追加します。
- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ttshrk.cp_sample" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="3" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".CPSample" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:name="com.ttshrk.provider.FileProvider" android:authorities="com.ttshrk.provider.File" /> <provider android:name="com.ttshrk.provider.AssetFileProvider" android:authorities="com.ttshrk.provider.AssetFile" /> </application> <uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission> </manifest>
その他リソース
- res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout1"> <Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/button1" android:text="@string/attach_mail"></Button> <Button android:text="@string/action_02" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> </LinearLayout> <ImageView android:src="@drawable/icon" android:id="@+id/imageView1" android:layout_height="fill_parent" android:layout_width="fill_parent"></ImageView> </LinearLayout>
- res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">cp_sample</string> <string name="attach_mail">メール送信</string> <string name="action_02">View表示</string> </resources>
- 画像ファイル
以下のファイルを設置
assets/sample_01.gif
/sdcard/sample_02.jpg
CPSampleの作成
コンテントプロバイダのテストコードを書きます。
実装例は以下のとおりです。
-
- Gmailに画像を添付する。
- ImageViewに画像を渡す。
- CPSample.java
package com.ttshrk.cp_sample; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; public class CPSample extends Activity { ImageView imageView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button1 = (Button) findViewById(R.id.button1); button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { buttonClick1(); } }); Button button2 = (Button) findViewById(R.id.button2); button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { buttonClick2(); } }); imageView = (ImageView)findViewById(R.id.imageView1); } /** * GMailにAssetFileProvider経由でAssetのファイル(sample_01.gif)を添付 */ private void buttonClick1() { Intent intent = new Intent(Intent.ACTION_SEND); // subject intent.putExtra(Intent.EXTRA_SUBJECT, "さぶじぇくと"); // text intent.putExtra(Intent.EXTRA_TEXT, "テキスト"); // attachment intent.setType("image/gif"); Uri uri = Uri.parse("content://com.ttshrk.provider.AssetFile/sample_01.gif"); intent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(intent, null)); } /** * imageViewにFileProvider経由でSDカードのファイル(sample_02.jpg)を表示 */ private void buttonClick2() { imageView.setImageURI(Uri.parse("content://com.ttshrk.provider.File/")); } }
以上で、実装は終了です。
起動すると、「メール送信」と「View表示」のボタンが表示されます。
- メール送信
→GMailにファイルを添付してを起動します。送信するとAssetのファイル(assets/sample_01.gif)が添付されます。
uriを「content://com.ttshrk.provider.AssetFile/sample_01.gif」のように指定していますが、GMailに添付ファイル名を渡すため、sample_01.gifをつけています。上記の実装では、AssetFileProvider側で「sample_01.gif」の部分を無視しています。
- View表示
→アプリ上に、SDカードから読み込んだファイル(/sdcard/sample_02.jpg)が表示されます。ContentProvider経由でImageViewを表示するサンプルとしてみてもらえれば・・・。
以上です・・・。
上記のサンプルコードです。(すこしコードが変わってしまっていますが・・・)
cp_sample.zip
参考サイト
Android content provider example
http://www.xinotes.org/notes/note/1294/
JavaMailでのメール送信まとめその4
JavaMailでのメール送信まとめその4。
AndroidアプリからJavamailでメール送信を行うサンプル。
Android端末専用のjarファイル(mail.jar、additionnal.jar、activation.jar)が必要ですが、MimeMessageの作成、送信手順は同じです。
また、Outbound Port 25 Blockingにより、25番ポートでメール送信が不可能なことがあるため、gmailのアカウントを使用して送信しています。
Android用JavaMailのダウンロード
android用のJavaMailを提供しているサイト「javamail-android」よりダウンロードします。
以下のjarファイルをダウンロードしてください。
- javamail-android
http://code.google.com/p/javamail-android/
-
- mail.jar
- additionnal.jar
- activation.jar
libsといったディレクトリを作成し、上記のjarファイルをコピーしてください。
その後、ビルドパスに追加すれば準備完了です。
(eclipseの場合は、jarファイルを右クリックした後、ビルドパスに追加を選択すればOKです。)
サンプル
gmailでメール送信
ネットワークアクセスが発生するため、パーミッションの設定が必要になります。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
- gmail経由での送信サンプルコード
Properties props = new Properties(); props.put("mail.smtp.host", "smtp.gmail.com"); // SMTPサーバ名 props.put("mail.host", "smtp.gmail.com"); // 接続するホスト名 props.put("mail.smtp.port", "587"); // SMTPサーバポート props.put("mail.smtp.auth", "true"); // smtp auth props.put("mail.smtp.starttls.enable", "true"); // STTLS // セッション Session session = Session.getDefaultInstance(props); session.setDebug(true); MimeMessage msg = new MimeMessage(session); try { msg.setSubject("gmailでメール送信テスト(utf-8)", "utf-8"); msg.setFrom(new InternetAddress("Fromアドレス")); msg.setSender(new InternetAddress("Senderアドレス")); msg.setRecipient(Message.RecipientType.TO, new InternetAddress("送信先アドレス")); msg.setText("gmail経由でgmail.com向けメール送信テスト", "utf-8"); Transport t = session.getTransport("smtp"); t.connect("gmailアカウント XXXX@gmail.com", "gmailパスワード"); t.sendMessage(msg, msg.getAllRecipients()); } catch (MessagingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
携帯メール、デコメを送信
iPhone(i.softbank.jp)向けに送信してみます。
package com.ttshrk.send_mail_from_app; import java.util.Properties; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.NoSuchProviderException; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.ttshrk.mbemoji.Emoji; import com.ttshrk.mbemoji.EmojiUtility; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.UnknownServiceException; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.FileDataSource; public class SendMailFromApp extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button1 = (Button) findViewById(R.id.button1); button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { buttonClick1(); } }); Button button2 = (Button) findViewById(R.id.button2); button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { buttonClick2(); } }); } /** * utf-8 softbank.ne.jp */ private void buttonClick1() { Session session = createSendGMailSession(); MimeMessage msg = new MimeMessage(session); try { msg.setSubject("gmailで携帯向けメール送信テスト(utf-8)", "utf-8"); msg.setFrom(new InternetAddress("from@example.com")); msg.setSender(new InternetAddress("from@example.com")); msg.setRecipient(Message.RecipientType.TO, new InternetAddress( "?????@i.softbank.jp")); msg.setDataHandler(new DataHandler(new TextBinaryDataSource( "gmail経由でi.softbank.jp向けメール送信テスト[:1025:][:0105:]" .getBytes(), "utf-8", "plain", Emoji.Mode.SOFTBANK))); sendGmail(msg, session); } catch (MessagingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * utf-8 i.softbank.jp でこめ * multipart/related * ├ multipart/alternative * │ ├ text/plain * │ └ text/html * └ image (インライン画像/添付画像) */ private void buttonClick2() { Session session = createSendGMailSession(); MimeMessage msg = new MimeMessage(session); try { msg.setSubject("gmailでメール送信テスト(utf-8) でこめ", "utf-8"); msg.setFrom(new InternetAddress("from@example.com")); msg.setSender(new InternetAddress("from@example.com")); msg.setRecipient(Message.RecipientType.TO, new InternetAddress( "?????@i.softbank.jp")); // related Multipart relatedPart = new MimeMultipart("related"); // alternative MimeBodyPart alternativeBodyPart = new MimeBodyPart(); Multipart alternativePart = new MimeMultipart("alternative"); alternativeBodyPart.setContent(alternativePart); relatedPart.addBodyPart(alternativeBodyPart); // text mail MimeBodyPart textBodyPart = new MimeBodyPart(); textBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource( "gmail経由でi.softbank.jp向けメール送信テスト[:1025:][:0105:]".getBytes(), "utf-8", "plain", Emoji.Mode.SOFTBANK))); textBodyPart.setHeader("Content-Transfer-Encoding", "base64"); alternativePart.addBodyPart(textBodyPart); // html mail MimeBodyPart htmlBodyPart = new MimeBodyPart(); htmlBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource( "<HTML><BODY>gmail経由でi.softbank.jp向けメール送信テスト[:1025:][:0105:]<IMG src=\"cid:12345@12345\"></BODY></HTML>".getBytes(), "utf-8", "html", Emoji.Mode.SOFTBANK))); htmlBodyPart.setHeader("Content-Transfer-Encoding", "base64"); alternativePart.addBodyPart(htmlBodyPart); // inline image MimeBodyPart imageBodyPart = new MimeBodyPart(); imageBodyPart.setDataHandler(new DataHandler(new FileDataSource("/sdcard/sample.gif"))); imageBodyPart.setFileName(MimeUtility.encodeWord("sample.gif")); imageBodyPart.setDisposition("inline"); // inline指定しておく imageBodyPart.setContentID("12345@12345"); // インライン画像を指定 relatedPart.addBodyPart(imageBodyPart); // set related msg.setContent(relatedPart); sendGmail(msg, session); } catch (MessagingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * * @return */ private Session createSendGMailSession() { Properties props = new Properties(); props.put("mail.smtp.host", "smtp.gmail.com"); // SMTPサーバ名 props.put("mail.host", "smtp.gmail.com"); // 接続するホスト名 props.put("mail.smtp.port", "587"); // SMTPサーバポート props.put("mail.smtp.auth", "true"); // smtp auth props.put("mail.smtp.starttls.enable", "true"); // STTLS // セッション Session session = Session.getDefaultInstance(props); session.setDebug(true); return session; } /** * * @param msg * @param session * @throws MessagingException */ private void sendGmail(MimeMessage msg, Session session) throws MessagingException { Transport t = session.getTransport("smtp"); t.connect("gmailユーザアカウント", "gmailパスワード"); t.sendMessage(msg, msg.getAllRecipients()); } } /** * 特定のコードをバイナリ絵文字に変換するDataSource */ class TextBinaryDataSource implements DataSource { private byte[] text; private byte[] convertedText; private String charset; private String subType; private Emoji.Mode emojiMode; public TextBinaryDataSource(byte[] text, String charset, String subType, Emoji.Mode emojiMode) throws IOException { this.text = text; this.charset = charset; this.subType = subType; this.emojiMode = emojiMode; initialize(); } public TextBinaryDataSource(String text, String charset, String subType, Emoji.Mode emojiMode) throws IOException { this(text.getBytes(), charset, subType, emojiMode); } public byte[] getPlainText() { return text; } private void initialize() throws IOException { InputStream dec_in = new ByteArrayInputStream(text); ByteArrayOutputStream bo = null; OutputStream os = null; try { bo = new ByteArrayOutputStream(); os = EmojiUtility.decode(bo, charset, emojiMode); byte[] buf = new byte[2048]; int len; while ((len = dec_in.read(buf)) != -1) { os.write(buf, 0, len); } } finally { if (os != null) { os.close(); } } convertedText = bo.toByteArray(); } @Override public InputStream getInputStream() throws IOException { InputStream is = new ByteArrayInputStream(convertedText); return is; } @Override public OutputStream getOutputStream() throws IOException { throw new UnknownServiceException(); } @Override public String getContentType() { return "text/" + subType + "; charset=" + charset; } @Override public String getName() { return ""; } }
デコメの部分の添付ファイルはSDカードから取得するようにしました。
(assetsから読み込む場合、「new InputStreamReader(context.getAssets().open("sample.gif"))」のようにして取得するため、InputStreamを設定できるDataSourceが必要らしいので・・・)
- サンプルコード
また、shift-jis、utf-8のコードで、ソフトバンク携帯アドレス(XXXX@softbank.ne.jp)、iPhone(i.softbank.jp)向けに送信してみたところ、どちらも送信はできましたが以下のような違いがありました。ただ、ちゃんと検証したわけではないので、目安程度にしてください。
(どちらのメールも、iPhone宛てに送信しました。)
phonegap-pluginsを使っためも
phonegap-pluginsを使っためも。
PhoneGap(Android版)のJavascript上からIntentを呼び出す必要があったので。。。
参考サイト
PhoneGapプラグインの作り方
http://wiki.phonegap.com/w/page/36753494/How-to-Create-a-PhoneGap-Plugin-for-Android
phonegap-plugins作者のブログ
http://borismus.com/android-phonegap-plugins/
phonegap-plugins github
https://github.com/borismus/phonegap-plugins
phonegapの動作環境を準備
http://www.phonegap.com/start#androidを参考に環境を構築。
phonegap-pluginを適用
phonegap-plugin ダウンロード
phonegap-pluginをダウンロードします。
https://github.com/borismus/phonegap-plugins
今回はIntentの機能を追加するため、ダウンロードしたファイルのうち、「phonegap-plugins / Android / WebIntent」を利用します。
pluginの追加
com.borismus.webintentというパッケージ名で、ダウンロードしたファイル「phonegap-plugins / Android / WebIntent/WebIntent.java」を追加します。
このWebIntent.javaがPhoneGapのプラグインの実装ファイルになります。
また、PhoneGapからプラグインを利用するとき、javascriptからクラス名を指定して読み込みを行うため、パッケージ名が異なるとエラーとなるのでご注意ください。
javascriptファイルの追加
「asset/www」あたりにダウンロードしたファイル「phonegap-plugins / Android / WebIntent/webintent.js」を追加します。
このwebintent.jsの中で、PhoneGapにプラグインの追加を行っています。
実行例
index.htmlで実行してみます。
- asset/www/index.html
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> PhoneGap </title> <script type="text/javascript" charset="utf-8" src="phonegap.js"></script> <script type="text/javascript" charset="utf-8" src="webintent.js"></script> <script type="text/javascript" chareset="utf-8"> // Wait for PhoneGap to load // function onLoad() { document.addEventListener("deviceready", onDeviceReady, false); } function onDeviceReady() { open_maps(); //open_mail(); } function open_maps() { var address = 'hogehoge'; window.plugins.webintent.startActivity({ action: WebIntent.ACTION_VIEW, url: 'geo:0,0?q=' + address}, function() {}, function() {alert('Failed to open URL via Android Intent')} ); } function open_mail() { var extras = {}; extras[WebIntent.EXTRA_SUBJECT] = "サブジェクト"; extras[WebIntent.EXTRA_TEXT] = "テキスト"; window.plugins.webintent.startActivity({ action: WebIntent.ACTION_SEND, type: 'text/plain', extras: extras }, function() {}, function() {alert('Failed to send email via Android Intent');}); } </script> </head> <body onload="onLoad()"> <h2> plugin test </h2> </body> </html>
アプリの起動後に、google mapsやメーラを起動できると思います。
注意点として、devicereadyイベントの後でないと、プラグインが有効にならないという点があります。
また、メーラ起動時に添付ファイルを指定する方法がないか試してみましたが、無理っぽい気がします。
プラグインを改造するのが手っ取り早いかも・・・。
mysql-5.5.9インストールめも
mysql-5.5.9のインストールめも。cmakeが使いづらかい。
cmake
http://www.cmake.org/
インストール手順
cmake install
依存ライブラリがいろいろと必要になるかも。
自分のときは
gcc-c++.x86_64
ncurses.x86_64
ncurses-devel.x86_64
が足りませんでした・・・。
- cmake インストール
cd /usr/local/src wget http://www.cmake.org/files/v2.8/cmake-2.8.4.tar.gz tar xvfz cmake-2.8.4.tar.gz cd cmake-2.8.4 ./configure make su make install exit
mysql install
- ユーザ、グループの追加
su - groupadd mysql useradd -g mysql -d /home/mysql mysql exit
- インストール
mysql downloadページからGAのバージョンをダウンロードしてください。
cd /usr/local/src wget http://www.mysql.com/get/Downloads/MySQL-5.5/mysql-5.5.9.tar.gz/from/http://ftp.jaist.ac.jp/pub/mysql/ tar xvfz mysql-5.5.9.tar.gz cd mysql-5.5.9 su # cmake cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_EXTRA_CHARSETS=all -DWITH_PIC=1 # make make # install make install # ユーザ、グループ設定 cd /usr/local/mysql chown -R mysql:mysql /usr/local/mysql
-
- cmakeのときに以下のオプションで構築していましたが、起動時にエラーが発生したため、上記のオプションに変更しました。
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci
-
- cmakeをやり直すときは、以下の操作が必要です。
rm CMakeCache.txt make clean
rootユーザのまま作業
- DB初期化、設定
# DB初期化 cd /usr/local/mysql ./scripts/mysql_install_db --basedir=/usr/local/mysql --user=mysql # 設定ファイル(my.cnf) cp -p /usr/local/mysql/support-files/my-medium.cnf /usr/local/mysql/data/my.cnf
mysql_install_db はスクリプトを実行したディレクトリ配下のdataディレクトリにmysql権限テーブルのインストールを行うようなので、「/usr/local/mysql/scripts」で作業を行わないように注意してください。
- 起動設定
# 起動ファイル cp -p /usr/local/mysql/support-files/mysql.server /etc/init.d/mysql # サービス登録 chkconfig --add mysql # 起動 /etc/init.d/mysql start # 接続確認 /usr/local/mysql/bin/mysql -u root mysql> quit
- rootユーザ設定
# rootユーザのパスワード設定 mysqladmin -u root password [パスワード] # 匿名ユーザ削除 /usr/local/mysql/bin/mysql -u root -p mysql> delete from mysql.user where password = ''; mysql> flush privileges
作業ユーザ追加
# PATH設定 cat >> ~/.bashrc MYSQL=/usr/local/mysql export PATH=$PATH:$MYSQL/bin export MANPATH="$MANPATH":$MYSQL/man # ctrl+D source ~/.bashrc # DB追加 mysql -u root -p mysql> create database testdb; # ユーザ追加 mysql> create user 'testuser'@'%' identified by 'testpassword'; mysql> grant all on testdb.* to testuser@'%'; mysql> quit # 接続確認 mysql -u testuser -p
以上です。