최근에 만드는 앱들은 모두 아들이 가지고 놀기 위한 앱들이다...


얼마전에 Glass Harp 연주 동영상을 같이 보고 있었는데, 아들이 "아빠, 이거 어디서 나는소리야??" 하고 관심을 보여서...


Glass Harp의 샘플을 직접 구해서.. 일일이 주파수를 추출해서 3,4,5 옥타브의 음원 세트를 만들었는데..


3옥타브는 휴대폰에서는 아무리 재생해도 잘 들리지가 않아서... 그냥 대충 만들어올렸는데..


올린지 하루만에 ㅠ.ㅠ 별점 1 ... 


역시 대충 만든앱은 누가 봐도 별점 1....


그래서 앱자체를 아예 제거해버리고 새로 만들어서 올렸다.


아이가 쓰기 쉽게... 한글도 넣고... 그동안 너무 무성의 했던듯.... 사실 아들이 아직 한글은 모르고 A,B,C를 더 잘알아서...


https://play.google.com/store/apps/details?id=com.hopeisagoodthing.glasspiano&feature=more_from_developer#?t=W251bGwsMSwxLDEwMiwiY29tLmhvcGVpc2Fnb29kdGhpbmcuZ2xhc3NwaWFubyJd





블로그 이미지

커뉴

이 세상에서 꿈 이상으로 확실한 것을, 인간은 가지고 있는 것일까?

,

요즘들어 한글과 숫자를 배우고 있는 4살 난 아들이, 모두의 게임을 좀 씩 하고 있는데, 뭔가 부족하고, 하고싶은 만큼 할수 없는것 같아서...


직접 A to Z 까지 찾기, 1부터 64까지 찾기, ㄱ 부터 하 찾기 어플을 세개 만들어서 가지고 놀수 있게 해줬다.


아주 간단한 앱이지만.. 만들다보니.. ㅠ.ㅠ 손댈게 여기 저기 많아서 벌써 각각 버전 패치를 몇번 발행했다.


사운드가 나와서 아들이 관심을 가지면서 하고 있긴한데..엄청 오래 걸리고 있다.. ㅎㅎㅎㅎ



1. A to Z

https://play.google.com/store/apps/details?id=com.hopeisagoodthing.atoz&feature=search_result#?t=W251bGwsMSwyLDEsImNvbS5ob3BlaXNhZ29vZHRoaW5nLmF0b3oiXQ..



2. 1 to 64

https://play.google.com/store/apps/details?id=com.hopeisagoodthing.onetosixtyfour&feature=search_result#?t=W251bGwsMSwyLDEsImNvbS5ob3BlaXNhZ29vZHRoaW5nLm9uZXRvc2l4dHlmb3VyIl0.



3. ㄱ to 하

https://play.google.com/store/apps/details?id=com.hopeisagoodthing.hangul&feature=search_result#?t=W251bGwsMSwyLDEsImNvbS5ob3BlaXNhZ29vZHRoaW5nLmhhbmd1bCJd




블로그 이미지

커뉴

이 세상에서 꿈 이상으로 확실한 것을, 인간은 가지고 있는 것일까?

,

안드로이드 어플을 개발하다보면, DataBase를 사용해야 하는 경우가 간혹있다. 우리가 많이 사용하는 전화번호부, 일정관리 어플, 메시지 프로그램들이 모두가 DB를 자체적으로 사용하고 있는 것들이다.


안드로이드는 프레임웍에서 SQLite를 사용하기 쉽게 제공하고 있다. 그래서 오늘은 정말 정말 정말, 간단한 진짜 아무런 기능이 아직 준비되어 있지 않은 기본적인 Provider의 기본중의 기본 클래스만 만들어서 공유하고자 한다. 이후 DB를 사용하거나, openFile(API 9 GingerBread 부터 지원)과 같은 API를 사용해서 기능을 붙여나가고 진짜 DB를 써야 할 이유가 있을때 적절한 설명과 예제를 공유하도록 하겠지만, 오늘은 정말 기 기초적인 Provider의 껍데기만을 만들어뒀다.


나중에 필요할일이 생기면 그대로 복사해서 필요한 것들 추가하고 바로 사용할수 있게함이 목적이다.


Provider를 만들기 위해서는 AndroidManifest.xml에 provider를 선언해줘야 한다, ContentProvider는 내부적으로 Binder Interface를 통해서 IPC를 지원하고 있다.그래서 Manifest File에 내가 제공하는 provider정보를 명세해주어야지 다른 어플에서도 내가 제공하는 Provider의 Authority로 DB를 접근할수 있게 할수있다.


1. AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hoepisagoodthing.simpleprovider"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="15" />

    <application android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme">
        
        <provider
            android:authorities="com.hopeisagoodthing.simpleprovider"
            android:enabled="true"
            android:exported="true"            
            android:label="Coolkim"
            android:name="com.hopeisagoodthing.simpleprovider.Provider"
            android:syncable="false" >
        </provider>

    </application>

</manifest>




2. Provider와 DataBaseHelper Class

package com.hopeisagoodthing.simpleprovider;

import java.io.File;
import java.io.FileNotFoundException;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.SparseArray;

public class Provider extends ContentProvider{
	private static final String TAG="Provider";
	private static final String authority = "com.hopeisagoodthing.simpleprovider";
	private SQLiteOpenHelper dbHelper;
	private SQLiteDatabase db;
	private UriMatcher uriMatcher;
	
	private interface Scheme{
		interface Table{
			static final String NAME="name";
			static final String ADDR="address";
			static final String INTEREST="interest";
		}
		interface Match{
			static final int NAME=1;
			static final int ADDR=2;
			static final int INTEREST=3;
		}		
	}	
	
	private static final SparseArray<Stringgt; tableMap = new SparseArraylt;Stringgt;();
	static{
		tableMap.put(Scheme.Match.NAME, Scheme.Table.NAME);
		tableMap.put(Scheme.Match.ADDR, Scheme.Table.ADDR);
		tableMap.put(Scheme.Match.INTEREST, Scheme.Table.INTEREST);		
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		int 	match 	= uriMatcher.match(uri);
		switch(match){
		case Scheme.Match.NAME:
			break;
		
		case Scheme.Match.ADDR:
			break;
			
		case Scheme.Match.INTEREST:
			break;
			
		default:
			throw new UnsupportedOperationException();			
		}
		
		return db.delete(tableMap.get(match), selection, selectionArgs);		
	}

	@Override
	public String getType(Uri uri) {
		int 	match 	= uriMatcher.match(uri);
		switch(match){
		case Scheme.Match.NAME:
			break;
		
		case Scheme.Match.ADDR:
			break;
			
		case Scheme.Match.INTEREST:
			break;
			
		default:
			throw new UnsupportedOperationException();	
		}
		return null;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		int 	match 	= uriMatcher.match(uri);
		switch(match){
		case Scheme.Match.NAME:
			break;
		
		case Scheme.Match.ADDR:
			break;
			
		case Scheme.Match.INTEREST:
			break;
			
		default:
			throw new UnsupportedOperationException();	
		}
		
		long rowId =  db.insert(tableMap.get(match), null, values);
		uri = ContentUris.withAppendedId(uri, rowId);
		getContext().getContentResolver().notifyChange(uri, null);
		return uri;
	}

	@Override
	public boolean onCreate() {
		dbHelper 	= new SimpleDatabaseHelper(getContext());
        db 		= dbHelper.getWritableDatabase();
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        uriMatcher.addURI(authority, Scheme.Table.NAME, Scheme.Match.NAME);       
        uriMatcher.addURI(authority, Scheme.Table.ADDR,Scheme.Match.ADDR); 
        uriMatcher.addURI(authority, Scheme.Table.INTEREST, Scheme.Match.INTEREST);
       
		return true;		
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		int 	match 	= uriMatcher.match(uri);
		switch(match){
		case Scheme.Match.NAME:
			break;
		case Scheme.Match.ADDR:
			break;
		case Scheme.Match.INTEREST:
			break;
			
		default:
			throw new UnsupportedOperationException();	
		}
		
		return db.query(tableMap.get(match), projection, selection, selectionArgs, null, null, sortOrder);
		
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		int 	match 	= uriMatcher.match(uri);
		switch(match){
		
		case Scheme.Match.NAME:
			break;
		
		case Scheme.Match.ADDR:
			break;
		
		case Scheme.Match.INTEREST:
			break;
			
		default:
			throw new UnsupportedOperationException();	
		}
		
		
		return db.update(tableMap.get(match), values, selection,selectionArgs);
	}
	
	@Override
	public ParcelFileDescriptor openFile (Uri uri,String mode){
		ParcelFileDescriptor fd = null;
		
		String filename = uri.getQueryParameter("filename");
		if(filename == null || filename.isEmpty()){
			throw new UnsupportedOperationException();
		}
		File dir = getContext().getFilesDir();
		if(dir.exists() == false){
			dir.mkdir();
		}
		String filepath = dir.getAbsolutePath()+ "/" +filename;
	    try {
			fd = ParcelFileDescriptor.open(new File(filepath), ParcelFileDescriptor.MODE_CREATE|ParcelFileDescriptor.MODE_READ_WRITE);
		} catch (FileNotFoundException e) {
			Log.e(TAG,"Unable to open file "+ filepath);
		}		
		return fd;		
	}
	
	@Override
	public int bulkInsert(Uri uri, ContentValues[] values) {
		long 	rowId 		= -1;
		int 	count = 0;
		int 	match 	= uriMatcher.match(uri);
			
		db.beginTransaction();
					
		switch(match){
		
		case Scheme.Match.NAME:
			break;
		
		case Scheme.Match.ADDR:
			break;
		
		case Scheme.Match.INTEREST:
			break;
		
		default:
			throw new UnsupportedOperationException();	
		}
		
		for(ContentValues value:values){
			rowId = db.insert(tableMap.get(match), null, value);
			if(rowId > 0 ){
				count++;
			}
		}
		
		db.setTransactionSuccessful();			
		
		db.endTransaction();
		uri = ContentUris.withAppendedId(uri, rowId);
		getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}
	
	private class SimpleDatabaseHelper extends SQLiteOpenHelper {	
		private static final String DATABASE_NAME = "simple.db";
		private static final int DATABASE_VERSION = 1;
		private static final String NAME_TABLE_CREATE = "CREATE TABLE "
				+ "name" + " (" 
				+ "_id" + " INTEGER PRIMARY KEY AUTOINCREMENT," 
				+ "name" + " TEXT "+");";
		
		private static final String ADDRESS_TABLE_CREATE = "CREATE TABLE "
				+ "address" + " (" 
				+ "_id" + " INTEGER PRIMARY KEY AUTOINCREMENT," 
				+ "address" + " TEXT ," 
				+ "name_id" + " INTEGER " + ");";
		
		private static final String INTEREST_TABLE_CREATE = "CREATE TABLE "
				+ "interest" + " (" 
				+ "_id" + " INTEGER PRIMARY KEY AUTOINCREMENT,"				
				+ "interest" + " TEXT ," 
				+ "name_id" + " INTEGER " + ");";
		
		protected SimpleDatabaseHelper(Context context) {
			super(context, DATABASE_NAME, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			try {				
				db.execSQL(NAME_TABLE_CREATE);	
				db.execSQL(ADDRESS_TABLE_CREATE);
				db.execSQL(INTEREST_TABLE_CREATE);
			} catch (Exception e) {
				Log.e(TAG, "onCreate: Exception - " + e.getMessage());
			}
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		}
	}
}

블로그 이미지

커뉴

이 세상에서 꿈 이상으로 확실한 것을, 인간은 가지고 있는 것일까?

,

자바 표준으로 URL class등을 사용하여 네트워크 리소스를 사용할수도 있지만, 각종 설정들을 조절하고 더 사용하기 쉬운방법이 있는데, 그것이 바로 HttpClient Class를 사용하는 방법이다.


GET, POST 메소드등을 빈번하게 구분해서 사용하고, 각종 http 네트워크 리소스들을 사용자화 해서 개발할 필요가 있다면, URL class를 사용하는 것도 좋지만, HttpClient를 사용하는 방법도 알아두는 것도 권할만하다.


참고로, 내가 참여하고 있는 프로젝트는 HttpClient Class만을 사용하여 각종 데이터 싱크등을 구현하였다.


사용하는 법 자체는 쉽다.


package com.hopeisagoodthing.samplehttpclient;

import java.io.IOException;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {
	private static final String TAG = "MainActivity";
	private static TextView textView = null;
	private static Handler handler = new Handler(){
		public void handleMessage(final Message msg) {			
			textView.setText(Integer.toString((Integer) msg.obj));
		};		
	};

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);	
		textView = (TextView) findViewById(R.id.result_text);
		Thread thread = new Thread(mRunnable);
		thread.start();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		
		return true;
	}	
	
	private Runnable mRunnable = new Runnable() {
		@Override
		public void run() {
			try {
				final String url = "http://coolkim.tistory.com";
				final HttpGet get = new HttpGet(url);
				final HttpClient httpClient = getHttpClient();
				final HttpResponse httpResponse = httpClient.execute(get);
				handleResponse(httpResponse);
			} catch (ClientProtocolException e) {
				Log.e(TAG, "ERROR : ClientProtocolException");

			} catch (IOException e) {
				Log.e(TAG, "ERROR :  IOException");
			}

		}
	};

	private HttpClient getHttpClient() {
		HttpClient httpClient = null;
		try {
			final HttpParams params = new BasicHttpParams();

			// PARAMS
			HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
			HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
			HttpConnectionParams.setConnectionTimeout(params, 1000 * 30); // 30s timeout.
			HttpConnectionParams.setSoTimeout(params, 1000 * 30); // 30s timeout.
			HttpConnectionParams.setSocketBufferSize(params, 512 * 1024);
			HttpConnectionParams.setStaleCheckingEnabled(params, false);
			HttpClientParams.setRedirecting(params, false);
			params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);

			// SCHEME
			final SchemeRegistry registry = new SchemeRegistry();
			registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
			registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

			final ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

			httpClient = new DefaultHttpClient(ccm, params);
		} catch (Exception e) {
			httpClient = new DefaultHttpClient();
		}

		return httpClient;
	}
	
	private void handleResponse(final HttpResponse httpResponse) {
		final int status = httpResponse.getStatusLine().getStatusCode();
		
		try {			
			Message msg = handler.obtainMessage();
			msg.obj = status;
			handler.sendMessage(msg);			
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}	
}


블로그 이미지

커뉴

이 세상에서 꿈 이상으로 확실한 것을, 인간은 가지고 있는 것일까?

,

특정한 폴더들의 파일 변경을 모니터링을 해야 하는 경우가 있다면, Android에서는 File Observer를 사용할수 있다.


단말이 켜져있는 상태에서만 해당 변경에 대한 이벤트르 전달 받을수 있기 때문에, 단말이 꺼져있거나, 모니터를 하지 않다가 다시 모니터를 시작할 경우에는, 


모니터를 하고 있단 디렉토리의 변경사항을 따로 추적 관리하는 방법도 준비되어야 한다. (해쉬값을 뽑아둔다던가... 하는 방법)



package com.hopeisagoodthing.fileobserver;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
import android.util.Log;

public class FileObserverActivity extends Activity {
	private FileObserver observer;
	private static final String pathToWatch = Environment.getExternalStorageDirectory().toString();
	private static final String TAG = "FileObserverActivity";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		observer = new FileObserver(pathToWatch) {

			@Override
			public void onEvent(int event, String path) {

				Log.d(TAG, "File Changed --> Path = " + pathToWatch +"/"+ path);
				Log.d(TAG, "File Changed --> Event = " + event);
			}
		};
		observer.startWatching();
	}
}
블로그 이미지

커뉴

이 세상에서 꿈 이상으로 확실한 것을, 인간은 가지고 있는 것일까?

,