Android/App개발2013. 11. 6. 19:28



Android 3.1 부터 system 의 package manager 는 application 의 stop 상태를 유지관리하며, background process 나 다른 app 에서 launch 할 수 있는 것을 control 합니다. 


App 실행후 Back Key를 이용하여 App을 종료한 상태와, 

단말기 "환경설정 > 에플리케이션 관리 > App > 앱 강제 종료"와는  상태가 다르다는 것 입니다.



여기서 Back Key를 이용해 Activity를 종료하는 것은 stopped state이며 앱 강제 종료는 Application Stopped State 입니다.


문제는 안드로이드 3.1이전에 사용한 방식으로 BroadCast 메시지를 발송하였을때 Application Stopped State 상태에서는 Broadcast 메시지를 받을 수 없습니다.


Application Stopped State에서 BroadCast 메시지를 받기위해서는 FLAG_INCLUDE_STOPPED_PACKAGES를 추가하여 Broadcast하여야 합니다. 



 FLAG_INCLUDE_STOPPED_PACKAGES  : stopped 된 application 도 target 이 됩니다. 

 FLAG_EXCLUDE_STOPPED_PACKAGES : stopped 된 application 은 target 이 되지 않습니다. 

* 기본 값은 FLAG_EXCLUDE_STOPPED_PACKAGES 입니다.



사용 방법

1
2
3
4
5
6
7
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
     intent.addFlags(FLAG_INCLUDE_STOPPED_PACKAGES);
}
  
sendBroadcast(intent);


그렇담.. 

Push리시버같은것은 어떻게 해야 할까요???


Posted by 삼스
Android/App개발2013. 7. 11. 19:13



private void getMusicMetaInfo(String fileStr) {

Uri fileUri = Uri.parse(fileStr);

String filePath = fileUri.getPath();

Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,null,"_data ='" + filePath + "'",null,null);

c.moveToNext();

if(c.getCount()>0) {

int id = c.getInt(0);

Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,id);

Log.d(LOG_TAG, fileStr + "'s uri : " + uri.toString());

Cursor cur = managedQuery(uri, null, null, null, null);

if(cur.moveToFirst()) {

Log.d(LOG_TAG, "==============================");


// int artistColumn = cur.getColumnIndex(MediaStore.Audio.AlbumColumns.ARTIST);

Log.d(LOG_TAG, "타이틀 : " + cur.getString(cur.getColumnIndex(MediaStore.Audio.AudioColumns.TITLE)));

Log.d(LOG_TAG, "아티스트 : " + cur.getString(cur.getColumnIndex(MediaStore.Audio.AlbumColumns.ARTIST)));

Log.d(LOG_TAG, "앨범 : " + cur.getString(cur.getColumnIndex(MediaStore.Audio.AlbumColumns.ALBUM)));

Log.d(LOG_TAG, "재생시간 : " + cur.getString(cur.getColumnIndex(MediaStore.Audio.AudioColumns.DURATION)));


Log.d(LOG_TAG, "==============================");

}

}

c.close();

Log.d(LOG_TAG, "");

}

Posted by 삼스
Android/App개발2013. 3. 7. 11:20


안드로이드 Bluetooth 프로그래밍을 위해 http://developer.android.com/guide/topics/connectivity/bluetooth.html의 내용을 정리함.

안드로이드 플랫폼은 블루투스네트웍스택을 제공하여 다음의 블루투스의 기능을 제공한다.

- 다른 디바이스의 스캔

- 페어링을 위해 로칼디바이스에 쿼리 

- RFCOMM채널 연결 가능

- 서비스 디스커버리를 통한 타디바이스 연결

- 디바이스간 데이터 전송

- 다중연결 관리


기초

블루투스셋업, 페어링된 또는 유효한 디바이스 찾기, 연결하기 그리고 데이터 전송하기에 대한 기초적인 내용을 기술하겠다.

android.bluetooth패키지에 모든 API들이 정의되어 있다. 다음에 설명되는 클래스들이 대표적인 클래스들이다.

BluetoothAdapter

  로컬블루투스어댑터를 대표한다. 이 객체를 통해서 디바이스를 찾고 연결하고 디바이스를 활성화하여 통신을 수행하는등의 작업이 가능하다.

BluetoothDevice

  원격블루투스디바이스를 대표한다. 이 객체를 통해 블루투스소켓을 통한 연결이나 디바이스의 속성정보를 요청할 수 있다.

BluetoothSocket

  블루투스소켓의 인터페이스를 대표한다. TCP socket과 유사하다. 입출력스트림을 통해 다른 디바이스와 데이터를 주고받을 수 있다.

BluetoothServerSocket

  들어오는 요청을 수신할 수 있는 서버소캣을 대표한다. TCP ServerSocket과 유사하다. 

BluetoothClass

  블루투스디바이스의 특성과 사양을 나타낸다. 읽기전용속성이며 major, minor 디바이스 클래스로 디바이스의 성격을 규정한다.  

BluetoothProfile

  블루투스프로파일을 나타낸다. 블루투스기반의 통신을 위한 무선인터페이스 규격을 말한다. 예를 들어 핸즈프리 프로파일같은 것을 말한다. 더 자세한 내용은 http://developer.android.com/guide/topics/connectivity/bluetooth.html#Profiles 를 참조하라.

BluetoothHeadset

  모바일폰을 위한 블루투스헤드셋을 지원한다. 핸즈프리(v1.5)프로파일을 포함한다.

BluetoothA2dp

  오디오전송을 위한 품질을 정의한다. A2DP(Advanced Audio Distribution Profile)

BluetoothHealth

  건강관련 디바이스 프로파일

BluetoothHealthCallback

  BluetoothHealth 콜백을 구현하기 위한 추상클래스. 애플리케이션의 등록상태나 블루투스채널의 변경이나 업데이트의 수신을 감지하기 위해 이 클래스를 반드시 구현해야 한다.

BluetoothHealthAppCOnfiguration

  Bluetooth Health 3rd-party애플리케이션을 health device에 연결하기 위한 설정

BluetoothProfile.ServiceListener

  Bluetooth 서비스가 연결 또는 차단되었을 때 BluetoothProfile IPC 클라이언트에 통지를 한다. 즉 내부에서 서비스는 각각의 프로파일로 동작한다.


블루투스 권환

BLUETOOTH와 BLUETOOTH_ADMIN 권한이 준비되어 있다.

BLUETOOTH : 블루투스통신을 위한 권한

BLUETOOTH_ADMIN :  디바이스 디스커버리나 장치설정등을 할 수 있는 권한.


블루투스 셋업

블루투스를 사용하기 전에 디바이스가 블루투스가 가능한지 살펴야 한다.

1. BluetoothAdapter를 얻어낸다.

  1. BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
       
    // Device does not support Bluetooth
    }

2. 블루투스를 활성화한다.

if (!mBluetoothAdapter.isEnabled()) {
   
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult
(enableBtIntent, REQUEST_ENABLE_BT);
}

위와 같이 호출하면 블루투스활성화여부 팝업이 뜨며 사용자가 확인을 선택하면 RESULT_OK가 onActivityResult()에서 리턴되고 그러지 않으면 RESULT_CANCELED가 리턴된다.

이 때 앱은 onPause와 onResume루틴을 타게 되니 유의한다.


디바이스 찾기

BluetoothAdapter를 통해 근처의 디바이스들을 찾을 수 있으며 페어링된 다바이스리스트도 얻을 수 있다.

디바이스디스커버리는 주변의 블루투스가 활성화되어 있는 디바이스들을 스캔한다. 그리고 각 디바이스의 정보를 수집한다. 수집이 가능한 디바이스는 블루투스가 활성화되어 있고 검색가능한 상태로 셋팅되어 있어야 한다. 그러면 검색되어 지고 디바이스명, 클래스 그리고 MAC주소가 수집이 가능하다. 이 정보를 기반으로 특정 디바이스를 선택하여 연결할 수 있다.

한번 연결이 이루어진 디바이스는 자동으로 페어링이 요청되게 된다. 디스커버리할때 저장된 MAC주소정보를 통해 디서커버리 단계 없이 바로 연결을 할 수 있다.

페어링과 연결은 한가지 차이점이 있다. 페어링은 두 디바이스가 서로 존재함을 인식하는 것이고 인증을 위해 공유된 link-key를 가지고 서로 암호화된 연결을 확립할 수 있다.

연결은 디바이스간에 RFCOMM채널을 공유하였다는 것이고 서로 데이터를 주고 받을 수 있다는 것이다. 페어링이 먼저 되어야 통신을 수행할 수 있다.

안드로이드가 탑재된 제품들은 discoverable이 꺼진 상태이다. 따라서 페어링을 위해 discoverable을 활성화하여야 한다. http://developer.android.com/guide/topics/connectivity/bluetooth.html#EnablingDiscoverability 를 참조하라.


페어링된 다바이스 얻기

디스커버리를 수행하기 전에 현재 페어링된 디바이스를 알아낼수 있다. getBondedDevices()메서드로 페어링된 디바이스들의 리스트를 얻을 수 있다.

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
   
// Loop through paired devices
   
for (BluetoothDevice device : pairedDevices) {
       
// Add the name and address to an array adapter to show in a ListView
        mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
   
}
}

검색된 디바이스에는 연결을 위한 MAC주소정보가 포함되어 있으며 연결하기는 다음을 참조하라

http://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices


디바이스 디스커버리

startDiscovery()메서드를 수행하면 된다. 프로세스는 비동기로 동작하고 메서드는 바로 리턴된다. 

비동기로 진행되는 과정에 디바이스가 검색이 되면 ACTION_FOUND 인텐트가 브로드캐스트 되며 이 이벤트를 받기 위해 BroadcastReceiver를 사용해야 한다.

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
   
public void onReceive(Context context, Intent intent) {
       
String action = intent.getAction();
       
// When discovery finds a device
       
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           
// Get the BluetoothDevice object from the Intent
           
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
           
// Add the name and address to an array adapter to show in a ListView
            mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
       
}
   
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver
(mReceiver, filter); // Don't forget to unregister during onDestroy

디스커버리 과정은 전원을 많이 소비사는 과정이므로 검색이 다 끝나면 반드시 cancelDiscovery()를 호출해주어야 한다. 그리고 연결이 되어 있는 상태에서 디스커버리는 통신데이터 bandwidth를 현저하기 떨어뜨리기 때문에 연결중에는 디스커버리를 하지 않아야 한다.

디스커버러블 활성화

ACTION_REQUEST_DISCOVERABLE 액션인텐트를 startActivityForResult로 호출한다. EXTRA_DISCOVERABLE_DURATION 으로 탐색시간을 파라메터로 넘길 수 있다. 최대시간은 3600초이고 0이하는 내부적으로 120초로 자동 셋팅된다. 다음은 300초로 셋팅한 예이다.

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent
.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity
(discoverableIntent);

디바이스가 블루투스가 활성화가 안되어 있다면 자동으로 활성화후 진행한다.

디스커버러블모드는 백그라운드에서 지정한 시간만큼 유지되었다가 끝나게 된다. 만일 이 상태변화를 처리하고 싶다면 ACTION_SCAN_MODE_CHANGED인텐트의 리시버를 통해서 처리하면 된다. 이 인텐트는 추가정보로 EXTRA_SCAN_MODE, EXTRA_PREVIOUS_SCAN_MODE로 현재와 이전 스캔모드를 얻을 수 있다. 

모드는 3가지이다.

- SCAN_MODE_CONNECTABLE_DISCOVERABLE :    디바이스가 둘다 가능한 상태

- SCAN_MODE_CONNECTABLE :  디스커버러블모드는 아니고 연결은 가능한 상태.

-  SCAN_MODE_NONE : 둘다 안되는 상태

디바이스와 이미 연결이 초기화된 상태이면 굳이 디스커버러블을 활성화할 필요는 없다. 디스커버러블을 활성화하는 경우는 앱이 서버소켓을 제공하여 연결시도를 허락하기 위한 경우만 해당된다. 왜냐하면 원격디바이스들은 연결이 가능해지기 이전에 먼저 검색이 가능해야 하기 때문이다.


디바이스 연결

앱이 두단말간에 연결을 생성하기 위해서 서버와 클라이언트단을 모두 구현해야 한다. 하나는 서버가 되어서 대기하고 하나는 연결을 시도해야 하기 때문이다. 연결시 사용하는 정보는 서버의 MAC주소가 된다. 서버와 단말이 둘다 동일 RFCOMM채널상에서 BluetoothSocket으로 연결되면 상호간에 연결된것으로 간주된다. 이 때 입출력스트림을 통해 데이터를 주고받을 수 있다. 

서버와 클라이언트 디바이스는 각각 다른 방식으로 요구되는 BluetoothSocket을 얻는다. 서버는 요청받은 연결을 허락했을 때 얻어지고 클라이언트는 서버와 RFCOMM채널이 열렸을 때 얻어진다.

두 단말이 모두 자동으로 서버모드로 대기하고 서로간에 연결을 시도하는 방식과 명시적으로 하나는 호스트로써 서버로 대기하고 다른 하나가 연결을 시도하는 방식이 있다.

두 디바이스가 페어링된적이 없으면 안드로이드는 자동으로 페어링요청 통지나 팝업을 연결과정에 띄운다. 그래서 연결시도과정에 두 단말이 페어링된적이 있는지 알 필요가 없다. 연결시도는 페어링과정이 끝난 이후 계속 진행된다.


서버 연결

BluetoothServerSocket으로 서버로 동작시킬 수 있다. 서버소켓은 연결요청을 받아들이기 위한것이다.

1. BluetoothServerSocket을 얻는다.

   lisetnUsingRfcommWithServiceRecord(String, UUID)로 얻는다. String는 서비스명이고 UUID는 클라이언트디바이스와 연결 동의에 기반으로 사용된다. 다시 말해 클라이언트디바이스가 연결을 시도할 때 UUID가 전달되어진다. 다음단계에서 UUID는 서로 맷치가 되어야 한다.

2. accept()메서드로 연결요청을 대기한다.

  연결이 허용되거나 예외가 발생할 때 까지 이 메서드는 블럭된다.  클라이언트의 연결요청이 UUID가 일치하였을 때만 accept가 성공하고 BluetoothSocket을 리턴한다.

3. 더이상 연결을 허용하지 않을거라면 close()를 호출한다.

  서버소켓을 종료하고 모든 리소스를 해재한다. 연결된 상태는 유지된다.  TCP/IP와 다르게 RFCOMM은 하나의 채널에 하나의 연결만 허용한다. 따라서 accept()로 연결이 성립되면 서버소켓을 close()로 닫아준다.

accept는 Main activity에서 호출하지 않아야 블럭되는것을 방지할 수 있다. 블럭중에 중단하려면 다른 스레드에서 close()를 호출하면 된다.

다음은 서버구현의 예이다.

private class AcceptThread extends Thread {
   
private final BluetoothServerSocket mmServerSocket;
 
   
public AcceptThread() {
       
// Use a temporary object that is later assigned to mmServerSocket,
       
// because mmServerSocket is final
       
BluetoothServerSocket tmp = null;
       
try {
           
// MY_UUID is the app's UUID string, also used by the client code
            tmp
= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       
} catch (IOException e) { }
        mmServerSocket
= tmp;
   
}
 
   
public void run() {
       
BluetoothSocket socket = null;
       
// Keep listening until exception occurs or a socket is returned
       
while (true) {
           
try {
                socket
= mmServerSocket.accept();
           
} catch (IOException e) {
               
break;
           
}
           
// If a connection was accepted
           
if (socket != null) {
               
// Do work to manage the connection (in a separate thread)
                manageConnectedSocket
(socket);
                mmServerSocket
.close();
               
break;
           
}
       
}
   
}
 
   
/** Will cancel the listening socket, and cause the thread to finish */
   
public void cancel() {
       
try {
            mmServerSocket
.close();
       
} catch (IOException e) { }
   
}
}


클라이언트 연결

리모트디바이스와 연결하기 위해서는 일단 리모트디바이스객체를 얻어야 한다. 페어링된 디바이스를 얻거나 디스커버리 과정을 통해서 연결하고자 하는 BluetoothDevice를 얻어야 한다.

1. BluetoothDevice.createRfcommSocketToServiceRecord(UUID) 를 호출

  BluetoothSocket을 초기화한다. 이 때 서버디바이스에서 제공하는 UUID를 인자로 넘긴다. 이 값이 동일해야 연결이 성립된다.

2. connect()로 연결 시도

  UUID가 매칭되는 디바이스와 연결을 시도하고 성공하면 소켓을 리턴한다. 

connect()메서드가 블럭되는 호출이기 때문에 분리된 스레드에서 수행되어야 한다.

connect()과정 중에는 디스커버리가 진행되면 안된다. 만일 진행하게 되면 연결과정이 아주 느리게 진행되거나 실패하게 된다.

다음은 간략한 예제이다.

private class ConnectThread extends Thread {
   
private final BluetoothSocket mmSocket;
   
private final BluetoothDevice mmDevice;
 
   
public ConnectThread(BluetoothDevice device) {
       
// Use a temporary object that is later assigned to mmSocket,
       
// because mmSocket is final
       
BluetoothSocket tmp = null;
        mmDevice
= device;
 
       
// Get a BluetoothSocket to connect with the given BluetoothDevice
       
try {
           
// MY_UUID is the app's UUID string, also used by the server code
            tmp
= device.createRfcommSocketToServiceRecord(MY_UUID);
       
} catch (IOException e) { }
        mmSocket
= tmp;
   
}
 
   
public void run() {
       
// Cancel discovery because it will slow down the connection
        mBluetoothAdapter
.cancelDiscovery();
 
       
try {
           
// Connect the device through the socket. This will block
           
// until it succeeds or throws an exception
            mmSocket
.connect();
       
} catch (IOException connectException) {
           
// Unable to connect; close the socket and get out
           
try {
                mmSocket
.close();
           
} catch (IOException closeException) { }
           
return;
       
}
 
       
// Do work to manage the connection (in a separate thread)
        manageConnectedSocket
(mmSocket);
   
}
 
   
/** Will cancel an in-progress connection, and close the socket */
   
public void cancel() {
       
try {
            mmSocket
.close();
       
} catch (IOException e) { }
   
}
}


연결관리

디바이스간 연결이 완료되면 BluetoothSocket을 각각 소유하게 된다. 이넘을 통해서 데이터를 주고받을 수 있다.

1. 소켓으로부터 InputStream과 OutputStream을 얻는다.

2.  read(byte[]), write(byte[])로 읽거나 쓰면 끝...

두 메서드 모두 블럭되기 때문에 스레드로 분리되어야 한다.

private class ConnectedThread extends Thread {
   
private final BluetoothSocket mmSocket;
   
private final InputStream mmInStream;
   
private final OutputStream mmOutStream;
 
   
public ConnectedThread(BluetoothSocket socket) {
        mmSocket
= socket;
       
InputStream tmpIn = null;
       
OutputStream tmpOut = null;
 
       
// Get the input and output streams, using temp objects because
       
// member streams are final
       
try {
            tmpIn
= socket.getInputStream();
            tmpOut
= socket.getOutputStream();
       
} catch (IOException e) { }
 
        mmInStream
= tmpIn;
        mmOutStream
= tmpOut;
   
}
 
   
public void run() {
       
byte[] buffer = new byte[1024];  // buffer store for the stream
       
int bytes; // bytes returned from read()
 
       
// Keep listening to the InputStream until an exception occurs
       
while (true) {
           
try {
               
// Read from the InputStream
                bytes
= mmInStream.read(buffer);
               
// Send the obtained bytes to the UI activity
                mHandler
.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                       
.sendToTarget();
           
} catch (IOException e) {
               
break;
           
}
       
}
   
}
 
   
/* Call this from the main activity to send data to the remote device */
   
public void write(byte[] bytes) {
       
try {
            mmOutStream
.write(bytes);
       
} catch (IOException e) { }
   
}
 
   
/* Call this from the main activity to shutdown the connection */
   
public void cancel() {
       
try {
            mmSocket
.close();
       
} catch (IOException e) { }
   
}
}


프로파일로 동작하기

안드로이드 3.0부터 블루투스프로파일로 동작이 가능해졌다. 블루투스프로파일은 무선인터페이스 규격으로 블루투스기반의 디바이스간 통신에 기반한것이다. 예를 들어 헨즈프리프로파일같은 것이 있는데 모바일폰과 헤드셋간의 연결이 가능하려면 두 디바이스 모두 핸즈프리 프로파일을 지원해야 한다.

블루투스프로파일을 직접 커스텀하게 작성이 가능하다. 안드로이드 블루투스 API는 다음의 프로파일의 구현을 지원한다.

- 헤드셋

- A2DP

- Health Device

프로파일기반으로 동작시키려면 다음과정으로 진행한다.

1. 블루투스어댑터를 얻는다.

2. 프로파일 프록시 객체를 얻는다 getProfileProxy(). 이 객체로 연결을 달성하게 된다. 아래 예제에서는 BluetoothHeadset의 인스턴스를 프록시객체로 사용하였다.

3. BluetoothProfile.ServiceListener를 셋업하여 연결과 해재 이벤트를 처리한다.

4. onServiceConnected()에서 프록시객체의 핸들을 얻는다.

5. 프록시객체를 얻으면 이 객체를 통하여 연결상태를 모니터링하고 해당 프로파일과 연관된 동작들을 수행할 수 있다.

다음은 BluetoothHeadset 프록시객체를 사용하여 해드셋프로파일을 제어하는 예이다.

BluetoothHeadset mBluetoothHeadset;
 
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 
// Establish connection to the proxy.
mBluetoothAdapter
.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
 
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
   
public void onServiceConnected(int profile, BluetoothProfile proxy) {
       
if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset
= (BluetoothHeadset) proxy;
       
}
   
}
   
public void onServiceDisconnected(int profile) {
       
if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset
= null;
       
}
   
}
};
 
// ... call functions on mBluetoothHeadset
 
// Close proxy connection after use.
mBluetoothAdapter
.closeProfileProxy(mBluetoothHeadset);


Posted by 삼스
Android/App개발2012. 8. 14. 08:59


JellyBean(4.1) WebKit은 ICS대비 변경 없음

ICS(4.0) Webkit

  • WebKit updated to version 534.30
  • Support for Indic fonts (Devanagari, Bengali, and Tamil, including the complex character support needed for combining glyphs) in WebView and the built-in Browser
  • Support for Ethiopic, Georgian, and Armenian fonts in WebView and the built-in Browser
  • Support for WebDriver makes it easier for you to test apps that use WebView

HoneyComb(3.1) WebKit
  • File scheme cookies
    • The CookieManager now supports cookies that use the file: URI scheme. You can usesetAcceptFileSchemeCookies() to enable/disable support for file scheme cookies, before constructing an instance of WebView or CookieManager. In a CookieManager instance, you can check whether file scheme cookies is enabled by calling allowFileSchemeCookies().
  • Notification of login request
    • To support the browser autologin features introduced in Android 3.0, the new methodonReceivedLoginRequest() notifies the host application that an autologin request for the user was processed.
  • Removed classes and interfaces
    • Several classes and interfaces were removed from the public API, after previously being in deprecated state. See theAPI Differences Report for more information.


HoneyComb(3.0) Webkit
  • New WebViewFragment class to create a fragment composed of a WebView.
  • New WebSettings methods:
    • setDisplayZoomControls() allows you to hide the on-screen zoom controls while still allowing the user to zoom with finger gestures (setBuiltInZoomControls() must be set true).
    • New WebSettings method, setEnableSmoothTransition(), allows you to enable smooth transitions when panning and zooming. When enabled, WebView will choose a solution to maximize the performance (for example, the WebView's content may not update during the transition).
  • New WebView methods:
    • onPause() callback, to pause any processing associated with the WebView when it becomes hidden. This is useful to reduce unnecessary CPU or network traffic when the WebView is not in the foreground.
    • onResume() callback, to resume processing associated with the WebView, which was paused during onPause().
    • saveWebArchive() allows you to save the current view as a web archive on the device.
    • showFindDialog() initiates a text search in the current view.
안드로이드 버전별 WebKit버전
3.0 - AppleWebKit/534.13
2.3 - AppleWebKit/533.1
2.2 - AppleWebKit/533.1
2.1 - AppleWebKit/530.17

Posted by 삼스
Android/App개발2012. 7. 16. 17:38


출처 : http://iyeti.kr/630

안드로이드를 개발하면서 아이폰과는 달리 가장 짜증났던부분이 해상도에 관련된 문제 일듯합니다.
안드로이드 펍에서 회색님이 기본적인걸 정리해 주셨네요. 


=============== 스마트폰 =================

[HVGA 스마트폰]

- RESOLUTION : 320px(320dp), 480px(480dp)

- DENSITY DPI  : mdpi, 160 dpi

- SCREEN SIZE : normal


예 : G1, 옵티머스원


[WVGA 스마트폰] 

- RESOLUTION : 480px(320dp), 800px(533dp) or 854px(569dp)

- DENSITY DPI  : hdpi, 240 dpi

- SCREEN SIZE : normal


예 : WVGA800 : 넥서스원, 갤럭시S, 갤럭시S2 WVGA854 : 모토로라 드로이드, XPERIA X10


=============== 태블릿 =================

[7인치 태블릿]

- RESOLUTION : 600px(600dp), 1024px(1024dp)
- DENSITY DPI  : mdpi, 160 dpi

- SCREEN SIZE : large


예 : 킨들 파이이, 누크 태블릿


[10인치 태블릿]

- RESOLUTION : 1280px(1280dp), 800px(800dp) or 720px(720dp)
- DENSITY DPI  : mdpi, 160 dpi

- SCREEN SIZE : xlarge


예 : 갤럭시탭 10.1, 모토로라 XOOM


=============== 태블릿폰 =================

[갤럭시노트]

- RESOLUTION : 800px(400dp), 1280px(640dp)

- DENSITY DPI  : xhdpi, 320 dpi

- SCREEN SIZE : ??? (Gingerbread)


[7인치 갤럭시탭(2010)]

- RESOLUTION : 600px(400dp), 1024px(682dp)

- DENSITY DPI  : hdpi, 240 dpi

- SCREEN SIZE : large(Gingerbread)



크게 4가지로 나눠지네요. 

1. 320dp 스마트폰 

2. 400dp 태블릿폰 (2011 현재 삼성에서만 출시, 갤탭(2010)은 7인치지만 dp 분류상 갤노트와 같이 분류.)

3. 600dp 7인치 태블릿

4. 720dp(800dp) 10인치 태블릿. 


* 참고 * 

http://developer.android.com/guide/practices/screens_support.html

http://android-developers.blogspot.com/2011/07/new-tools-for-managing-screen-sizes.html


* 주의사항 *

- 킨들파이어(2011) : 스크린 사이즈가 실제 디바이스에서는 Large인데 에뮬레이터에서는 XLarge로 잡힐 수 있습니다. 안드로이드 3.0 이하에서는 스크린 사이즈 잡는 부분이 문서와 같이 동작하지 않습니다. 


- 갤럭시탭(2010) : 400dp라는 흔치 않은 넓이를 가지고 있으며 문서와 다르게 Normal이 아니라 Large로 잡습니다. 문서대로라면 ICS로 업그레이드시에 달라질수 있을것으로 보이니 유의해야할것 같습니다.



실제기기에서 확인해보지 않은 값도 있습니다. 잘못된 부분 있으면 알려주세요. 확실치 않은 부분은 ? 붙여놓을테니 확인 부탁드립니다.


위키에 좀더 자세한 해상도 정보가 있습니다. :  http://en.wikipedia.org/wiki/List_of_displays_by_pixel_density 

[출처 : 
 http://www.androidpub.com/index.php?mid=android_dev_info&document_srl=1895935]


#.2012.04.19 내용추가
갤럭시넥서스: layout-sw360dp
갤럭시노트: layout-xhdpi-1280x800 (or layout-normal-xhdpi)
갤럭시탭10.1: layout-xlarge-mdpi(3.2이상 버전에서는 layout-sw800dp로도 가능)
HD(1280x720)폰: layout-xhdpi-1280x720 (layout-normal-xhdpi)
HTC Evo4g: layout-hdpi-960x540


Posted by 삼스
Android/App개발2011. 10. 20. 19:06

http://sdw8001.tistory.com/31
 

이번에 정리해 볼 이슈는 안드로이드 개발에 반드시 사용하게 되는 에뮬레이터의 관한 이슈이다.
에뮬레이터가 특별한 이슈가 될 것은 없지만 Network programming 을 할 때는 이슈가 발생한다.
특히, 안드로이드 애플리케이션이 서버로 동작할 때는 더욱더 그러하다.

들어가기에 앞서, 에뮬레이터를 클라이언트로 이용해서 사용하는 경우에는 별다른 이슈가 없다고 하였는데 이미 에뮬레이터에는 브라우저도 있고, WebView 클래스를 이용한 Webkit controller도 띄우는 예제가 있다. 다만, 외부로 접속하고자 할 경우(즉, 인터넷을 사용하는 경우에는) user-permission 셋팅을 해 주어야 하는 것을 잊지 말자.

AndroidManifest.xml 파일의 가장 상위 엘리먼트인 <manifes> 바로 다음 자식 엘리먼트로 다음을 넣어주면 된다.

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

이 부분에 대한 간략한 설명은 이전 포스트에도 살펴볼 수 있다.

http://www.wiseant.net/blog/7 : android.webkit.WebView 를 이용한 데모

자! 그러면 여기서 살펴보고자 하는 것은 안드로이드 애플리케이션을 SocketServer를 이용한 서버로 띄웠을 때의 이슈이다.

그렇다면, 이에 해당하는 부분을 안드로이드 문서에서는 어떻게 정의해 두었을까? 다음의 링크를 통해서 확인할 수 있다.

# emulator reference

# emulator reference networking

일종의 제약 사항이라고 할 수 있는데 에뮬레이터의 네트워크 아키텍쳐를 virtual router/firewall 을 사용하여 내부적으로 에뮬레이터내에 IP를 할당하고 있는 것을 볼 수 있다.
이렇게되어 있다보니 로컬 시스템의 IP를 사용할 것으로 생각하면 오산인 것이다. 일단은 위의 문서를 토대로 대략적인 것을 살펴볼 수 있고, Network redirerction이란 것도 볼 수 있다.

안드로이드 에뮬레이터에서 Network redirection은 에뮬레이터가 가지는 VLAN과 로컬 시스템의 포트를 연결(파이프로 연결한다고 생각하면 된다)해 주는 개념이다.
즉, 로컬 시스템의 포트로 들어오는 패킷을 에뮬레이터의 포트로 넘겨주는 식이다(Forward 개념과 비슷하다).

좀 복잡한 듯 하니 그냥 간단한 테스트 소스와 설정하는 방법을 통해서 알아보자.
먼저, 안드로이드 애플리케이션에 올라가는 간단한 서버 소켓을 여는 프로그램을 만들어보자. 참고로 안드로이드 전체 소스는 생략한다. 많은 안드로이드 기초 강좌 등에서 찾아볼 수 있다.

    private String host = "localhost";
    private String port = 8002;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);

        status = (TextView)findViewById(R.id.status);
        status.setText("do something");

        Button btnRunServer = (Button)findViewById(R.id.runserver);
        btnRunServer.setOnClickListener(new Button.OnClickListener() {
         public void onClick(View v) {
          // run server
          try {
           ServerSocket serverSocket = new ServerSocket(port);
           
           status.setText(status.getText() + " waiting");
           Log.d("Green", "Waiting...");
           
           Socket player1 = serverSocket.accept();

           status.setText(status.getText() + " Connected");
           Log.d("Green", "Connected");

           player1.close();
          }
          catch (Exception ex) {
           status.setText(status.getText() + ex.getMessage());
          }
         }
        });

        Button btnConnect = (Button)findViewById(R.id.connect);
        btnConnect.setOnClickListener(new Button.OnClickListener() {
         public void onClick(View v) {
          // connect to server
          try {
           Socket socket = new Socket(host, port);

           DataInputStream fromServer = new DataInputStream(socket.getInputStream());
           DataOutputStream toServer = new DataOutputStream(socket.getOutputStream());

           status.setText("connected");
           Log.d("Green", "Connected...");
           
           toServer.writeInt(1337);
          }
          catch (Exception ex) {
           status.setText(ex.getMessage());
          }
         }
        });
    }

위의 소스에서는 ServerSocket을 열어놓고(Listening), accept 시에 Connected 메시만을 뿌려주는 간단한 서버용 애플리케이션이다.
connect 버튼을 클릭 시에는 지정된 host로 접속해 보는 소스이다.

안드로이드 에뮬레이터에서 접속 문제를 다루고 있음으로 여기선 ServerSocket 등에 대해서는 자세하게 다루지 않는다.

일단 위의 소스를 기반으로 하여 안드로이드를 구동해 보자.

일단 위와 같이 애플리케이션이 구동되면 첫 번째 버튼을 클릭해서 ServerSocket을 띄워준다. 이제 안드로이드 서버 애플리케이션과 연동할 간단한 클라이언트 소스를 살펴보자.

/**
 * 
 */
package com.mediachorus.test.android.network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

/**
 * @author Sang-Hyup Lee
 * @version 1.0
 *
 */
public class ClientTest {

 /**
  * @param args
  */
 public static void main(String[] args) {
  String host = "localhost";
  int port = 8002;
  // TODO Auto-generated method stub
  try {
   Socket socket = new Socket(host, port);

   DataInputStream fromServer = new DataInputStream(socket.getInputStream());
   DataOutputStream toServer = new DataOutputStream(socket.getOutputStream());

   System.out.println("IP:" + socket.getInetAddress().getHostAddress()
       + ", port:" + socket.getPort());
   System.out.println("Connected...");
      
   toServer.writeInt(1337);
   // toServer.write("quit".getBytes());
   toServer.flush();
   
   System.out.println("Done...");
  }
  catch (Exception ex) {
   System.err.println(ex.getMessage());
  }
 }
}

클라이언트 소스 역시 설명할 것도 별로 없다. 자바 네트워크 프로그램으로 서버에 접속해서 패킷을 보내어 본다. 로그를 통해서 접속이 되었는지 확인만 해 보는 것이다.

클라이언트 소스를 실행해 보자. 다음과 같은 Exception을 발생시킨다.

Connection refused: connect

커넥션이 이루어지지 않았다는 말인데, 우리의 의도는 안드로이드 애플리케이션 TCP 서버 소켓에 접속하는 것이다. 동일한 PC 에서 이루어지는데 localhost 라고 하면 되는 줄 알았는데 아니다.

여기서 위의 구글 문서 링크에서 나온 것처럼 Network redirection이 필요하다.
다음과 같이 command 상에서 telnet 접속부터 시작해서 실행해보자. 참고로 에뮬레이터는 5554 포트를 기본으로 할당하여 telnet으로 접속이 가능하다.

telnet localhost 5554
redir add tcp:8002:8002 

사용자 삽입 이미지

위와 같이 로컬 시스템이 8002 포트를 사용하지 않으면 OK 메시지로 응답한다. 참고로 redir 사용법을 보려면 "redir help"라고 입력하면 된다.
위의 명령어 뜻은 다음과 같다.

TCP 형태로 redirection을 하는데, 로컬 시스템의 8002 포트를 VLAN(에뮬레이터에 올라간 안드로이드 애플리케이션) 8002 포트로 연결(redirection) 해 준다.

add <protocol>:<host-port>:<guest-port>

그렇다면 redirection을 해지하는 방법은 add를 del로 바꾸어주면 된다. 다음과 같은 형태이다.

redir del tcp:8002


del 옵션을 사용시에는 system host port만 입력해 주면 된다.

자, 이 상태에서(redirection을 걸어둔 상태에서) 클라이언트 프로그램을 실행해 보자. Connected 메시지를 볼 수 있을 것이다.

이렇게해서 기본적인 Network redirection을 해결할 수 있는데, 클라이언트 소스 코드를 '127.0.0.1'로 host 값을 바뀌어서 해 보자.
(결론은? 잘 된다. 어쩌면 당연한 것을?^^)

하지만, 로컬 시스템이 가지고 있는 IP를 host 값으로 수정하고 실행해 보자. 다시금 "Connection refused: connect" 에러가 발생한다.
그렇다면, 안드로이드 애플리케이션이 실행하고 있는 시스템이 아닌 다른 시스템에서의 접속은? 당연히 안 된다.

문제는 여기에 또 있는 것이다. Server/Client 프로그램은 당연히 서로 다른 시스템의 연결을 지양하고 있는 것인데 이런 문제가 발생한다.
이는 안드로이드 에뮬레이터가 동작시에 localhost에 해당하는 127.0.0.1을 개발 머신의 loopback interface로 alias를 해주기 때문이다.
결국은 로컬 시스템의 IP를 할당하여 수행하지 못한다는 것이다.

이를 해결하기 위해서 다음의 링크를 통해서 stcppipe.exe를 실행해 주어야 한다. 링크에는 sudppipe.exe도 포함되어 있다. 소스와 함께.

Simple UDP proxy/pipe 0.3a  : http://aluigi.org/mytoolz/sudppipe.zip
Simple TCP proxy/pipe 0.4.4a : http://aluigi.org/mytoolz/stcppipe.zip

사용법은 다음과 같다.

stcppipe.exe -b <local IP> <remote hosts> <local port> <remote port>
remote hosts : 127.0.0.1
remote port : emulator 의 host port

이를 응용해서 실행해 보면 다음과 같이 입력할 수 있다.

stcppipe -b 192.168.0.86 l27.0.0.1 8002 8002

이렇게 하고 다시금 로컬에서든 다른 시스템에서든지 Client 프로그램을 실행해보자.
stcppipe 프로그램을 통해서 IN/OUT 되는 포트도 확인해 볼 수 있다. UDP도 마찬가지이다. 각자 테스트해 보는 것도 좋을 거 같다.


이렇게해서 안드로이드 에뮬레이터의 Network redirection 이슈를 마친다. 끝으로 이 문제는 혼자서 고민하고 해결한 것이 아니라 kandroid.org 의 운영자이신 들풀님의 큰 도움이 있었다. 이에 감사의 뜻을 전하며 해당 부분을 Q&A를 통해서 해결하였는데, 링크를 걸어둔다.


# 처음 Emulator IP 관련된 질문후, redir (option)에 대해서 알게된 링크

# 로컬 시스템 내에서의 접속이 아닌 원격 시스템에서의 접속 처리를 해결하게 된 링크

두 번째 링크에도 포함된 링크인데 구글 그룹스에서도 똑같은 고민을 했었고 동일하게 해결한 것을 찾을 수 있다.



다시금 함께 고민해 주신 들풀님에게 감사의 뜻을 전한다.


[마치며]
지금까지의 Network redirection을 통해서 다양한 것을 개발해 보았다. 하지만 실제 개발에서 사용하는 것이 TCP, UDP를 함께 사용하면서 까다로운 네트워크 프로그램을 처리하는 것이었다. 특이한 점은 윈도우에서는 정상적으로 처리되지 않을 때가 많았다.
그래서 Ubuntu 에서 실행해 보니 접속에 대한 문제는 거의 발생하지 않았다. 이러한 원인에 대한 분석은 현재로서는 어려울 것 같아서 다루지 않겠다.
그냥 경험담이라고 생각해 주면 좋겠다.

모두들... 더 좋은 정보나 자료가 있으면 공유해 주세요~^^ 
Posted by 삼스
Android/App개발2011. 9. 3. 18:33


Locale을 생성 후 updateConfigurations로 변경한다.
 

Locale locale = new Locale("ko"); 

Locale.setDefault(locale);

Configuration config = new Configuration();

config.locale = locale;

getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());


변경 후 액티비티를 다시 시작하여야 변경된 로켈이 적용된다.
 
finish();

Intent myIntent = new Intent(MainActivity.this, MainActivity.class);

startActivity(myIntent);


이렇게 하면 해당 액티비티에만 적용된다.
액티비티를 시작할 때 이전에 선택한 로켈로 시작되게 하려면 onCreate에서 지정해야 한다.

onCreate(..) {
         Locale locale2 = new Locale("en"); 

    Locale.setDefault(locale2);

        Configuration config2 = new Configuration();

        config2.locale = locale2;

        getBaseContext().getResources().updateConfiguration(config2, getBaseContext().getResources().getDisplayMetrics());


        setContentView(R.layout.main);

}

setContentView(.)보다 이전에 설정해주어야 한다.

만일 이렇게만 하면 런타임에 로켈을 바꾸면 화면이 점점 더 작아지는 황당한 상황을 겪을 것이다.
Manifest에 다음을 추가해라.

<supports-screens

android:smallScreens="true"

android:normalScreens="true"

android:largeScreens="true"

android:anyDensity="true"

/>




 
Posted by 삼스
Android/App개발2011. 6. 16. 18:31

안드로이드 개발시 유닛테스트를 하는 방법에 대해 정리한다.

두가지 케이스정도로 정리해보면
 1. 액티비티 테스트 : UI 테스트 자동화
 2. 클래스 테스트 : 로직을 담고 있는 클래스들을 테스트 자동화

방법은 Android project를 만들 때 Android Test Project로 만든다. 이 때 테스트하고자 하는 앱을 지정하게 되어 있다. 
 
1. 액티비티 테스트 
 -  ActivityInstrumentationTestCase2를 확장
public class HelloActivityTest extends ActivityInstrumentationTestCase2<HelloActivity> 
 - 일부 메소드 override
protected void setUp()
protected void tearDown()
 - test 메소드들 구현
 - Activity에 접근은 this.getActivity()로 가능하다. 

2. 클래스 테스트
 - InstrumentationTestCase 를 확장
 
 public class MetadataManagerTest extends InstrumentationTestCase 
 - 일부 메소드 override
protected void setUp()
protected void tearDown()
 - test 메소드들 구현
 
AsyncTask를 테스트할 때는 CountdownLatch를 이용해서 비동기 스레드를 테스트 할 수 있다.
출처 : http://kingori.egloos.com/4554640
public class AsyncTaskTest extends InstrumentationTestCase {

  public void testAsyncTask() {
    final Data data = new Data();
    final CountDownLatch signal = new CountDownLatch(1); // countdown 1을 기다리는 랫치

    Callback a = new Callback() {
      onPrepare() { data.val = 1; }
      onSuccess() { data.val = 2; signal.countDown();} // 작업이 끝나면 1을 내려 유닛테스트 쓰레드가 진행되게 한다.
      onFail() { data.val = 3; signal.countDown(); } // 실패할 수도 있으니..
    }

    runTestOnUiThread( new Runnable() {
      public void run() {
        Manager.getInstance().doSomething(a);
      }
    });
    signal.await(); // 랫치 값이 0이 될 때 까지 대기 상태
    assertEquals( data.val, 2 ); //결과 확인
  }
}

Posted by 삼스
Android/App개발2011. 6. 10. 13:08
http://www.hanb.co.kr/network/view.html?bi_id=1033
 

저자:
 김대곤

Java Native Interface에 관련된 서적을 찾으려고 아마존을 뒤적일 때, 다음과 같은 서평을 보게 되었다. “한 가지 분명한 사실은 JNI를 쓰지 않는 것이 훨씬 좋다는 것이다. JNI는 다루기 힘들고, 불안정하며, 에러가 발생할 소지가 많으며, 무엇보다도 JNI가 쏟아내는 Runtime 에러는 자바 가상 머신을 날려 버릴 수 있는 사악한 기술이다.” 이 서평을 쓴 사람은 그 책을 사서 봤을 것이며, 단지 관심에서가 아니라 심각하게 Java Native Interface를 사용했을 것이며, 아마도 사용하고 있을 것이다. JNI는 자주 사용되지는 않지만, 그렇다고 불필요한 기술도 아니다. 아니 절대적으로 필요한 기술임이 틀림없다.

Java Native Interface는 자바에서 다른 언어로 개발된 기능을 내부적으로 사용할 수 있게 해 준다. 이미 C 언어나 C++로 개발된 기능이 있다면 그것을 자바 프로그램 안에서 사용할 수 있게 해 준다. 이 글은 JNI을 사용하는 두가지 간단한 예제를 통해 JNI가 무엇이며, JNI를 어떻게 사용하는지에 대해 간단히 살펴볼 것이다. 첫 번째 예제는 항상 등장하는 HelloWorld식 예제이고, 두 번째는 C 언어로 개발된 정렬 프로그램을 자바 프로그램에서 사용하는 예제이다. 

썬 자바 사이트에는 JNI를 구현하는 여섯가지 단계를 설명하고 있다.
1. Native 메소드를 호출하는 자바 코드 작성.
2. 첫 단계에서 쓴 자바 코드의 컴파일.
3. javah 명령어를 사용하여 헤더 파일의 생성.
4. 지정된 언어로 호출되는 Native 메소드를 구현.
5. Native 메소드를 구현한 코드를 동적 라이브러리로 컴파일.
6. 실행.

Native 메소드를 호출하는 자바 코드에는 세 가지 요소가 포함된다. Native 메소드의 선언, 실제 Native 메소드의 사용, 동적 라이브러리를 시스템 상에 상주하도록 불러오는 것이다. 이 세 가지는 아래 코드에서 강조되어 있다.


[HelloWorld.java]
/*
 * Created on 2004. 12. 31.
 * File name : HelloWorld.java
 * @author DaeGon Kim
 *
 */
public class HelloWorld {

              String greeting = "Hello Java World";

              public native void display();

              public static void main(String[] args) {

                            HelloWorld hw = new HelloWorld()
              System.out.println(hw.greeting);
                            hw.display();
                            System.out.println(hw.greeting);

              }

              static {
                            System.load("D:\\Project\\Java\\hello.dll");
              }

}


두 번째와 세 번째 단계에서는 javac 명령어로 HelloWorld.java를 컴파일한 후, javah 명령어로 헤더 파일을 생성한다. 
> javac HelloWorld.java
> javah HelloWorld
javah 명령을 실행하면, 아래에 보이는 HelloWorld.h가 생성되며, 이 파일을 수정하지 않는다.


[HelloWorld.h]
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    display
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_display
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


헤더 파일에서 강조된 부분은 실제 C 언어로 구현되어야 하는 함수이다. JNIEXPORT와 JNICALL과 같은 자바와 관련된 용어의 등장에서 알 수 있듯이, 헤더 파일에 정의된 함수를 구현하는 C 파일을 컴파일하기 위해서는 자바 시스템에서 포함되어 있는 파일이 필요하다. 환경설정에 관한 설명은 C 파일의 컴파일 단계에서 설명하기로 하고, C 언어로 위 함수를 구현해 보자.


[HelloWorld.c]
#include 
#include "HelloWorld.h"
#include 


JNIEXPORT void JNICALL Java_HelloWorld_display(JNIEnv *env, jobject this) {

    jfieldID       fldid;
    jstring        greeting;
    const   char   *temp;

    // 넘겨지는 객체 this의 클래스를 가져온다.   
    jclass instance = (*env)->GetObjectClass(env,this);

    printf("   Start of C implementation.\n");
    
    if ( this == NULL ) {
        fprintf(stderr, "Input pointer is null!");
        return;
    }
    
    // 클래스 안에서 greeting이라는 속성이 가진 위치를 가져온다.
    fldid = (*env)->GetFieldID(env, instance, "greeting", "Ljava/lang/String;");
    
    if ( fldid == NULL ) {
        fprintf(stderr, "Failed to get the field id\n");
        return;
    } 
    
    // 실제 객체에서 fldid에 있는 값을 읽어온다.
    greeting  = (*env)->GetObjectField(env, this, fldid);
    // 일어온 값을 C 데이터로 변환한다.
    temp      = (*env)->GetStringUTFChars(env, greeting, NULL);
   
    printf("      %s \n", temp);

    // 자바의 String 필드에 저장할 값을 만든다.
    greeting = (*env)->NewStringUTF(env, "Hello C World");
   
    if ( greeting == NULL ) {
        printf("Out of memory\n");
        return;
    }
    
    // greeting에 저정돤 값을 자바 객체로 전달한다.
    (*env)->SetObjectField(env, this, fldid, greeting);

    printf("   End of C implementation \n");
   
    return;

}


이제 필요한 모든 파일을 갖추어졌다. 이제 컴파일하고 실행하는 일만 남아 있다. 컴파일 하기 위해서는 몇 가지 설정이 필요하다. 앞으로 기술되는 설정은 윈도우를 기준으로 하고 있고, 마이크로 소프트 C 컴파일러를 쓴다는 가정하에서 기술되었다. 먼저, jni.h가 필요하다. 이 파일은 자바 홈디렉토리(이하 JAVAHOME) 아래 include 디렉토리에 있으며, 그 아래에 있는 win32 디렉토리도 include에 포함되어 있어야 한다.

set INCLUDE=%JAVAHOME%\include;%JAVAHOME%\include\win32;%INCLUDE%

LIB 속성을 위해서는 JAVAHOME 아래에 있는 lib 디렉토리가 포함되어야 한다.

set LIB=%JAVAHOME%\lib;%LIB%

위에서 설명한 설정은 JNI를 사용하게 됨에 따라 추가적으로 해야 하는 설정이며, Command Prompt에서 VC7 컴파일러를 실행하고자 할 때는 다음의 설정이 추가적으로 필요하다.

INCLUDE : VC 홈 디렉토리 아래에 있는 include 디렉토리
LIB : VC 홈 디렉토리 아래에 있는 lib 디렉토리
PATH : Studio 홈 - Common7 아래에 있는 IDE, VC 홈 디렉토리 아래에 있는 bin 디렉토리.

이제 남은 일은 HelloWorld.c를 컴파일하고 HelloWorld를 실행하는 일만 남았다.

cl HelloWorld.c -Fehello.dll -MD -LD

위의 명령어를 실행하면 hello.dll 파일이 생성된다. 그리고 나서 HelloWorld 클래스를 실행하면 다음과 같은 결과를 얻게 될 것이다. 


Hello Java World
   Start of C implementation.
      Hello Java World
   End of C implementation
Hello C World


이것으로 첫 번째 예제가 성공한 것이다. 이 예제는 일반적으로 JNI가 사용되는 방식과 거리가 있어 보인다. JNI는 개발 중인 자바 어플리케이션이 있으며, 이미 개발된 Native 라이브러리가 존재한다. 두 번째 예제에서는 기존에 C 언어로 개발된 정수 정렬 함수를 자바에서 사용하는 예제이다. 

먼저 Sort.c를 살펴보자. 흔히 볼 수 있는 C로 구현된 Insertion 정렬 함수이다.


[Sort.c]
#include 
#include 

int *sortIntegers(int *A, int n) {
    
    int i   = 0;
    int j   = 0;
    int key = 0;
    
    for ( j=1; j < n ; j++ ) {
        key = A[j];
        i = j - 1;
        while ( i >=0 && A[i] > key ) {
            A[i+1] = A[i];
            i--;
        }
        A[i+1] = key;
    }
    
    return A;
    
}


Sort.h 에는 Sort.c 안에 구현된 함수의 리스트가 나열되어 있다.


[Sort.h]
int *sortIntegers(int *A, int n);


이제 이 함수의 기능을 사용하는 Sorting.java 를 작성하자.


[Sorting.java]
public class Sorting {

    public static native int[] sort(int[] A, int n);
    
    public static void main(String args[]) {
        
        int Input[] = {3,4,7,2,5,6,9,11,1,8,10};
        
        int sorted[] = sort(Input, Input.length);
        
        for (int i=0; i < sorted.length ; i++ ) {
            System.out.println("A[" + i + "] = " + sorted[i]  );
        }
    }
    
    static {
        System.load("D:\\Project\\Java\\sorting.dll");
    }
    
}


Sorting.java를 컴파일 하고, Sorting 클래스를 사용하여 Sorting.h 를 생성한다. 이제 남은 일은 HelloWorld.c 와 같은 입력값과 반환값을 전달해주는 C 프로그램이다. 


[Sorting.c]
#include 
#include "Sorting.h"
#include "Sort.h"
#include 

JNIEXPORT jintArray JNICALL Java_Sorting_sort(JNIEnv *env, jclass cl, jintArray arr, jint n) {

    jint       *A   = NULL;
    jintArray  temp = NULL;
    
    A = (*env)->GetIntArrayElements(env, arr, NULL);
    
    if ( A == NULL ) {
        fprintf(stderr, "Failed to get int array\n");
        return arr;
    }
    
    A = sortIntegers(A, n);
    
    temp = (*env)->NewIntArray(env, n);
    
    (*env)->ReleaseIntArrayElements(env, temp, A, 0);
    
    return temp;

}


Sorting.c 를 컴파일하자. 이 때 다음과 같은 에러가 발생한다면, 

“LINK : warning LNK4098: defaultlib 'LIBC' conflicts with use of other libs; use /NODEFAULTLIB:library”

Sorting.c 와 Sort.c가 다른 방식으로 컴파일 되었기 생긴 에러이다. Sort.c를 MD 옵션으로 다시 컴파일하면, 정상적으로 컴파일될 것이다. 이제 Sorting 클래스를 사용할 수 있다. Sorting 클래스를 실행하면 다음과 같은 출력을 보게 될 것이다.

A[0] = 1
A[1] = 2
A[2] = 3
A[3] = 4
A[4] = 5
A[5] = 6
A[6] = 7
A[7] = 8
A[8] = 9
A[9] = 10
A[10] = 11


필자는 여기에서 어떻게 Wrapper 프로그램을 작성하는지에 대해서 설명하지 않았다. 하지만 앞에서 다룬 두 예제에는 Primitive 타입인 int, 배열, 객체를 다루었기 때문에 각 경우에 해당되는 함수 리스트만 있으면, 프로그램에 별 어려움이 없으리라 생각된다. 다음 기사에는 JNI가 자바 가상 머신을 날려버려 전체 자바 어플리케이션이 다운되는 것을 피해가는 방법에 대해서 살펴보도록 하자.
Posted by 삼스
Android/App개발2011. 6. 3. 16:42
http://sites.google.com/site/endihom/home/programming-language/android/article/listview-backgrounds

ListView 는 안드로이드에서 가장 널리 사용되는 위젯이다. 사용하기 쉽고, 매우 유연하며 강력하다. ListView는 가끔 이해하기 어려울수 있다.

ListView에서 나타나는 가장 공통된 이슈중에 하나는 커스텀 백그라운드를 사용하고자 할때이다. 다른 안드로이드 위젯처럼 디플트로 ListView는 반투명 배경을 가질수 있는데 이는 개발자가 매우 어두운 회색(현재 다크 테마에서는 #FF191919)디폴트 윈도우 배경을 통해 볼수 있다는 것을 뜻한다. 게다가 ListView는 fading edges를 기본적으로 제공한다. 아래의 스크린샷의 윗 부분을 보면 첫번째 아이템이 점차 검은색으로 페이드 되는것을 알수 있다. 이 기술은 스크롤되는 컨테이너를 가지는 시스템에서 전반적으로 사용된다.

Android's default ListView

패이드 효과는 Canvas.saveLayerAlpha() 와 Porter-Duff Destination Out blending 모드의 조합으로 구현되어 진다.

불행하게도, ListView에서 커스텀 배경을 사용하거나 윈도우의 배경을 변경할때 화면이 이상하게 보일 것이다. 아래 두개의 스크린샷은 윈도우 배경을 변경했을때 어플리케이션에서 어떤일이 일어나는지를 보여준다. 첫번째 화면은 디폴트로 리스트가 어떡해 보여지는지를 볼수 있고 두번째 화면은 터치 제스쳐를 통해 초기화되어 스크롤되는 동안의 모습이다.

Dark fade Dark list

이 랜더링 이슈는 안드로이드 프레임워크가 ListView의 모든 인스턴스에 기본적으로 적용된 최적화 기법에 의한 것이다. 앞서 언급한데로 페이드 효과는 Porter-Duff blending mode를 사용해 구현되었다. 이 구현방법은 정말 잘 동작하지만 화면에 보이지 않는 비트맵에서 렌더링의 일부분을 캡쳐해야 함으로서 매우 고비용이며 드로잉 성능을 저하시킬수 있다. 그래서 추가적인 blending(메모리로부터 다시 읽어 들이는 것을 의미)이 필요하다 

ListView는 거의 대부분 고정 배경화면에서 디스플레이되기 때문에 고비용이 드는 작업으로 내려갈 이유가 없다. 이것이"cache color hit"라 불리는 최적화를 소개하는 이유다. 캐쉬 컬러 힌트는 윈도우의 배경색에 기본적으로 설정되어 있는 RGB색으로 안드로이드 dark테마에서 #191919이다. 이 힌트가 설정되면 ListView(사실 이 클래스의 부모 클래스)는 단일 배경화면으로 그릴 것이므로 고비용의 saveLayerAlpha()와 Porter-Duff 렌더링을 단순한 그레디언트로 대체할수있다는 것을 알게 된다. 이 그레디언트는 완전히 투명한 컬러에서 캐시 컬러 힌트 값까지 그레디언트 되고 이미지 위에서 리스트 바닥에 dark 그레디언트된 보고자 하는 바로 그것이다.

앞서 언급한데로 Listview와 안드로이드 UI 툴킷의 모든 기본 위젯들은 기본적으로 투명/반투명 배경을 가지진다. 이것은 ListView가 child를 다시 그릴때 윈도우 배경과 child를 반드시 혼합(blend)해야한다는 것을 뜻한다. 다시 한번 말하지만, 이것은 고비용의 메모리 다시 읽기를 초래하며 이 메모리 다시 읽기는 초당 수십번의 다시 그리기가 발생하므로 스크롤이나 플리핑하는동안 성능이 매우 떨어지게된다.

스크롤하는 동안 그리기 성능을 향상 시키기 위해 안드로이드 프레임워크는 cache color hint를 재사용한다. 이 힌트가 설정되면 프레임워크는 힌트값(scrolling cache라는 또다른 최적화 기법이 적용되지 않은 상태라고 가정하자)으로 채워진 비트맵안 리스트의 child를 복사한다. 그럼 다음 ListView는 화면에 비트맵을 직접 그린다. 비트맵은 불투명하므로 혼합(blending)이 필요하지 않다. 기본 cache color hint는 #191919이므로 스크롤 되는 동안 각 아이템의 뒤에서 dark 배경색을 볼수 있다.

만약 단일 배경색을 가지지 않거나 hint를 적절한 단일 컬러 값으로 설정한다면 이 이슈를 해결하기 위해 cache color hint 최적화 방법을 꺼 두어야 한다. 코드(setCacheColorHint(int) 참고)나 XML을 적절히 사용(android:cacheColorHint 속성)하여 이 작업을 수행할 수 있다. 최적화를 꺼두기 위해 단순히 투명색 #00000000을 사용하라. 아래의 화면은 XML 레이아웃 파일에 android:cacheColorHint="#00000000"으로 설정한 리스트를 보여준다.

Fade on a custom background

보는것 처럼 나무느낌의 배경화면에서 페이드 효과는 완벽하게 동작한다. cache color hint 기능은 최적화가 어떤 상황에서 개발자를 매우 힘들게 할수도 있으므로 흥미롭다. 하지만 이 특별한 상황에서 기본 동작의 이점은 증가된 복잡도 보다 더 크다.


Posted by 삼스