メートルとヤードの変換

1m = 1.0936133yards
1yard = 0.9144m

なので、相互の変換をするクラス

public class Converter {
	
	private Converter() {};

	static final double ONE_YARD = 1.0936133; // 1m = 1.0936133yards
	static final double ONE_METER = 0.9144;   // 1yard = 0.9144m

	public static double ConvertToYards(double meter) {
		return meter * ONE_YARD;
	}

	public static double ConvertToMeters(double yard) {
		return yard * ONE_METER;
	}
}

AndroidHttpClientのサンプルを書いた

今更ではありますが、AndroidHttpClientのコードを書いたので、メモ程度の書き残しておきます。
AndroidHttpClientはLevel 8 (Android 2.2 Froyo)から利用できます。2.1以前のAndroidには対応しませんので、DefaultHttpClientなどを利用する事になるかと思います。


AndroidHttpClient | Android Developers
http://developer.android.com/intl/ja/reference/android/net/http/AndroidHttpClient.html


下のソースは、AndroidHttpClientを利用して、Jsonデータを画面に出力するまでを書きました。
利用方法を見る程度なら、Activityを見れば十分です。

利用方法を見たけど、何がAndroidに最適化しているんだろうか???中身とか見てないので、ちょっと謎。


ソースはすべて、Githubに置いておきます。
https://github.com/sharakova/AndroidHttpClientSample

  • Activity

AndroidHttpClientは、メインのスレッドで実行できないようなので、AsyncTaskを利用して実行しています。

package jp.sharakova.android.httpclient;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import android.app.Activity;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.TextView;

public class TestActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.main);
        final TextView text = (TextView)findViewById(R.id.text);
        
        new AsyncTask<Void, Void, String>() {

		@Override
		protected String doInBackground(Void... params) {
			try {
				// AndroidHttpClientを使ってみた (Android 2.2 Froyoから使えます)
				AndroidHttpClient client = AndroidHttpClient.newInstance("Android UserAgent");
				HttpResponse res = client.execute(new HttpGet("https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=fuzzy%20monkey"));
					
				// HttpResponseのEntityデータをStringへ変換
		                BufferedReader reader = new BufferedReader(new InputStreamReader(res.getEntity().getContent(), "UTF-8"));
		                StringBuilder builder = new StringBuilder();
		                String line = null;
		                while ((line = reader.readLine()) != null) {
		        	        builder.append(line + "\n");
		                }
			        
		                return builder.toString();
			} catch (Exception e) {
				e.getStackTrace();
				return "";
			}
		}
			
		@Override
		protected void onPostExecute(String result) {
			// 画面に文字列を表示
			text.setText(result);
		}
        	
        }.execute();

    }
}
  • layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="loading" />

</LinearLayout>
  • AndroidManifest.xml

ネットに接続するために、permissionにandroid.permission.INTERNETを追加

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.sharakova.android.httpclient"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".TestActivity"
            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>

</manifest>

ImageDownloadServiceで、大きな画像を非同期でダウンロード

AndroidでWebにある画像をダウンロードするサービスクラス ImageDownloadService を作りました。ソースはgithubで管理してます。
https://github.com/sharakova/ImageDownloadService
MIT Lisenceで、ご自由におつかいください。

説明

  • DownloadManagerが利用できない、Android 2.1などで画像をダウンロードするプログラムです。
  • 大きな画像をバックグランドでダウンロードします。
  • ダウンロードした画像は、ギャラリーやコンテンツマネージャーでも表示されるようになります。
  • ダウンロードを開始すると、Notificationでダウンロードの状態を確認できます。
  • ダウンロードは、URLと保存したいタイトルをIntentで渡すだけです。
  • 保存する画像は、他のアプリからも閲覧できる場所に保存されます。
  • SDカードに画像を保存するため、SDカードが挿入されていない場合を想定してません。
  • Notificationには、デフォルトのiconを設定していますが、他のICONも設定可能です。

利用方法
Android マニフェストに必要となる設定

  • Serviceクラスを設定する jp.sharakova.android.service.ImageDownloadService
<service android:name="jp.sharakova.android.service.ImageDownloadService" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Activityから呼び出し

  • Actionに、ImageDownloadService.IMAGE_DOWNLOAD_SERVICE を設定する。
  • image_urlに、画像のURLを渡す
  • image_titleには、画像のタイトルを設定する
Intent intent = new Intent(ImageDownloadService.IMAGE_DOWNLOAD_SERVICE);
intent.setClass(getApplicationContext(), ImageDownloadService.class);
intent.putExtra("image_url", "画像URL");
intent.putExtra("image_title", "画像の名前");
startService(intent);

ImageDownloadService.java

package jp.sharakova.android.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import jp.sharakova.android.imagedownload.R;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.provider.MediaStore.Images.Media;
import android.util.Log;

public class ImageDownloadService extends Service {
	
    public final static String IMAGE_DOWNLOAD_SERVICE = "jp.sharakova.android.service.ImageDownloadService";
	private static ArrayList<String> downloadTask = new ArrayList<String>();
    private NotificationManager mNM;
    private String imageUrl; 
    private String imageTitle;
    private final IBinder mBinder = new LocalBinder();
    
    public class LocalBinder extends Binder {
    	ImageDownloadService getService() {
            return ImageDownloadService.this;
        }
    }
    
    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    }
    
    @Override
    public void onStart(Intent intent, int startId)
    {
    	super.onStart(intent, startId);
    	
    	// とりあえず、アクションもチェックする
        if (intent != null && IMAGE_DOWNLOAD_SERVICE.equals(intent.getAction())) {
        	
        	// URLとタイトルを設定する
        	imageUrl = intent.getStringExtra("image_url");
        	imageTitle = intent.getStringExtra("image_title");
        	
        	// ダウンロードタスクにaddする
        	downloadTask.add(imageUrl);

        	// ダウンロード開始するメッセージをタスクトレイで表示
        	showNotification(imageTitle, imageUrl);
        	
            Thread downloadThread = new Thread(null, new Runnable() {
                public void run() {
                	String path = imageUrl;
                	String title = imageTitle;
                	String message = null;
                	try {
                		// HTTPで画像をダウンロード
                		DefaultHttpClient httpClient = new DefaultHttpClient();
        	        	HttpResponse response = httpClient.execute(new HttpGet(path));
        	        	InputStream stream = response.getEntity().getContent();
        	        	ContentResolver contentResolver = getContentResolver();
        	        	
        	        	// ファイルへ保存
        	        	saveImage(contentResolver, getFileName(path), imageTitle, stream);
        	        	
        	        	// タスクトレイで表示するメッセージ
        	        	message = title + " download complete.";
        	        	
        	        	// コンテンツマネージャーなどのアプリでは、キャッシュをクリアしてやらないと
        	        	// すぐに、画像が表示されないので、メディアマウントをしてやる。
        	        	sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
                                Uri.parse("file://" + Environment.getExternalStorageDirectory()))); 
                	} catch (Exception e) {
                		message = "Error.";
                	} finally {
                		// ダウンロードのタスクから削除
       	        		downloadTask.remove(path);
                		mNM.cancel(path.hashCode());
                		// メッセージをタスクトレイに表示
                		showNotification(message, path);
                	}
                }
            }, "ImageDownloadService");
            downloadThread.start();
        }
    }

    @Override
    public void onDestroy() {
        mNM.cancelAll();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    private void showNotification(String message, String filePath) {
        Notification notification = new Notification(R.drawable.icon, message, System.currentTimeMillis());
        
        PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, null, 0);
        notification.setLatestEventInfo(this, "download picture.", message, contentIntent);
        mNM.notify(filePath.hashCode(), notification);
    }
    
    private static String saveImage(ContentResolver contentResolver, String filename, String title, InputStream inputStream) {
		OutputStream outputStream = null;
		File file = null;
		try {
			makeDownloadDir();
			file = new File(getDownloadPath(), filename);
			outputStream = new FileOutputStream(file);
			int DEFAULT_BUFFER_SIZE = 1024 * 4;
			  byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
			  int n = 0;
			  while (-1 != (n = inputStream.read(buffer))) {
				  outputStream.write(buffer, 0, n);
			  }
			  inputStream.close();
			  outputStream.close();
		} catch (Exception e) {
			Log.w("save", e);
			if (file != null) {
				file.delete();
			}
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Throwable t) {
					Log.w("save", "finally");
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					Log.w("save", "finally");
				}
			}
		}
		
		// ContentResolver にファイル情報の書き込み。
		// これやらないと、ギャラリーなどのアプリでダウンロードした画像が表示されないです。
		String filePath = getDownloadPath() + "/" + filename;
	        ContentValues values = new ContentValues(7);
	        values.put(Media.TITLE, title);  
	        values.put(Media.DISPLAY_NAME, filename);  
	        values.put(Media.DATE_TAKEN, System.currentTimeMillis());  
	        values.put(Media.MIME_TYPE, getMimeType(filename));
	        values.put(Media.DATA, filePath);
	        contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
	    
		return filePath;
	}
    
	private static String getSdCardPath() {
		return Environment.getExternalStorageDirectory().toString();
	}

	private static String getDownloadPath() {
		// 別のフォルダを指定する場合は、下記のフォルダ名を変更してください。
		return getSdCardPath() + "/download";
	}
	
	private static void makeDownloadDir() {
		makeDir(new File(getDownloadPath()));
	}
	
	private static void makeDir(File dir) {
		if (!dir.exists()) {
			dir.mkdir();
		}		
	}

	private static String getFileName(String url) {
		int point = url.lastIndexOf("/");
	    if (point != -1) {
	    	return url.substring(point + 1, url.length());
	    } 
		int hash = url.hashCode();
		return String.valueOf(hash);
	}

	
	// mime-typeの判定が微妙です。すみません。
	private static String getMimeType(String filename) {
		int point = filename.lastIndexOf(".");
	    if (point != -1) {
	    	String extension = filename.substring(point + 1, filename.length());
	    	if(extension.equals("jpeg") || extension.equals("jpg")) {
	    		return "image/jpeg";
	    	}
	    	if(extension.equals("png")) {
	    		return "image/png";
	    	}
	    	if(extension.equals("gif")) {
	    		return "image/gif";
	    	}
	    } 
		return "image/jpeg";
	}
}


(実行サンプルソース
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="jp.sharakova.android.imagedownload"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="jp.sharakova.android.service.ImageDownloadSampleActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="jp.sharakova.android.service.ImageDownloadService" />
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

ImageDownloadSampleActivity

package jp.sharakova.android.service;

import jp.sharakova.android.imagedownload.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class ImageDownloadSampleActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // ダウンロードのOnClickで、画像のダウンロードを開始する
        findViewById(R.id.download_btn).setOnClickListener(downloadBtnListener);
    }
    
    OnClickListener downloadBtnListener = new OnClickListener() {
		@Override
		public void onClick(View arg0) {
                        Intent intent = new Intent(ImageDownloadService.IMAGE_DOWNLOAD_SERVICE);
                        intent.setClass(getApplicationContext(), ImageDownloadService.class);
                        intent.putExtra("image_url", "http://lh5.googleusercontent.com/-U_ZrYg86EMc/S9uxmpuT6hI/AAAAAAAACUs/-zOKvjJKH8E/s640/golf.png");
                        intent.putExtra("image_title", "golf");
                        startService(intent);
            
                        // Toastの表示
			Toast.makeText(getApplicationContext(), "download start", Toast.LENGTH_SHORT).show();
		}
	};
}

Androidで、ネットにある画像をImageViewで表示する(UrlImageView)

AndroidのImageViewで、インターネット上にある画像を表示する独自ImageViewを作成しました。
ソースはすべてgithubで管理してます。MIT Licenseで、自由にお使いください。
名前、UrlImageView。適当でごめんなさい。
https://github.com/sharakova/UrlImageView

利用方法とソースの内容も書きますが、若干違いがあるかも・・・。

このクラスの利点

  • 画像のURLを指定するだけで、ネットにある画像を非同期で取得、キャッシュ、表示を簡単に実装できます。
  • 本当に、ほとんど何も気にしなくても、利用できます。
  • layoutファイルで、ImageViewからこのクラス(UrlImageView)に変更して、setImageUrl(String url);を呼び出すだけです。
  • OnImageLoadListener のリスナ登録で、ネットから画像を読み込む前後で処理を実行できます。
  • 読み込んだ画像は、一時的にAndroid内にキャッシュし、2度目の表示では高速に読み込む事ができます。
  • サンプルソースのonDestroyで実行している ImageCache.deleteAll(getCacheDir()); で、Android内に保存したキャッシュを削除いたします。実行しなければ、アプリをアンインストールもしくはユーザの操作でキャッシュを削除するまでデータを保持します。


ちょっと内部的な話

  • 複数のUrlImageViewを利用されると、画像の読み込み順番がLIFOの方がなにかと良かったので、画像を読み込む優先順位を変更しました。
  • WorkerThreadを実装しました。スレッド数は5つ。
  • GridViewなどで利用した場合、ユーザ操作で見たい画像がある程度先に読まれるようになった。
  • 画像のキャッシュの圧縮率は90です。また、アニメーションGIFなどには対応してません。
  • 画像のキャッシュデータをファイルに書き出す際に若干遅いので、今後の課題です。
  • ファイルが存在しない場合(404 Not Found)とかの場合、処理が分けられないのも課題です。


利用方法
Android マニフェスト

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

layoutファイル

  • レイアウトファイルでは、ImageViewと同様の設定が可能。設定例。
<jp.sharakova.android.urlimageview.UrlImageView
    android:id="@+id/imageView"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    />

Activity サンプル

package jp.sharakova.android.urlimageview.sample;

import jp.sharakova.android.urlimageview.ImageCache;
import jp.sharakova.android.urlimageview.R;
import jp.sharakova.android.urlimageview.UrlImageView;
import jp.sharakova.android.urlimageview.UrlImageView.OnImageLoadListener;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

public class UrlImageViewSampleActivity extends Activity {	
    UrlImageView mImageView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);        
        mImageView = (UrlImageView)findViewById(R.id.imageView);
        mImageView.setImageUrl("http://k.yimg.jp/images/top/sp/logo.gif", imageLoadListener);
    }   

    @Override
    public void onDestroy() {
    	ImageCache.deleteAll(getCacheDir());
    	super.onDestroy();
    }

    final private OnImageLoadListener imageLoadListener = new OnImageLoadListener() {
		@Override
		public void onStart(String url) {
			Toast.makeText(getApplicationContext(), "start", Toast.LENGTH_SHORT).show();
		}
		@Override
		public void onComplete(String url) {
			Toast.makeText(getApplicationContext(), "end", Toast.LENGTH_SHORT).show();
		}
    };  
}


各ソースの説明

  • UrlImageView.java
    • ImageViewを拡張したViewクラス
  • Channel.java
    • WorkerThreadの生成
    • Requestキュー管理 (Request Queue) LIFOを実装
  • ImageCache.java
    • メモリで画像データをキャッシュ。
    • キャッシュディレクトリにファイルとして保存
    • キャッシュデータの読み出し
  • Request.java
    • 読み込む画像のURL
    • キャッシュディレクトリの保存
  • WorkerThread.java
    • ChannelからRequestを取得し、ネットから画像を読み込む
    • Threadクラス
    • ステータスの保持


UrlImageViewクラス

package jp.sharakova.android.urlimageview;

import java.lang.ref.SoftReference;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.util.AttributeSet;
import android.widget.ImageView;


public class UrlImageView extends ImageView {
	private final Context context;
	
	private Request request;
	private String url;
	private final Handler handler = new Handler();
	private OnImageLoadListener listener = new OnImageLoadListener() {
		public void onStart(String url) {
		}
		public void onComplete(String url) {
		}		
	};
	
    public static interface OnImageLoadListener {
        public void onStart(String url);
        public void onComplete(String url);
    }
	
	private final Runnable threadRunnable = new Runnable() {
		public void run() {
			handler.post(imageLoadRunnable);
		}
	};
	
	private final Runnable imageLoadRunnable = new Runnable(){
		public void run(){
			setImageLocalCache();
		}
	};
	
	private boolean setImageLocalCache() {
		SoftReference<Bitmap> image = ImageCache.getImage(context.getCacheDir(),url);
		if(image != null && image.get() != null) {
			setImageBitmap(image.get());
			listener.onComplete(url);
			return true;
		}
		return false;
	}
	
	public UrlImageView(Context context) {
		super(context);
		this.context = context;
	}
	
	public UrlImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;
	}

	public UrlImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		this.context = context;
	}
	
	public void setOnImageLoadListener(OnImageLoadListener listener) {
		this.listener = listener;
	}
		
	public void setImageUrl(String url, OnImageLoadListener listener) {
		setOnImageLoadListener(listener);
		setImageUrl(url);
	}
	
	public void setImageUrl(String url) {
		this.url = url;
		request = new Request(url, context.getCacheDir(), threadRunnable);
		if(setImageLocalCache()){
			return;
		}
		
		listener.onStart(url);
		Channel.getInstance().putRequest(request);
	}
}


Channelクラス

package jp.sharakova.android.urlimageview;

import java.util.LinkedList;

public class Channel {
	private static Channel instance;
	private static final int MAX_THREAD = 5;
	private final LinkedList<Request> requestQueue = new LinkedList<Request>();
	private final WorkerThread[] threadPool;
	
	private Channel() {
		threadPool = new WorkerThread[MAX_THREAD];
		for (int i = 0; i < threadPool.length; i++) {
			threadPool[i] = new WorkerThread("Worker-" + i, this);
		}
	}
	
	public static Channel getInstance() {
		if(instance == null) {
			instance = new Channel();
			instance.startWorkers();
		}
		return instance;
	}
	
	public synchronized void removeQueueAll() {
		requestQueue.clear();
	}
	
	public void startWorkers() {
		for (WorkerThread thread : threadPool) {
			thread.start();
		}
	}
	
	public void stopWorkers() {
		for (WorkerThread thread : threadPool) {
			thread.stop();
		}		
	}
	
	public synchronized void putRequest(Request request) {
		requestQueue.addFirst(request);
		notifyAll();
	}
	
	public synchronized Request takeRequest() {
		while (requestQueue.size() <= 0) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}		
		return requestQueue.poll();
	}
}

ImageCacheクラス

package jp.sharakova.android.urlimageview;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.HashMap;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

public class ImageCache {
	
	private static HashMap<String,SoftReference<Bitmap>> cache = new HashMap<String,SoftReference<Bitmap>>();
	
	private static String getFileName(String url) {
		int hash = url.hashCode();
		return String.valueOf(hash);
	}
		
	public static void saveBitmap(File cacheDir, String url, Bitmap bitmap) {
		cache.put(url, new SoftReference<Bitmap>(bitmap));
		
		String fileName = getFileName(url);
		File localFile = new File(cacheDir, fileName);
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(localFile);
			bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
		} catch (Exception e) {
			e.printStackTrace();
		} catch (OutOfMemoryError e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e1) {
					Log.w("save", "finally");
				}
			}
		}
	}
	
	public static SoftReference<Bitmap> getImage(File cacheDir, String url) {
		
		SoftReference<Bitmap> ref = cache.get(url);
		if(ref != null && ref.get() != null) {
			return ref;
		}
		
		String fileName = getFileName(url);
		File localFile = new File(cacheDir, fileName);
		SoftReference<Bitmap> bitmap = null;
		try {
			bitmap = new SoftReference<Bitmap>(BitmapFactory.decodeFile(localFile.getPath()));
		} catch (Exception e) {
			e.printStackTrace();
		} catch (OutOfMemoryError e) {
			e.printStackTrace();
		}
		return bitmap;
	}
	
	public static void deleteAll(File cacheDir) {
		if (!cacheDir.isDirectory()){
			return;
		}
		File[] files = cacheDir.listFiles();
		for (File file : files) {
			if (file.isFile()){
				file.delete();
			}			
		}
	}
}


Requestクラス

package jp.sharakova.android.urlimageview;

import java.io.File;


public class Request {
	private final String url;
	private final File cacheDir;
	private final Runnable runnable;
	private int status = Status.WAIT;
		
    public interface Status {
        int WAIT = 0;
        int LOADING = 1;
        int LOADED = 2;
    }
    	
	public Request(String url, File cacheDir, Runnable runnable) {
		this.url = url;
		this.cacheDir = cacheDir;
		this.runnable = runnable;
	}
	
	public synchronized void setStatus(int status) {
		this.status = status;
	}
	
	public int getStatus() {
		return status;
	}
	
	public String getUrl() {
		return url;
	}
	
	public File getCacheDir() {
		return cacheDir;
	}
	
	public Runnable getRunnable() {
		return (runnable != null)? runnable : getDefaultRunnable();
	}
	
	private Runnable getDefaultRunnable() {
		return new Runnable() {
			public void run() {
			}
		};
	}
}


WorkerThreadクラス

package jp.sharakova.android.urlimageview;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class WorkerThread extends Thread {
	private final Channel channel;

	public WorkerThread(String name, Channel channel) {
		super(name);
		this.channel = channel;
	}
	
	public void run() {
		while (true) {
			Request request = channel.takeRequest();
			request.setStatus(Request.Status.LOADING);
			SoftReference<Bitmap> image = ImageCache.getImage(request.getCacheDir(), request.getUrl());
			if(image == null || image.get() == null) {
				image = getImage(request.getUrl());
				if(image != null && image.get() != null) {
					ImageCache.saveBitmap(request.getCacheDir(), request.getUrl(), image.get());
				}
			}
            request.setStatus(Request.Status.LOADED);
        	request.getRunnable().run();
		}
	}
	
    private SoftReference<Bitmap> getImage(String url) {
    	try {
	   		byte[] byteArray = getByteArrayFromURL(url);
	    	return new SoftReference<Bitmap>(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length));
    	} catch (Exception e) {
    		e.printStackTrace();
    		return null;
    	} catch (OutOfMemoryError e) {
    		e.printStackTrace();
    		return null;
    	}
    }
    
    private byte[] getByteArrayFromURL(String strUrl) {
        byte[] byteArray = new byte[1024];
        HttpURLConnection con = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        int size = 0;
        try {
            URL url = new URL(strUrl);
            con = (HttpURLConnection) url.openConnection();
            con.setUseCaches(true);
            con.setRequestMethod("GET");
            con.connect();
            in = con.getInputStream();

            out = new ByteArrayOutputStream();
            while ((size = in.read(byteArray)) != -1) {
                out.write(byteArray, 0, size);
            }
            return out.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[0];
        } catch (OutOfMemoryError e) {
        	e.printStackTrace();
        	return new byte[0];
        } finally {
            try {
                if (con != null)
                    con.disconnect();
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Shibuya UX ”ユーザープロファイリング” 参加メモ

渋谷のWebの会社が集まってやっているShibuya UXに参加して来たので、メモ。

  • 内容

第5回 SHIBUYA UX MEETING ”ユーザープロファイリング”
http://kokucheese.com/event/index/14144/

facebook
http://www.facebook.com/pages/Shibuya-UX/137643802957319

  • 感想

UX勉強会は珍しくいろいろな人(ディレクター、デザイナ、エンジニア、もちろんUX担当者など)がいて面白い交流ができました。mixiとかほかの会社では、ユーザビリティテストとかアンケートとか力入れてる事が分かった。僕たちの予算の少なさが際立ったw
mixiも年に1回くらい大規模なユーザインタビューとかしてるそうです。また、月一回程度で、大学生だけとか、高校生だけとか、小さなクラスタ別でアンケートとっているそうです。う〜ん、やっぱり規模違うなぁ…

mixiのあたらしいビルにも行ってみたけど、渋谷駅から遠いよ…。

mixiの発表
16グループ 53名のインタビュー

背景
mixiをとりまく市場環境
ユーザの環境がかわってきている。
ユーザのコミュニケーション形態の変化
電話、ブログ、SNSSkype

動向を把握するため
重要ターゲットの再定義と明確化、戦略

1.定性調査
2.ユーザクラスタリング
3.定量分析
4.重要ターゲットの特定
5.HCDプロセスへの展開

インタービューで意識した
部分ではなく全体を理解する
できるだけ全体を理解する
ターゲットのみきわめ
属性クラスターから価値観ベースクラスターへの変化
価値観でユーザをクラスタ分け
価値観でくぎったサービスの構造を把握する
競合が提供している価値とユーザの価値観クラスタマッピング


調査設計
どんな手法を選択するのか
グループインタビュー
比較的幅広く大人数で、インタビューできる
デプスインタビュー
根掘りは掘りインタビュー
重要ターゲットの詳細化
リクルート範囲
網羅性
既存ユーザ
Nonユーザ
重要性
Nonユーザは人数厚め
ライトユーザは外す
人数が限られる
使ってる理由がライトな答えになりがち
きちんと使ってるユーザの方がいい
昔はヘビーでつかってくれているのはOK
1グループどれくらい
グループインタビュー
3人にした 1人あたりの情報量を取得しようとした。
情報量がすくなくならないように少人数にした。
調査会社の選定
モデレーターですべてが決まる
スキル、経験
サービス理解度
事前に面接しておくことが大事
調査の質がかわってくる
会場ができるだけ会社の近く
対象者のリクルート
どんなリクルート方法を選択するか
ネットモニタ
大量の候補者からえらぶ
機縁法
調査会社がもってくる
スクリーニング調査
使ってる頻度とかをみて選ぶ
自由回答をもうけて、テキスト量をみてたくさん書いている人を選ぶと発言量が違う。
インタビュー内容をせっけい
価値観の洞察
周囲の環境の情報を厚く
SNSに閉じない
画面を見せてもらう
途中の回から内容を再構成する
不要な部分を削除して重要ポイントに焦点をあてる


インタビュー後
意味のあるクラスタ
ライフステージ軸、コミュニケーション軸で、各クラスタで問題点を対応していく
Aug 10

Me:
課題

ユーザの画面をみるのは重要、しかし
調査会社は、個人を特定できる内容を見せさせたくない
Facebook,twitterの画面を見れると個人を特定できないようにする
Aug 10

Me:

mixiの調査費用
550万

440万くらいからあったけど、会場などで5

Titanium Studioをこの土日にためして思ったこと

うちの会社で作成したiPhoneアプリは、Titaniumでつくったんですが、今後僕がメンテナンスしていく予定になりそうなので、土日にちょっとだけTitaniumをいじってみた感想をつらつらと書きます。

そもそも、Appcelerator社のサンプルコードで、iPhoneのシュミレーターがおちる。タブを切り替えるたびに、ちょっと動作が不安定で、どうもちゃんと動作しないです。
KitchenSink (Githubで公開されているサンプル)
https://github.com/appcelerator/KitchenSink

また、Androidの実機にインストールしてみたんですが、Androidに関しては起動もしないです。僕の操作手順が悪かったのかも知れません。

また、ImageViewを外部のサーバから取得して表示するサンプルを作って動作させてみたが、iPhoneAndroidで見た目がかわりすぎる。結局どっちでも動作確認しないと意味なさそうじゃないですか…。

//
// create controls tab and root window
//
var win3 = Titanium.UI.createWindow({  
    title:'画像',
    backgroundColor:'#abc'
});
var tab3 = Titanium.UI.createTab({  
    icon:'KS_nav_ui.png',
    title:'golf画像',
    window:win3
});

var imageView = Titanium.UI.createImageView({
	image:'http://pic.prcm.jp/gazo/bCN/0xJwX5.jpeg',
	top:20,
	width:300,
	height:300
	
});
	
win3.add(imageView);

Titaniumのソースを一部抜粋


iPhone画面イメージ


Android画面イメージ

Andridの方は単純に表示領域のサイズを指定しても、画面が伸びてしまって全く使い物にならないじゃないか…。
単純なコードでこれだけ表示がかわってしまうなら、それぞれ独立したプログラムを書いた方が良さそうだよね。

Unity 3Dが起動しない

MacBook Air (Lion MacOS 10.7)を購入したが、AndroidiPhoneのゲーム作成したかったので、Unity 3Dをインストールしようとしたけど、起動しない。UnityはWindows7で試したことがあったけど、Macでの感じがわからず、Lionならではの問題なのか、僕のインストール方法とかそもそも別の要因があるのか、わからす。

Unity 3D
http://unity3d.com/


初回起動時のManual Activationを選んで、この画面で止まるんですけど…。うーん、どうしたらいいのか…


ごらんのとうおり、Finishボタンがdisableです。オフッ。
MacBook Airを買って、AndroidiPhoneアプリの開発環境を入れるだけで、すげー苦労してます。