월별 글 목록: 2012년 9월월

wget으로 java sdk 다운로드 하기..

wget은 리눅스/유닉스 기반에서 http, https 그리고 ftp상에 있는 컨텐츠를 쉽게 다운로드 해 주는 간단한 유틸리티 프로그램이다. 리눅스/유닉스에서는 기 설치되어 있거나, 쉽게 찾을 수 있지만.. 윈도우용으로는 http://users.ugent.be/~bpuype/wget/ 에서 다운로드 받아서 사용할 수 있다. 

보통, 리눅스/유닉스 기반의 서버들은 윈도와 다르게, GUI 환경을 사용하지 않는 것이 일반적이어서, 필요한 라이브러리나 어플리케이션을 다운로드하고 설치하기에는 wget이 정말 유용하다.

자 본론으로, java sdk를 wget으로 설치하는 방법에 대해서 알아보자..

1. java.sun.com에 접속해서 다운로드할 java 버전을 확인하고, 다운로드를 시작한다. 

 – 아래에서는 Linux x86버전의 sdk를 선택했습니다.

2. 다운로드를 시작하고, 다운로드 창에서 Get Link를 통해서 실제로 다운로드하는 바이너리의 위치를 복사한다.

3. wget으로 복사된 Link의 java sdk를 다운로드 한다. 

 – 실행

[xxxxx@justin ~]$ wget http://download.oracle.com/otn-pub/java/jdk/6u35-b10/jdk-6u35-linux-i586.bin?AuthParam=1348550746_bb8a619fac178661a342f75b3362ceea

 – 다운로드 결과

–2012-09-25 05:23:56–  http://download.oracle.com/otn-pub/java/jdk/6u35-b10/jdk-6u35-linux-i586.bin?AuthParam=1348550746_bb8a619fac178661a342f75b3362ceea
Resolving download.oracle.com… 207.109.73.49, 207.109.73.27
Connecting to download.oracle.com|207.109.73.49|:80… connected.
HTTP request sent, awaiting response… 200 OK
Length: 71758261 (68M) [application/octet-stream]
Saving to: “jdk-6u35-linux-i586.bin?AuthParam=1348550746_bb8a619fac178661a342f75b3362ceeaâ€

100%[===========================================================>] 71,758,261  9.95M/s   in 5.8s

2012-09-25 05:24:02 (11.8 MB/s) – “jdk-6u35-linux-i586.bin?AuthParam=1348550746_bb8a619fac178661a342f75b3362ceeaâ€

– 확인

[xxxxx@justin ~]$ ll
total 70080
-rw-rw-r– 1 ec2-user ec2-user 71758261 Sep  5 16:54 jdk-6u35-linux-i586.bin?AuthParam=1348550746_bb8a619fac178661a342f75b3362ceea

4. 일반적인 형태의 sdk로 파일이름 변경..

[xxxxx@justin ~]$ mv jdk-6u35-linux-i586.bin\?AuthParam\=1348550746_bb8a619fac178661a342f75b3362ceea jdk-6u35-linux-i586.bin
[xxxxx@justin ~]$ ll
total 70080
-rw-rw-r– 1 ec2-user ec2-user 71758261 Sep  5 16:54 jdk-6u35-linux-i586.bin

자, 위의 과정으로 그 동안 서버에서 java sdk 설치를 위해서 귀찮게, 다운로드 해서 scp나 filezila같은 툴로 업로드 했던 과정을 줄여줄 수 있겠다.. ^^

Android에서 Ant(build.xml)로 마켓 별 바이너리 빌드하기..

안드로이드 마켓은 애플 앱스토어와 다르게, 통신사나 회사에서 마켓을 만들어서 서비스 할 수 있다. 그래서 여러 안드로이드 마켓을 확인할 수 있다. 안드로이드 앱을
개발하면, Play 마켓 과 더불어 아마존 이나 티스토어 등의 여러 마켓에 런칭하는 경우가 있다. 개별 앱 마켓에 앱을 런칭하는 경우, 개별 마켓은 고유한 정책을 가지고 있고, 정책에 위배되면, 위배되는 내용을 수정해서 다시 요청(Play 마켓을 제외한 다른 마켓들은 거의 리뷰를 한다)해야 한다.

개별 마켓이 가지고 있는 정책중에 하나가, 앱에 대한 설명이나 앱의 주소가 적인 URL(http://play.google.com과 같은 주소는 다른 마켓에서 반려 사유가 된다)이 문제가 되는 경우가 있다. 그리고 개별 마켓에서 설치한 앱은 마켓 별로 다른 메세지를 보여줄 필요도 있다.

그래서, 앤트(Ant, build.xml)를 사용해서 마켓별로 앱을 패키징 하는 방법을 살펴보자.

1. 기본적인 구조

기본적인 패키지의 구조는 좌측과 같습니다..

2.3 버전부터 지원할 수 있는 형태로 프로젝트를 생성했습니다.

좌측의 패키지 구조를 살펴보면…

\libs-ext : 마켓의 분리를 위해서 필요한 Market.java를 폴더별로 가지고 있습니다.

build.properties : 릴리즈를 하기 위한 인증서의 위치와 정보를 저장하는 파일..

build.xml : 빌드 스크립트 파일..

local.properties : 로컬의 SDK 위치를 가리치는 파일..

project.properties : target project의 버전정보를 가지고 있는 파일..

 

참고로, 검은색으로 칠해버린 넘은 인증서 파일입니다..

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2. 코드 보기..

 

 

 

 

 

 

 

2.1 MainActivity.java

– 화면에 마켓의 정보를 보여주는 클래스..

package net.sjava.buildtest;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.TextView;

import net.sjava.buildtest.util.Constants;

public class MainActivity extends Activity {
	private TextView tv;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        tv = (TextView)findViewById(R.id.main_textview);
        tv.setText(Constants.getMarketString(MainActivity.this));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

2.2 Constants.java

– 마켓에 대한 정보를 넘겨주는 클래스..

package net.sjava.buildtest.util;

import android.content.Context;
import net.sjava.buildtest.R;
import net.sjava.buildtest.util.config.Debug;
import net.sjava.buildtest.util.config.Market;

public class Constants {
	public static final boolean DEBUG = Debug.DEBUG;
	public static final int MARKET = Market.MARKET;
	
	public static String getMarketString(Context c) {
		if(c == null)
			return "";
		
		if(MARKET == 0)
			return c.getString(R.string.mk_play);
					
		if(MARKET == 1)
			return c.getString(R.string.mk_amazon);
		
		if(MARKET == 2)
			return c.getString(R.string.mk_tstore);
		
		return "";
	}
}

 

2. 마켓의 정보를 구분하기..

마켓의 정보는 위의 폴더에서 자신의 상수값을 가지고 있게 된다..

따라서, 첨부한 소스를 확인해 보면, mk-play 폴더의 Market.java는 상수값으로 1을 가지고 있다..

 

3. 빌드 스크립트..

– 빌드 스크립트인 build.xml은 구글의 프레임웍이 빌드를 하기 위한 스크립트를 제공하기 때문에 매우 간단하게 작성을 할 수 있다.. 떙큐.. ^^, 아래의 스크립트는 dev/rc/live 같은 개발/라이브의 테스트/라이브 별도 바이너리를 릴리즈 하는 스크립트는 포함하지 않지만, 이것도 쉽게 추가할 수 있다.. 또한, 포함되어 있지 않지만, obfuscate를 위해서  proguard가 포함되어 있어, obfucate를 쉽게 지원한다.

 

3.1 build.properties

#
# Set the keystore properties for signing the application.
# 맞게 수정하시면 됩니다.. ^^
key.store=./libs-ext/xxxx
key.alias=xxxx

key.store.password=xxxx
key.alias.password=xxxx

3.2 local.properties

# location of the SDK. This is only used by Ant
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=C:\\Program Files\\android\\android-sdk

 

3.3 project.properties

# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

# Project target.
target=android-16

 

3.4 build.xml

– 아래의 빌드 스크립트의 buildtest-release-all 타켓을 실행해서, 마켓별 바이너리를 뽑아낼 수 있다.

 

<?xml version=”1.0″ encoding=”UTF-8″?>
<project name=”BuildTest” default=”help”>
<property file=”local.properties”/>
<property file=”build.properties” />
<property file=”project.properties” />

<!–property name=”config-target-dir-path” value=”net/sjava/buildtest/util/config” /–>
<property name=”market-target-dir-path” value=”net/sjava/buildtest/util/config” />

<property name=”project.name” value=”BuildTest”/>
<property name=”dist.dir” value=”./dist/” />
<property name=”release.file” value=”./bin/BuildTest-release.apk” />
<property name=”dist.backup” value=”c:/dev/dist” />

<path id=”android.antlibs”>
<fileset dir=”${sdk.dir}/tools/lib/” includes=”*.jar” />
</path>

    <!– 안드로이드 SDK가 제공하는 build.xml을 사용하기 위해서 import 한다.. –>
    <import file=”${sdk.dir}/tools/ant/build.xml”/>

    <!–AndroidManifest에서 xpath를 이용해서 선언된 versionName 값을 가져온다. –>
    <xpath input=”AndroidManifest.xml” expression=”/manifest/@android:versionName” output=”versionName” default=”1.0.0″/>

<tstamp>
<format property=”touch.time” pattern=”yyyyMMdd_HHmm” />
</tstamp>

<target name=”switch-market”>
<echo>Market Configuration file: ${market.filename}</echo>
<property name=”market-target-path” value=”${source.dir}/${market-target-dir-path}” />
<copy file=”${market.filename}” todir=”${market-target-path}” overwrite=”true” encoding=”utf-8″ />
</target>

<target name=”buildtest-backup”>
<copy todir=”${dist.backup}/${project.name}/${versionName}_${touch.time}”>
<fileset dir=”./dist”/>
</copy>
</target>

<target name=”buildtest-release-mk-play”>
<echo>Selected release configuration</echo>
<antcall target=”clean” />

<antcall target=”switch-market”>
<param name=”market.filename” value=”libs-ext/mk-play/Market.java” />
</antcall>

<property name=”proguard.enabled” value=”false”/>
<antcall target=”release” />
<copy file=”${release.file}” tofile=”${dist.dir}/${project.name}_live_play_${versionName}_${touch.time}.apk”/>
</target>

<target name=”buildtest-release-mk-amazon”>
<echo>Selected release configuration</echo>
<antcall target=”clean” />

<antcall target=”switch-market”>
<param name=”market.filename” value=”libs-ext/mk-amazon/Market.java” />
</antcall>

<property name=”proguard.enabled” value=”false”/>
<antcall target=”release” />
<copy file=”${release.file}” tofile=”${dist.dir}/${project.name}_live_amazon_${versionName}_${touch.time}.apk”/>
</target>

<target name=”buildtest-release-mk-tstore”>
<echo>Selected release configuration</echo>
<antcall target=”clean” />

<antcall target=”switch-market”>
<param name=”market.filename” value=”libs-ext/mk-tstore/Market.java” />
</antcall>

<property name=”proguard.enabled” value=”false”/>
<antcall target=”release” />
<copy file=”${release.file}” tofile=”${dist.dir}/${project.name}_live_tstore_${versionName}_${touch.time}.apk”/>
</target>

<target name=”buildtest-release-all”>
<echo>Start distribute binaries all of the market</echo>
<antcall target=”buildtest-release-mk-play” />
<antcall target=”buildtest-release-mk-amazon” />
<antcall target=”buildtest-release-mk-tstore” />
</target>

</project>

 

4. 결과

– 위의 코드를 통해서 마켓별로 필요한 URL이나, 리소스를 구분해서 보여줄 수 가 있겠습니다.. 아래의 화면은 만들어진 바이너리가 마켓별로 구분된 메세지를 보여주는지 테스트한 화면입니다..

 

아래의 명령을 통해서 각 바이너리를 에뮬레이터에 설치해서 테스트 하시면 됩니다.

> adb install BuildTest_live_amazon_1.0_20120918_2132.apk

> adb install BuildTest_live_tstore_1.0_20120918_2132.apk

 

 

5. 소스

cfile27.uf.1534F3375059F12A030C14.zip

 

 

 

Android 3.x(Honeycomb) 기반의 앱에서 SplitActionBar를 추가하기..

안드로이드 3.x의 코드네임은 Honeycomb으로 태블릿 전용이고, 그래서, 굳이 SplitActionBar를 사용할 필요가 없다. 

하지만, 3.x ~ 최신의 버전까지, 폰 UI와  태블릿 UI를 하나의 바이너리로 지원하다고 가정하면, SplitActionBar가 필요해 지게 마련입니다. 

간단하게 SplitActionBar라는 것이 무엇인지를 살펴보면… 

아래의 이미지에서 볼 수 있듯이, ActionBar의 option menu가 아래로 내려온 것을 말한다..

안드로이드에서 SplitActionBar를 사용하기 위해서는 4.0 ICS에서 부터 AndroidMenifest.xml에 추가해 주시면 됩니다.. 

To enable split action bar, simply add uiOptions=”splitActionBarWhenNarrow” to your <activity> or<application> manifest element.

SplitActionBar를 static하게 지원하는 코드가 4.0부터 추가되어 있으니, 3.x에서 지원을 하려면, 4.0에서 추가된 Window 객체의 setUiOptions()를 추가하면 됩니다.. 

간단하게 3.x 기반의 코드에 4.0 이상의 기능인 setUiOptions()을 사용하려면.. 

1. @TargetApi(14) 어노테이션을 통해서 4.0의 코드가 들어가 있다고 알려줘서, 컴파일 에러를 피하구요.. 

2. 버전 체크를 통해서 4.0 이상인 경우에만 Window 객체에 setUiOptions()으로 SplitActionBar를 만들어 주면 됩니다.. 

따라서, 아래의 코드를 보시면.. SplitActionBar를 만들어 주는 onLoadSplitActionBar() 에서는 Reflection을 이용해서 4.0 이상의 Window 클래스에서 지원하는 메쏘드인 setUiOptions()를 사용합니다. 

	@TargetApi(14)
	private void onLoadSplitActionBar() {
		Window w = getWindow();
		try {
			Method m = w.getClass().getMethod("setUiOptions", new Class[]{ Integer.TYPE });
			m.invoke(w, Integer.valueOf(Window.FEATURE_ACTION_BAR_OVERLAY));
		} catch(Exception e) {
			if(DEBUG)
				ExceptionUtil.getException(e);
		}
	}
	
	@Override
	public void onCreate(Bundle bundle) {
		super.onCreate(bundle);
		if(android.os.Build.VERSION.SDK_INT >= 14) {
			this.onLoadSplitActionBar();		
		}     
.......
   }

위의 코드로 Honeycomb버전까지 포함하는 SplitActionBar를 만들수가 있습니다.. ^^

LIFO 구조의 AsyncTask 디스패칭 구현하기..

안드로이드에서 우측의 GridView의 썸네일들을 API 서버에서 받아온다고 가정을 합니다. API 서버를 통해서 각 썸네일을 가져오려면, 보통은 Adapter의 getView()메쏘드에서 썸네일을 가져오는 AsyncTask를 순서대로 실행을 할 것입니다. 순서대로 FIFO의 구조로.. 그렇지만, 터치를 해서 두번 정도만 아래로 내려도, FIFO 구조가 좋지 않은 것은 자명한 일리겠죠.. 화면에 보이지도 않는 썸네일을 가져오는 쓰레드가 Background로 돌고 있겠죠.. 그래서, LIFO 구조로 AsyncTask를 디스패칭하는 넘을 만들어 봤습니다.. 

많은 썸네일을 보여주기 위해서는, 보통 페이징을 합니다. 썸네일을 보려고, 빠르게 터치해서 이동하다 보면, 쓰레드가 많이 생기게 마련이고, 이런경우에 쓰레드 응답문제로 앱이 죽을 수 있습니다.. 물론, THREAD_POOLING을 이용하는 EXECUTOR를 사용한다고 해도, AsyncTask의 과도한 큐잉으로 인해서 죽을 수도 있습니다.. 

그래서, AsyncTask를 중간에 큐잉(화면에 나오지 않을 넘들까지 큐잉할 필요는 없다고 생각함)을 하고, AsyncTask를 디스패칭(엄밀히 말하면, Executor에 넘겨주는..)을 담당하는 매니저를 두는 것으로 간단하게 해결을 합니다. 디스패칭을 담당하는 클래스는 기본적으로 DEQUE를 사용해서 LIFO구조의 QUEUE를 사용합니다.. 

참고로, 구현된 코드의 일부만 발췌를 했기 때문에, 아래의 코드는 컴파일이 안됩니다.. 필요한 부분은 적당한 수정과 추가가 필요합니다..

1. AsyncTaskVO.java

public class AsyncTaskVO {

public Context c;

public AsyncTask<String, Integer, Bitmap> task;

public static AsyncTaskVO newInstance(Context c, AsyncTask<String, Integer, Bitmap> task) {

AsyncTaskVO vo = new AsyncTaskVO();

vo.c = c;

vo.task = task;

return vo;

}

}

2. AsyncTaskDispatcher.java

import java.util.ArrayList;

import java.util.concurrent.LinkedBlockingDeque;

import java.util.concurrent.atomic.AtomicInteger;

public class AsyncTaskDispatcher {

static String CNAME = AsyncTaskDispatcher.class.getSimpleName();

public static AtomicInteger runningTask = new AtomicInteger(0);

static int MAX_SIZE = 72;

static int HALF_MAX_SIZE = 36;

static LinkedBlockingDeque<AsyncTaskVO> queue = new LinkedBlockingDeque<AsyncTaskVO>(MAX_SIZE); 

static ArrayList<Thread> consumers = new ArrayList<Thread>(3);

static {

consumers.add(new Thread(new Consumer(“1”)));

consumers.add(new Thread(new Consumer(“2”)));

consumers.add(new Thread(new Consumer(“3”)));

consumers.get(0).start();

consumers.get(1).start();

consumers.get(2).start();

}

public static void put(AsyncTaskVO e) {

if(e == null)

return;

try {

queue.addFirst(e);

if(queue.size() > HALF_MAX_SIZE) {

AsyncTaskVO taskVo =queue.takeLast();

if(DEBUG)

Logger.w(CNAME, “task deleted ~~” + taskVo);

taskVo = null;

return;

}

if(DEBUG)

Logger.w(CNAME, “task added~~”);

} catch (InterruptedException e1) {

if(DEBUG)

Logger.e(CNAME, ExceptionUtil.getException(e1));

}

}

public static class Consumer implements Runnable {

private String name = “”;

final Object lock = new Object();

public Consumer(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public void run() {

while(true) {

synchronized (lock) {

try {

// 실행중인 Task가 20개가 넘으면 wait을 한다. 

if(runningTask.intValue() > 20)

lock.wait(200);

if(DEBUG)

Logger.e(CNAME, “running task size = ” + runningTask.intValue());

AsyncTaskVO taskVo = queue.takeFirst();

if(taskVo == null || taskVo.c == null || taskVo.task == null) {

if(DEBUG)

Logger.e(CNAME, “TASK : NULL”);

return;

}

if(!NetworkUtil.isAvailable(taskVo.c)) {

queue.clear();

return;

}

taskVo.task.executeOnExecutor(EXECUTOR, “”);

} catch(Exception e) {

if(DEBUG)

Logger.e(CNAME, ExceptionUtil.getException(e));

}

}

}

}

}

3. GetThumbnailTask.java

public class GetThumbnailTask extends AsyncTask<String, Integer, Bitmap> {

static final String CNAME = GetThumbnailTask.class.getSimpleName();

@Override

public void onPreExecute() {

AsyncTaskDispatcher.runningTask.incrementAndGet();

}

@Override

protected Bitmap doInBackground(String… params) {

// 여기에서 Bitmap 이미지를 가져온다. 

}

@Override

protected void onPostExecute(Bitmap result) {

AsyncTaskDispatcher.runningTask.decrementAndGet();

}

}

LIFO 구조의 AsyncTask 디스패칭을 실행하는 방법은, 위의 GetThumbnailTask를 Adapter에서 생성해서 AsyncTaskDispatcher에 추가만 하면 됩니다.