간단한 예제:

1. onCreate 에서 

requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
            
        setContentView(R.layout.main);  // setContentView 가 반드시 이 위치에 있어야 정상 동작함.
            
        getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);

// title bar 에 사용자 정의된 것들임.
        imageview = (ImageView)findViewById(R.id.favicon);
        textview2 = (TextView)findViewById(R.id.right_text);
        progress1 = (ProgressBar)findViewById(R.id.progress2);
            
        imageview.setImageResource(R.drawable.app_web_browser_sm);


2. custom_title.xml


<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/screen" android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <ImageView android:id="@+id/favicon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:scaleType="center" />
   
 
<ProgressBar android:id="@+id/progress2"
            style="?android:attr/progressBarStyleHorizontal"
            android:gravity="center_vertical"
            android:paddingRight="5dip"
            android:layout_width="200dip"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/favicon"/>
        
    <TextView android:id="@+id/right_text"
        android:gravity="center_vertical"
        style="?android:attr/windowTitleStyle"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent" android:paddingRight="5dip"
        android:layout_toRightOf="@id/favicon"/>

 
<ImageButton 
android:id="@+id/stop"
android:layout_width="30dip"
         android:layout_height="fill_parent"
        android:src="@drawable/icon"
         android:layout_toRightOf="@id/progress2"
        />
 
</RelativeLayout>

3. snapshot

title bar 만 보면 imageview, progressbar, textview, imagebutton 으로 사용자 구성이 된 것임.



'안드로이드' 카테고리의 다른 글

Fingerprint 추적 방법  (0) 2011.01.27
디바이스의 고유 정보값 얻어오기  (0) 2011.01.27
웹킷 브라우저  (0) 2011.01.27
안드로이드 2.0 Service API 변화  (0) 2011.01.27
Handler  (0) 2011.01.27

안드로이드에 내장된 브라우저는 구조가 굉장히 복잡하기 때문에 android.webkit이라는 독립적인  담겨있다. 그리고 WebView라는 위젯을 사용해 간단하게 화면에 HTML을 표시하는 정도로  수 있고, 아니면 들어 있는 기능을 총동원해 강력한 브라우징 기능을  수도 있다.

 

1. 단순한 브라우저

   간단한 기능만 사용한다면 WebView 역시 안드로이드의 다른  별반 다를게 없다. 그저 레이아웃에 추가하고, 자바 코드에서 URL만 지정해주면  URL의 내용을 바로 표시해준다.

 

       layout/main.xml의 예제

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
     >
     <WebView android:id="@+id/webkit"
            android:layout_width="fill_parent" 
              
      />
</LinearLayout>

        자바코드 예제

import android.app.Activity;
import android.os.Bundle;
import 

public class BrowserDemo1 extends Activity {
      WebView  
 
      @Override
      public void onCreate(Bundle  {
           super.onCreate(icicle);
          setContentView(R.layout.main);
           browser=(WebView)findViewById(R.id.webkit);
  
           browser.loadUrl("http://commonsware.com");
    }
}

           사용할 수 있는 권한을 확보하도록 AndroidMainifest.xml 파일을 수정해야 한다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.commonsware.android.webkit">
 <uses-permission android:name="android.permission.INTERNET" />
 <application>
 <activity android:name=".BrowserDemo1" android:label="BrowserDemo1">
   <intent-filter>
    <action android:name="android.intent.action.MAIN" />
   <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>
 </application>
</manifest>

         - WebView 위젯에서는 자바스크립트가 기본적으로  있다. 자바스크립트를 동작하도록 활성화하려면 WebView 인스턴스의 getSettings().setJavaScriptEnabled(true) 메소드를 호출하면 된다.

 

2. 원하는 내용 표시

    WebView 안에 원하는 내용을 표시하는 방법 두가지

   - loadUrl() 메소드를 사용해 WebView에 URL을 넘겨주고 해당하는 페이지의  가져와 표시하도록 하는 방법

    - loadData() 메소드에는  HTML을 직접 전달한다.

 

    WebView 위젯을  좋은 경우

    - 애플리케이션 패키지와 함께 설치된  화면에 표시하고자 할 때

    - 다른 작업을  결과로 받아온 HTML 형태의 문자열을 표시할 때

     ex) Atom 피트에서 뽑아낸 본문 내용을 화면에 표시할 때

   - 안드로이드에 내장된 위젯 대신 HTML 만으로 애플리케이션 화면을  경우

 

    loadData() 메소드 설명

   - loadData() 메소드의 가장 간단한 형태는 본문내용, MIME 타입,  인코딩 등의 내용을 모두 문자열로 지정한다.(MIME 타입은 text/html이며, 인코딩은 UTF-8을 

 

  browser.loadData("<html><body>Hello, world!</body></html>",
      "text/html", "UTF-8");

3. 브라우저 내비게이션

   WebView에는 기본적으로 브라우저 내비게이션 툴바가 포함돼 있지  앞으로, 뒤로 등의 기능을 갖고 있는 내비게이션 툴바를 별로 사용할  없거나 화면을 최대한 활용해야 하는 경우에는 툴바가 없는 편이 낫다.  브라우저 내비게이션 기능을 사용자에게 제공하려면 내비게이션 기능과 관련된 인터페이스를 개바자가  만들어야 하는 단점도 있다.

 

   WebView 위젯에는  같은 다양한 브라우져 내비게이션 기능이 내장

   - reload() 메소드 : 현재 표시하는 페이지 내용을 새로 고침

   -  메소드 : 브라우저 URL 기록 가운데 바로 이전 주소로 되돌아간다.

   - goForward() 메소드 : 브라우저 URL 기록 가운데 바로  주소로 나아간다.

   - goBackOrForward() 메소드 : 브라우저의 URL  앞뒤 원하는 방향으로 이동할 수 있다. 넘겨주는 값이 0보다 작으면  숫자만큼 뒤로 돌아가고, 0보다 크면 해당 숫자만큼 앞으로 나아간다.

  - canGoBackOrForward() 메소드 : 넘겨주는 숫자값만큼 브라우저 URL 기록을 따라  수 있는지 확인한다.

   - clearCache() 메소드 : 브라우저가  갖고 있던 파일을 제거한다.

   - clearHistory() 메소드는  URL 기록을 제거한다.

 

4. WebViewClient

   WebView 위젯을 사용해 애플리케이션 내부 데이터를 표현하려면,  표시된 HTML 링크를 플릭하는 등의 상황에서 제어권을 넘겨받아야 할 필요가 

    - 특정 작업에 대한 제어권을 확보하려면  인스턴스를 하나 생성해 WebView 위젯의 setWebViewClient() 메소드에 넘겨줘야 한다.

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 java.util.Date;

public class BrowserDemo3 extends Activity {
     WebView browser;
 
      @Override
      public  onCreate(Bundle icicle) {
            super.onCreate(icicle);
           setContentView(R.layout.main);
           browser=(WebView)findViewById(R.id.webkit);
            browser.setWebViewClient(new Callback());
 
            loadTime();
    }
 

    // 현재 시각을  HTML 형태로 생성해 WebView 위젯에 적용
    void  {
            String page="<html><body><a href=\"clock\">"
                                  +new Date().toString()
                                  +"</a></body></html>";
      
           browser.loadDataWithBaseURL("x-data://base", page,
                                                      "text/html", "UTF-8", null);
     }

 

    private class Callback extends WebViewClient {

        // 링크 클릭 이벤트 처리

         public boolean shouldOverrideUrlLoading(WebView view, String url)  
                loadTime();
   
               return(true);
        }
    }
}

5. 설정과 

    데스트탑에서 사용하던 웹브라우저를 보면 각종  옵션이 가득하다. 수많은 설정이나 옵션과 툴바 컨트롤 사이에서 기본 글꼴을  자바스크립트의 동작 상태를 지정하는 등 다양한 기능에 대해 설정값을 지정할  있다.

 

    WebSetting 클래스의 설정 메소드

     - setDefaultFontSize() 메소드 : setTextSize() 메소드를 사용해 기본  크기를 지정할 수 있다.

     - setJavaScriptEnabled() 메소드  자바스크립트를 켜거나 끌 수 있다.

     - setUserAgent()  : 0값을 넘겨주면 WebView에서 대상 웹사이트에 스스로가 휴대폰용 브라우저라는 사실을  통해 알려준다.


출처 : http://nopd.textcube.com/?indexTimestamp=1264487399&c=5

'안드로이드' 카테고리의 다른 글

디바이스의 고유 정보값 얻어오기  (0) 2011.01.27
타이틀 바 꾸미기  (0) 2011.01.27
안드로이드 2.0 Service API 변화  (0) 2011.01.27
Handler  (0) 2011.01.27
hardware keyboard의 존재 여부 및 상태 확인  (0) 2011.01.27

개인적으로 Service 는 사용하는 일은  관리가 어렵고 생각 못한 문제가 많이 발생해서 서 늘 고생하고 있는 부분이였습니다만, 구글 쪽에서도 관련되어 여러가지 고민이 많았는지, 2.0 이후 부터는 Service API 에 많은 변화가 생겼네요. 구글 Android Developer 블로그에 Android 2.0 에서사 변경된 Service API 에 관한 글이 있어서 번역해 보았습니다. 원래 번역글을 블로그에 올려두고 있었습니다만, 안드로이드 펍 분들에게도 혹시 도움이 되지 않을까 해서 올려봅니다.

원문 : http://android-developers.blogspot.com/2010/02/service-api-changes-starting-with.htm


Service API changes starting with Android 2.0
  • Service.setForeground() 가 더 이상 작동하지 않음. (Deprecated)
  • 상황에 따라 더 이상 필요하지 않은 Service 가 죽지 않는 경우가 많았는데, 이런한 에외사항을 보다 잘 처리할 수 있도록 새로운 API 를 제공함.
  • 사용자들이 현재 시스템에서 작동 중인 Service 를 확인하고 관리할 수 있는 UI 를 제공.
Background on services

  서비스 API 는 어플리케이션이 백그라운드에서 특정 작업을 수행할 수 있게 해주는.안드로이드의 핵심 매커니즘의 하나이다. 안드로이드 플랫폼 특성상, 화면상에 표시되지 않은 개별 어플리케이션은 시스템이 종료시키고, 필요한 경우 다시 시작할 수 있다고 여겨진다. 따라서 특정 어플리이션이 백그라운드에서도 특정 일을 수행할 필요가 있을 경우, Service 를 시작해야 하며, 시스템은 Service 를 구동중인 어플리케이션은 강제로 종료시키지 않는다. (최대한...)

   Service 는 매우 유용한 수단이지만, 언제나 권한에는 책임이 뒤 따른다. (power comes some responsibility). Service 를 가동하고 있는 어플리케이션은 시스템 자원을 소모기 때문에 전반적인 시스템 성능에 문제를 일으킬 수 있다. 따라서 개발자들은 정말로 필요한 경우에만 Service 를 사용해야 하며, 필요없는 Service 가 백그라운드에 남아있지 않도록 주의를 기울여야 한다. (서비스 릭 버그를 내지 말아야 한다...)

Redesigning Service.setForeground()

 Androi 1.6 안정화 기간동에, 너문 많은 어플리케이션이 Service.setForeground() API 를 사용하기 때문에 발생하는 문제들이 발견되었다.  이 API가 호출 되면,  시스템은 특정 Service가 구동중인 프로세스가 포그라운드 프로세스로 간주한다. (우선 순위 부여)  따라서, 시스템이 메모리 부족등의 예외 상황을 처리하게 매우 어렵게 만든다. 

  이러한 문제점이 발견된 시점이 너무 늦었기에, 1.6 버전 SDK는 이 API에 대하여 큰 변화 없이 공개되었지만, 2.0 에서는 중요한 변경사항이 이루어졌다. Service.setForeground() 는 이제 작동하지 않는다. 따라서 기존의 API 를 대체해서 2.0 버전에서는 두가지  새로운 API 가 추가되었다. 이제 Service 가 포그라운드로 진입하고자 할  경우, 사용자가 해당 사실을 확인하고 필요한 경우, 포그라운드에서 작동중인 서비스를 정지 시킬 수 있도록, Notification 을 보내야 한다. (주> 음악 재생 서비스 와 같이, 상태바에 특정 메세지를 표시하도록...)
 
public final void startForeground(int id, Notification notification);

public final void stopForeground(boolean removeNotification);
 
 시스템은 Service 가 포그라운드에서 작동 중이라면, 어떠한 형태든 Notification 이 남아 있음을 확신 할 수 있고 따라서, Service 와 연관된 Notification 상태를 쉽게 관리 할 수 있다.  당연히, 많은 개발자들이 2.0 뿐만 아니라 이전 플랫폼에서도 작동하는 Service 코드를 작성하고 싶어한다. 아래와 같이 가능한 경우에 한해서 새롭게 추가된 API 를 호출하는 방법으로 예전 버전과 2.0 이후 버전에서 모두 작동하는 코드를 작성 할 수 있다.

private static final Class[] mStartForegroundSignature = new Class[] {
int.class, Notification.class};
private static final Class[] mStopForegroundSignature = new Class[] {
boolean.class};

private NotificationManager mNM;
private Method mStartForeground;
private Method mStopForeground;
private Object[] mStartForegroundArgs = new Object[2];
private Object[] mStopForegroundArgs = new Object[1];

@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
try {
mStartForeground = getClass().getMethod("startForeground",
mStartForegroundSignature);
mStopForeground = getClass().getMethod("stopForeground",
mStopForegroundSignature);
} catch (NoSuchMethodException e) {
// Running on an older platform.
mStartForeground = mStopForeground = null;
}
}

/**
* This is a wrapper around the new startForeground method, using the older
* APIs if it is not available.
*/
void startForegroundCompat(int id, Notification notification) {
// If we have the new startForeground API, then use it.
if (mStartForeground != null) {
mStartForegroundArgs[0] = Integer.valueOf(id);
mStartForegroundArgs[1] = notification;
try {
mStartForeground.invoke(this, mStartForegroundArgs);
} catch (InvocationTargetException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke startForeground", e);
} catch (IllegalAccessException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke startForeground", e);
}
return;
}

// Fall back on the old API.
setForeground(true);
mNM.notify(id, notification);
}

/**
* This is a wrapper around the new stopForeground method, using the older
* APIs if it is not available.
*/
void stopForegroundCompat(int id) {
// If we have the new stopForeground API, then use it.
if (mStopForeground != null) {
mStopForegroundArgs[0] = Boolean.TRUE;
try {
mStopForeground.invoke(this, mStopForegroundArgs);
} catch (InvocationTargetException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke stopForeground", e);
} catch (IllegalAccessException e) {
// Should not happen.
Log.w("MyApp", "Unable to invoke stopForeground", e);
}
return;
}

// Fall back on the old API. Note to cancel BEFORE changing the
// foreground state, since we could be killed at that point.
mNM.cancel(id);
setForeground(false);
}

Service lifecycle changes

 Service 들이 필요하지 않은 경우에도 불구하고, 자신을 포그라운드 프로세스로 설정하는 문제를 제외하고도,  백그라운드에서 너무 많은 수의 Service 들이 돌아감에 따라, 각각의 Service 들이 한정된 메모리 자원을 차지하게 위해 서로 경쟁하게 되면 또 다른 문제가 발생할 수 있다. 안드로이드 플랫폼과 Service 간의 상호 작용 과정에서, 특정 어플리케이션이 코드상으로는 별다른 문제 없이 작성된 경우에도, Service 가 정상 종료되지 않고 백그라운드에 남아있을 수 있다. 예를 들어 다음과 같은 상황이다.
  1. Application 이 StartService 를 호출 한다.

  2. Service 의 onCreate, onStart() 함수가 호출 되고, 특정한 작업을 수행하기 위해 백그라운드 스레드가 생성된다.

  3. 시스템이 메모리 부족으로 인해 현재 작동중인 서비스를 강제로 종료 한다.

  4. 잠시 후에, 메모리 상황이 호전되어 Service 가 재시작한다. 이 때, Service 의 onCreate 함수는 호출 되지만, onStart 는 호출 되지 않는다. (startService 는 호출 되지 않았음으로..)

 
 결국, Service 는 생성된 채로 남아있게 된다. Service 는 어떠한 일을 수행 하지도 않으며 (무슨 일을 수행해야할 지 알 수 없음으로...), 어떤 시점에 종료되어야 하는지도 알 수 없다.

 이러한 문제를 해결하기 위해 안드로이드 2.0 에서 Service 의 onStart() 는 더이상 사용되지 않는다. (이전 버전과의 호환성 문제때문에 작동 자체는 하지만...) 대신 Service.onStartCommand() 콜백이 사용된다. 이 콜백 함수는 시스템이 Service 를 좀 더 잘 관리 할 수 있도록 해주는데, 기존 API 와는 달리  onStartCommand() 는 결과값을 반환한다. 안드로이드 플랫폼은 이 결과값을 기반으로 만일 작동중인 서비스가 강제로 종료될 경우, 어떠한 일을 수행해야 하는지 판단한다.
  • START_STICKY  는 기본적으로 이전과 동일하게 작동한다. 이전과의 차이점은 다음과 같다.  기존에 프로세스가 강제 종료된 후 Service 가 재 시작 될 때, onStart() 콜백이 호출 되지 않았지만, START_STICKY 형태로 호출된 Service 는 null Intent 가 담긴 onStartCommand() 콜백 함수가 호출된다. 이 모드를 사용하는 Service 는 null Intent 로 onStartCommand() 콜백 함수가 호출되는 경우를 주의깊게 처리해야 한다. 
  • START_NOT_STICKY 모드로 시작된 Service는  안드로이드 플랫폼에 의해 프로세스가 강제로 종료되는 경우, 다시 시작되지 않고 종료된 상태로 남게된다. 이러한 방식은 특정 Intent 로 주어진 명령을 수행하는 동안에만 Service 가 실행되면 되는 경우에 적당하다 예를 들어, 매15분마다 네트워크 상태를 체크하기 위해 실행되는 Service 를 생각해 보면, 만일 이 Service 가 작업도중 강제로 종료될 경우, 이 Service 는 재시작하기 보다는 정지된 상태로 남겨두고, 15분 후에 새롭게 시작되도록 하는 것이 최선이다. 
  • START_REDELIVER_INTENT  는 START_NOT_STICKY 와 유사하다. 단, 프로세스가 강제로 종료되는 경우 (stopSelf() 가 호출 되기 전에 종료되는 경우), Intent 가 다시 전달 되어 Service 가 재시작 한다.  (단, 여러차례 시도한 후에도, 작업이 종료되지 않으면, Service 는 재시작되지 않는다.) 이 모드는 전달받은 명령을 반드시 수행해야 하는 Service 에 유용하다. 
 기존에 존재하는 어플리케이션과의 호환성을 위해서, 이전 버전을 기준으로 제작된 어플리케이션은  START_STICKY_COMPATIBILITY 모드로 작동한다.  (null intent 를 보내지 않고 기존과 동일하게 작동). 단 API 버전 5  이상을 기준으로 작성된 어플리케이션들은 기본적으로 START_STICKY 모드로 작동 하며, 개발자는 반드시 onStart() 나 onStartCommand() 가 null intent 를 가지고 호출되는 경우를 염두해 두어야 한다. 

 개발자들은 아래에 제시된 코드를 활용하여, 손쉽게 이전 버전과 새로운 버전 양쪽 모두에서 작동하는 Service 를 구현할 수 있다. 

    // This is the old onStart method that will be called on the pre-2.0
    // platform.  On 2.0 or later we override onStartCommand() so this
    // method will not be called.
    @Override
    public void onStart(Intent intent, int startId) {
        handleStart(intent, startId);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handleStart(intent, startId);
        return START_NOT_STICKY;
    }

    void handleStart(Intent intent, int startId) {
        // do work


New "running services" user interface

 * 이 후 이어지는 내용은 새롭게 추가된 Running Service 를 확인 할 수 있는 UI 어플리케이션에 관한 내용이라 대충 대충 건너 띄도록 하겠습니다.

 안드로이드 2.0 부터는 새롭게 "Running Services" Activity 가 어플리케이션 세팅에 추가 되었다. 사용자는 목록에 표시된 Service 를 터치해서 종료할 수 있다.

 화면 하단부에는 현재 시스템의 메모리 상황에 대한 정보가 표시된다. 

  • Avail: 38MB+114MB in 25 은 현재 38MB 의 공간이 바로 사용가능하며, 114MB 의 메모리 공간은 언제든지 종료시킬 수 있는 25개의 백그라운드 프로세스에 의해 점유되고 있음을 알려준다. 
  • Other: 32MB in 3 은 현재 3개의 임의로 종료되서는 않되는 포그라운드 프로세스에 의해 32MB 의 공간이 사용되고 있음을 알려준다. 
 대부분의 사용자들에게 이 새로운 UI 는 기존의 Task Killer 어플리케이션에 비해 사용하기 편리하며, 강제로 Service 를 종료시키는 것이 아니기 때문에 문제를 일으킬 소지가 훨씬 적다. 

 개발자들에게는 자신이 작성한 어플리케이션이 잘 작동하는지 (특히 Service 관리라는 측면에서) 확인 할 수 있게 해주는 유용한 툴이 될 수있다. 또한 어플리케이션이 구동한 Service 가 사용자에 의해 어느시점에서든지 종료 할 수 있다는 점을 염두해 두어야 한다. 안드로이드의 Service 는 매우 강력한 도구이지만, 어플리케이션 개발자들이 전체 폰의 성능에 큰 악영향을 끼칠 수 있는 문제를 가장 많이 일으키는 도구이기도 한다. 


 
 

'안드로이드' 카테고리의 다른 글

타이틀 바 꾸미기  (0) 2011.01.27
웹킷 브라우저  (0) 2011.01.27
Handler  (0) 2011.01.27
hardware keyboard의 존재 여부 및 상태 확인  (0) 2011.01.27
안드로이드 개발 팁  (0) 2011.01.27

Handler란?

  한 스레드는 그 내부의 연산만 가능하며 다른 스레드의 UI를 건드릴 수 없습니다.
 그런데 만약 스레드들이 서로 영향을 줄 수 없다면 스레드의 존재 이유가 없을 것입니다.

  이를 해결하기 위해서 서로 다른 스레드 간의 참조를 위해서 스레드 간에 통신할 
 수 있는 장치
를 만들었는데 그것이 핸들러[Handler]입니다.
 핸들러는 스레드 간에 메시지 객체나 러너블 객체를 통해 통신할 수 있는 장치이며,하나
 의 핸들러는 하나의 스레드와 관련을 맺습니다. 핸들러는 자신이 생성된 스레드에 짝이
 되며 다른 스레드와 통신을 수행하게 됩니다.

Handler의 메시지 수령
  핸들러에 메시지가 도착하게 되면 아래의 메서드가 호출됩니다.
 public void handleMessage(Message msg)
 인수로 메시지 객체를 전달 받는데 이는 스레드 간에 통신을 해야 할 내용에 관한 객체입
 니다. 몇가지 정보가 추가 될 수 있기 때문에 여러 개의 필드를 가지고 있습니다.

 메시지 필드 : 설명
 1. int what : Message의 ID
 2. int arg1 : Message가 보낼 수 있는 첫번째 정보.
 3. int arg2 : Message가 보낼 수 있는 두번째 정보.
 4 Object obj : Integer로 표현 불가능 할 경우 객체를 보냄.
 5. Messenger replyTo : 응답을 받을 객체를 지정.

Handler의 메시지 전송
  Message를 전송할 때는 다음의 메서드를 사용합니다.
 1. boolean Hanler.sendEmptyMessage(int what);
    - Message 의 ID에 해당하는 값을 전달할 때 사용함.
 2. boolean Handler.sendMessage(Message msg);
     - ID만으로 불가능하고 좀 더 내용이 있는 정보를 전송할 때 사용.
 3. boolean sendMessageAtFrontOfQueue(Message msg);
     - 메시지가 큐에 순서대로 쌓여서 FIFO(First In Frist Out)형태로 처리되지만,
       이 메서드를 사용하면 노래방에서 우선 예약 하듯이 사용가능합니다.

Handler의 발송과 수령 예제
//Handler 시작 부분
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        BackThread thread = new BackThread();
        thread.setDaemon(true);
        thread.start();
    }

//Handler Class
  class BackThread extends Thread {
 @Override
 public void run() {
  // TODO Auto-generated method stub
  while(true)
  {
   //처리 내용

   //핸들러 메세지 보내는 부분
   mHandler.sendEmptyMessage(0);
   try {
    Thread.sleep(1000);
   } catch (Exception e) {
    // TODO: handle exception
    ;
   }
  }
  };
  //핸들러를 받아서 처리할 내용
  Handler mHandler = new Handler() {
   public void handleMessage(android.os.Message msg)
   {
    if(msg.what == 0){
     //처리할 내용
    }
   };

 }
}


Handler의 객체 전송
  앞선 메서드로 특정 정보를 보낼 수 있지만 메시지를 보내는 대신에 객체를 보낼 수도
 있습니다.
 boolean post(Runnable r)
  핸들러로 다음의 매서드를 통해 Runnable 객체를 보내면 해당 객체의 run 메서드가
 실행됩니다. 이럴 경우 메시지를 받는 쪽은 다른 것을 정의하지 않고 핸들러만 정의하면
 해당 내용을 수행할 수 있습니다.

Handler의 객체 전송 예제
  class BackThread extends Thread {
 @Override
 public void run() {
  // TODO Auto-generated method stub
  while(true)
  {
   //처리 내용
   mHandler.post(new Runnable() {
    
    @Override
    public void run() {
     // TODO Auto-generated method stub
     // 객체를 받아서 처리할 내용
    }
   });
   try {
    Thread.sleep(1000);
   } catch (Exception e) {
    // TODO: handle exception
    ;
   }
  }
 }
 //핸들러를 받아서 처리할 내용
 Handler mHandler = new Handler();
}

클래스의 분리 시 메시지 교환
  앞선 예제처럼 스레드가 이너 클래스로 구현되었을 경우 멤버의 공유가 가능하지만 그렇
 지 않고 분리된 클래스의 경우에는 더 상세한 코드의 구현이 필요합니다.
  이경우 스레드는 전달 받은 핸들러를 자신의 멤버로 따로 저장해야 하고 (공유가 불가능
 하기 때문입니다.) 메시지 객체에 추가 정보를 저장해서 보내야 합니다.

클래스 분리시 메시지 교환 예제
  public class TestSampleProject extends Activity {
 
 int mManinValue = 0;
 int mBackValue = 0;
 TextView mMainText;
 TextView mBackTest;
 
 BackThread thread;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        thread = new BackThread(mHandler );
        thread.setDaemon(true);
        thread.start();
    }
   
    Handler mHandler = new Handler() {
     public void handleMessage(android.os.Message msg)
     {
      if(msg.what == 0)
      {
       //처리할 내용
      }
     };
    };
}

class BackThread extends Thread {
 Handler mHandler;
 
 public BackThread(Handler handler) {
  // TODO Auto-generated constructor stub
  mHandler = handler;
 }
 
 @Override
 public void run() {
  // TODO Auto-generated method stub
  Message msg = new Message();
  msg.what = 0;
  msg.arg1 = 1;
  mHandler.sendMessage(msg);
  try {
   Thread.sleep(1000);
  } catch (Exception e) {
   // TODO: handle exception
  }
 }
}

메시지 풀의 사용
  매번 핸들러를 구현할 때마다 new 연산자로 새로 생성한다면 메모리도 계속해서 사용할 것이고 그에 다른 가비지 컬렉션의 작업도 생길 것입니다. 이러면 속도도 느려지는 결과가 발생합니다.
이런 경우를 보완하고자 메시지 풀 이라는 임시 캐쉬를 유지시켜서 빠른 작업이 가능하게 합니다.
static Message obtain(message orig)
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)
 - obtain 메서드는 메시지 풀에서 비슷한 메시지가 있다면 이를 재사용하게 합니다.
void recycle()
 - 사용한 메시지를 풀에 집어 넣는 역할을 하는데 풀에 한번 집어 넣게 되면 시스템이 
   관리하므로 더 이상 관여할 수 없습니다.

메시지 풀의 사용 예제
...
class BackThread extends Thread {
 Handler mHandler;
 
 public BackThread(Handler handler) {
  // TODO Auto-generated constructor stub
  mHandler = handler;
 }
 
 @Override
 public void run() {
  // TODO Auto-generated method stub
  while(true)
  {
   //처리할 내용
   
   Message msg = Message.obtain(mHandler, 0 , 1 , 0);
   mHandler.sendMessage(msg);
   try {
    Thread.sleep(1000);
   } catch (Exception e) {
    // TODO: handle exception
   }
  }
 }
}


출처 : http://blog.naver.com/crowdark7?Redirect=Log&logNo=109201348


1. 존재 여부 확인
context.getResources().getConfiguration().keyboard
Configuration.KEYBOARD_NOKEYS : 없음
Configuration.KEYBOARD_QWERTY : qwerty keyboard 있음
Configuration.KEYBOARD_12KEY : 12key keyboard 있음

2. 사용 여부 확인 (h/w keyboard가 열려 있나?)
context.getResources().getConfiguration().hardKeyboardHidden
Configuration.HARDKEYBOARDHIDDEN_NO : 사용상태(열려 있음)
Configuration.HARDKEYBOARDHIDDEN_Y ES : 사용하지 않는 상태(닫혀 있음)

1. Touch mode에서도 Keypad를 쓸수 있게 하기위해
- View를 생성할때 setFocusableInTouchMode(true);를 호출 한다.

2. View mode를 Portrait 나 Landscape로 고정하고 싶을때
- Manifest의 Activity 에 android:screenOrientation="landscape"을 추가한다.

3. 슬라이드를 열고 닫을때 onDestroy()가 호출되지 않도록 하기위해
- Manifest의 Activity 에 android:configChanges="orientationkeyboardHidden" 을 추가한다.
- "orientationkeyboardHidden" 이 둘중에 하나라도 빠지면 슬라이드를 열때 onDesroy()가 호출된다.

4. Scoket 통신을 할수 있도록 하기 위해
- 퍼미션을 추가 한다. uses-permission android:name="android.permission.INTERNET"

5. 진동을 사용할수 있도록 하기 위해

- 퍼미션을 추가한다. uses-permission android:name="android.permission.VIBRATE" 

6. 안테나 영역 없에기
- Manifest의 Activity 에 android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"

7. Manifest의 versionName 갖고오기.
- mContext.getPackageManager().getPackageInfo("package name", 0).versionName

ex)

package="com.gamevil.android"
android:versionCode="1"
android:versionName="1.0.0">





android:label="@string/app_name"
android:configChanges="orientationkeyboardHidden"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">


17. Android 키보드 숫자형으로 시작하게 하기
edittext.setInputType(InputType.TYPE_CLASS_NUMBER);

16. Android listview 검정화면 없애기
android:cacheColorHint="#00000000"

15. Android 화면 고정하기
AndroidManifest.xml의 activity 속성중 screenOrientation을 다음과 같이 지정해준다.

//화면을 세로로 유지
<activity android:name=".Powermeter"
                  android:label="@string/app_name"
                  android:screenOrientation="portrait">
//화면을 가로로 유지
<activity android:name=".Powermeter"
                  android:label="@string/app_name"
                  android:screenOrientation="landscape">
자바 소스에서
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

14. Android Handler example
  // 시간주고 바로 뜨게 하기 20이 최소 가능 값..
        new Handler().postDelayed(new Runnable() {
            public void run() {
                openOptionsMenu();
            }
        }, 20); 

13. Android 소프트 키보드 끄기 및 보이기 및 숨기기
  // 키보드 오프시키는 소스..
  EditText et = (EditText)findViewById(R.id.menu6_e_number);
  et.setInputType(0); //가상키보드 오프

1. 보이게 하기

EditText et = (EditText)findViewById(R.id.moneyEdit);
et.setInputType(0); //가상키보드 오프
        
        et.setOnClickListener(new OnClickListener() {   
   public void onClick(View arg0) {    
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
          imm.showSoftInput(input, 0);
   }         
        });

InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
imm.showSoftInput(editText, 0);

2. 숨기기
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
imm.hideSoftInputFromWindow(myEditText.getWindowToken(), 0); 


12. Android  Edittext Hint, 흐리게 보이기
android:hint="ex) 안드로이드"


11. Android  EditText 숫자키만 허용하기

DigitsKeyListener digit = 
new DigitsKeyListener(true, true); // first true : is signed, second one : is decimal
digit.setKeyListener( MyDigitKeyListener );

위와같이 하거나 xml 에서

android:inputType="number"


10. Android  Option menu 실행 소스
openOptionsMenu();


9. Android Menu 만들기
- xml 소스
res/menu/menu.menu

<menu xmlns:android="http://schemas.android.com/apk/res/android">
 <item
  android:id="@+id/adjust"
  android:title="수정"
  android:orderInCategory="1" >
 </item>
 <item
  android:id="@+id/delete"
  android:title="삭제"
  android:orderInCategory="2" >
 </item>
</menu>

- 자바 소스
 // OptionMenu
 public boolean onCreateOptionsMenu(Menu menu){
  getMenuInflater().inflate(R.menu.del_adjust, menu);
  return true;
 }

    // OptionMenu click event
 public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
   case R.id.adjust:
    finish();
    return true;
    
   case R.id.delete:
    finish();
    return true;
  }
  return false;
 }

8. Android Dialog 만들기 (Android.Dialog.Builder())

기존에는 Activity에서 showAlert(()를 제공해줬나보다. (예제를 보니...)
더 이상 지원하지 않기 때문에 AlertDialog.Builder()를 이용하여 Dialog를 생성하였다.

*onClickLister() 생성시 반드시 DialogInterface.OnClickListener()라고 클래스를 적어줘야 한다. 
 (Activity에서 button 클릭 이벤트를 처리하기 위해 import한 View 클래스에 OnClickListener 메소드가 있다.)

a. Android  yes or no - 버튼 2개짜리
new AlertDialog.Builder(LoginMainActivity.this)
.setTitle("Login Data")
.setMessage("rosa : test") //줄였음
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int whichButton) {                                        
        //...할일
    }
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int whichButton) {
        //...할일
    }
})
.show();

b. Android  ok (or cancel)
new AlertDialog.Builder(LoginMainActivity.this)
        .setTitle("Login Data")
        .setMessage("rosa : test") //줄였음
        .setNeutralButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {                                        
                //...할일
            }
        })        
        .show();

c. Android   다른 layout 출력
new AlertDialog.Builder(LoginMainActivity.this)
.setTitle("list 예제")        
.setItems(R.array.listBtnArray, new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int selectedIndex) {        
        String[] list = getResources().getStringArray(R.array.listBtnArray);        
        new AlertDialog.Builder(LoginMainActivity.this)
        .setTitle("선택한 리스트 아이템")
        .setMessage(list[selectedIndex])
        .setNeutralButton("OK", new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub                        
            }                    
        });                
    }
    })
.show();

--
value 값으로 array 추가
<string-array name="listBtnArray">
        <item>One</item>
        <item>Two</item>
        <item>Three</item>
    </string-array>



7. Android  뒤로 가기 키
 onBackPressed();

6. Android  바이브레이터 
Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vibe.vibrate(500);
// 퍼미션        
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>

5. Android  밝기 셋팅
Window w = getWindow();
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = 0~1까지의 float 값;
w.setAttributes(lp);

-1.f를 주면 default 밝기로 세팅됩니다.

4. Android  화면 꺼지는것 막기
현재 Activity가 보여지고 있는 동안은 시간이 지나도
화면이 자동으로 꺼지지 않도록 합니다.
즉 단말이 슬립상태로 들어가지 않고 계속 화면을 켜놓습니다.

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

3. Android  toast
1-Type
Toast.makeText.(this, "토스트 메세지", Toast.LENGTH_SHORT).show();

2-Type
Context context = getApplicationContext();
String msg = "";
int duration = Toast.LENGTH_SHORT;
Toast.makeText(context, b, duration).show();


2. Android  View 백그라운드 색 변경
TextView a = null; 
a.setBackgroundColor(Color.WHITE);

1. Android  timer 이용
http://docs.androidside.com/docs/reference/java/util/TimerTask.html
--------------------------------------------------------------------------------------------------
모토로이 볼륨 올리고 내리는 버튼 키값을 알아내서
edittext 에 원하는 값을 넣는 방법 볼룸 위아래 버튼을 누르면
화면에 벨로시 볼륨 조절하는 창이 나타나는데 완전히 키값을 가로채는 방법 

et_editText.setOnKeyListener(new OnKeyListener() {
        
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
           // TODO Auto-generated method stub
           if(event.getAction() == KeyEvent.ACTION_DOWN){
              if(keyCode == 24 || keyCode == 25){
              et_editText.setText("1234567890");
              
              return true;
              }
           }
           return false;
        }
        
     });


- 전체적으로 필요한 기능
// 종료 후 재 부팅시 기능이 죽지않고 유지되게 하는 방법들..
http://www.androidpub.com/android_dev_qna/189549

- 박사마 만드는데 필요한 기능
// view에서  페이지 넘기는 기능
API Demos에서 Animation에 간단하게 Fade in, Zoom in 효과가 있네요.

내가 만드는 부분에 필요한 부분들
// 강제로 클릭을 발생시키는 이벤트
dispatchTouchEvent
// 좌표관련들..
getWidth()랑 getHeight()로 전체 좌표값을 얻어오고 %로 비율 정해서 좌표값 설정하세요

출처 : http://winchester.tistory.com/

텍스트 입력 창이 있는 경우 soft input을 자동으로 띄워주는 것이 UX 측면에서 좋다. 그래서 대부분 iPhone이나 Android의 app들은 자동으로 soft input을 띄워준다.


Android에서 activity가 시작될 때 자동으로 soft input을 보여주는 방법은 http://developer.android.com/guide/topics/manifest/activity-element.html#wsoft 에 잘 정리되어 있다.
그런데 여기 나온 방법으로 app을 만들어서 emulator에서 백날 돌려봐야 soft input이 나오지 않는다. 왜 그럴까? 이를 명확하게 설명해 놓은 공식 문서는 어디에도 없다.
그 이유는 http://groups.google.com/group/android-developers/browse_thread/thread/17210d784766602d?pli=1 의 대화내용들을 읽어보면 알 수 있다. 여기서 android framework engineer로 보이는 Dianne Hackborn이라는 사람이 다음과 같은 말을 하였다.

Typically the emulator runs as a device that has a keyboard, in which case 
the system deliberately does not automatically show the IME because the user 
has easy access to a real keyboard.  It is recommended to not defeat this 
behavior, resulting in an inconsistent behavior with the rest of the 
applications. 

그렇다. hardware keyboard가 있으면 manifest에 option을 아무리 줘도 soft input이 안 나올 수도 있다는 얘기다. 문서에는 이딴 설명 하나도 없다. android를 하면서 많이 느끼는 거지만 java가 정말 문서화 하나는 잘 했었구나라는 거다. 
대화 내용들을 쭉 읽어 보면 알겠지만 보통 hardware keyboard는 접혀 있는 상태인데 그런 경우는 당연히 soft input을 보여줘야 하는 거 아니냐고 따지는 사람도 있다. 내가 봐도 그렇다. 
뭐 하여튼 이 option이 제대로 동작하는지 확인하려면 emulator를 만들 때 hardware keyboard를 지원하지 않는 것으로 profile을 만들어야 한다. 그럼 잘 된다.

그런데, 난! 장비에 hardware keyboard가 있든 없든 자동으로 soft input을 보여주고 싶단 말이다! 보통 hardware keyboard가 사용 가능 상태로 되어 있으면 soft input을 띄워달라고 해도 시스템에서 알아서 안 띄운다. 고로 activity 시작할 때 강제로 soft input을 띄워달라고 코드를 넣어야 한다.

InputMethodManager manager = (InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
manager.showSoftInput(findViewById(R.id.text), 0);

onCreate에 위 코드를 넣고 실행해 보자. soft input이 뜰까? 안뜰까? 안뜬다. 오 지쟈스!!! 왜 안뜰까? 안타깝게도 onCreate 시점에는 activity를 만들고 있는 상황이라 이런 요청을 가볍게 씹어주신다. 어차피 activity가 화면에 보여지는 최초의 순간은 life cycle 상 onCreate -> onStart -> onResume 이 모두 끝나야 한다. (물론 저 life cycle 중간 중간에 잡다구리한 phase들이 더 있다) 고로 onResume에 나 조금 있다가 이것좀 해달라고 요청을 넣어야 한다.

@Override
protected void onResume() {
super.onResume();
final View view = findViewById(R.id.text);
view.postDelayed(new Runnable() {
public void run() {
InputMethodManager manager = (InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
manager.showSoftInput(view, 0);
}
}, 100);
emulator 실행 시 다음과 같이 해보자.

~/dev/android-sdk/tools/emulator -avd 에뮬레이터이름 -partition-size 1024 -nojni -no-boot-anim -netfast

partition-size 는 성능과 관계는 없지만 기본적으로 emulator의 phone storage가 굉장히 작기 때문에 이렇게 설정해 놓으면 넉넉하게 사용할 수 있다.

nojni 는 테스트를 해 본 것은 아니지만 개념상 jni library check를 하지 않는 것이기 때문에 실행 속도가 좀 빨라지지 않을까 싶다.

no-noot-anim 이 녀석은 emulator를 처음 구동할 때 나오는 animation 효과를 하지 않도록 설정하는 것으로 emulator의 구동이 눈에 띄게 확연히 빨라지는 것을 체험할 수 있다.

1. keyDispatchingTimedOut 에 의해서 발생되는 원인

 5초내에 키또는 터치등에 대한 입력의 반응이 없을 경우, BroadcastReceiver가 10초내에
 작업을 종료하지 않을 경우 발생합니다. 안드로이드 문서에서 확인하세요.
 http://developer.android.com/guide/practices/design/responsiveness.html


안드로이드에서는 기본 입력 장치가 Touch이다. 실제로는 몇가지 입력 장치를 위한 준비가 되어 있는 것으로 보이며 인터넷에서 마우스를 위한 패치된 코드가 있다.

 

http://gitorious.org/0xdroid/frameworks_base/commit/a236aff3fcfca4ae3e200de48bfbb76daf2b7bb9

 

코드 패치 하기 전에 aesop에서 안드로이드의 key/touch event dispatch 과정을 정리한 내용이 있는데 이를 참고하면 좀 더 이해가 쉬울 것 같다.

 

 

 

실제 패치하여 동작시키면 완벽한 것은 아니고 몇가지 문제점을 가지고 있다.

일단 수정된 사항 위주로 정리해 본다.

 

/frameworks/base/core/java/android/view/RawInputEvent.java

 

public class RawInputEvent {
    // Event class as defined by EventHub.
    public static final int CLASS_KEYBOARD = 0x00000001;
    public static final int CLASS_ALPHAKEY = 0x00000002;
    public static final int CLASS_TOUCHSCREEN = 0x00000004;
    public static final int CLASS_TRACKBALL = 0x00000008;
    public static final int CLASS_MOUSE= 0x00000010;    
    // More special classes for QueuedEvent below.
    public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000;

 

    ......

}

 

코드를 보면 알곘지만 keyboard, alphakey, touchscreen, trackball 을 기본 지원 장치로 준비해 두었다. 이 패치는 trackball의 내용을 수정하여 mouse에 대응하도록 작업되었다.

 

/frameworks/base/include/ui/EventHub.h

 

class EventHub : public RefBase
{
public:
    EventHub();
    
    status_t errorCheck() const;
    
    // bit fields for classes of devices.
    enum {
        CLASS_KEYBOARD      = 0x00000001,
        CLASS_ALPHAKEY      = 0x00000002,
        CLASS_TOUCHSCREEN   = 0x00000004,
        CLASS_TRACKBALL     = 0x00000008,
        CLASS_MOUSE         = 0x00000010
    };

 

    ......


 

java 소스와 c 소스를 모두 고쳐주어야 하기 때문에 위와 같이 두 곳에서 타입을 추가해준다.

 

/frameworks/base/libs/ui/EventHub.cpp

 

int EventHub::open_device(const char *deviceName)
{

    ......

 

    if (test_bit(BTN_MOUSE, key_bitmask)) {
        uint8_t rel_bitmask[(REL_MAX+1)/8];
        memset(rel_bitmask, 0, sizeof(rel_bitmask));
        LOGV("Getting relative controllers...");
        if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0)
        {
            if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) {
              if (test_bit(BTN_LEFT,key_bitmask) && 
                  test_bit(BTN_RIGHT,key_bitmask))
                device->classes |= CLASS_MOUSE;
              else

                device->classes |= CLASS_TRACKBALL;
            }
        }
    }
 

 

}

 

touchscreen의 경우 디바이스 드라이버로부터 전달되는 좌표 정보는 절대 좌표이지만 mouse나 trackball 의 경우는 상대적인 좌표 이동 정보를 받게 된다. mouse나 trackball의 좌표 처리를 REL_X와 REL_Y 조건을 비교하면 알 수 있는데 mouse와 trackball을 구분하기 위해서 BTN_LEFT, BTN_RIGHT의 유무도 체크하고 있다. (개인적인 생각으로 TRACK BALL도 추후에는 두 경우가 있을 것 같은데 구분하는 이유를 모르겠다.)

 

/frameworks/base/services/java/com/android/server/KeyInputQueue.java

 

    Thread mThread = new Thread("InputDeviceReader") {
        public void run() {

 

                        // Is it a key event?
                        if (type == RawInputEvent.EV_KEY &&
                                (classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
                                (scancode < RawInputEvent.BTN_FIRST ||
                                        scancode > RawInputEvent.BTN_LAST)) {
                            boolean down;
                            if (ev.value != 0) {
                                down = true;
                                di.mDownTime = curTime;
                            } else {
                                down = false;
                            }
                            int keycode = rotateKeyCodeLocked(ev.keycode);
                            addLocked(di, curTime, ev.flags,
                                    RawInputEvent.CLASS_KEYBOARD,
                                    newKeyEvent(di, di.mDownTime, curTime, down,
                                            keycode, 0, scancode,
                                            ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
                                             ? KeyEvent.FLAG_WOKE_HERE : 0));
                        } else if (ev.type == RawInputEvent.EV_KEY) {
                            if (ev.scancode == RawInputEvent.BTN_TOUCH &&
                                    (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
                                di.mAbs.changed = true;
                                di.mAbs.down = ev.value != 0;
                            }
                            if (ev.scancode == RawInputEvent.BTN_MOUSE &&
                                (((classes&RawInputEvent.CLASS_TRACKBALL) != 0) ||
                                 ((classes&RawInputEvent.CLASS_MOUSE) != 0))) {     
                        
                                di.mRel.changed = true;
                                di.mRel.down = ev.value != 0;
//                                send = true;
                            }
    
                        } else if (ev.type == RawInputEvent.EV_ABS &&
                                (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
                            if (ev.scancode == RawInputEvent.ABS_X) {
                                di.mAbs.changed = true;
                                di.mAbs.x = ev.value;
                            } else if (ev.scancode == RawInputEvent.ABS_Y) {
                                di.mAbs.changed = true;
                                di.mAbs.y = ev.value;
                            } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) {
                                di.mAbs.changed = true;
                                di.mAbs.pressure = ev.value;
                            } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) {
                                di.mAbs.changed = true;
                                di.mAbs.size = ev.value;
                            }
    
                        } else if (ev.type == RawInputEvent.EV_REL &&
                                   (((classes&RawInputEvent.CLASS_TRACKBALL) != 0) ||
                                   ((classes&RawInputEvent.CLASS_MOUSE) != 0))) {

                            // Add this relative movement into our totals.
                            if (ev.scancode == RawInputEvent.REL_X) {
                                di.mRel.changed = true;
                                di.mRel.x += ev.value;
                            } else if (ev.scancode == RawInputEvent.REL_Y) {
                                di.mRel.changed = true;
                                di.mRel.y += ev.value;
                            }
                        }


                         if (send || ev.type == RawInputEvent.EV_SYN) {
                            if (mDisplay != null) {
                                if (!mHaveGlobalMetaState) {
                                    computeGlobalMetaStateLocked();
                                }
                                
                                MotionEvent me;
                                me = di.mAbs.generateMotion(di, curTime, true,
                                        mDisplay, mOrientation, mGlobalMetaState);
                                if (false) 
                                    Log.v(TAG, "Absolute: x=" + di.mAbs.x
                                        + " y=" + di.mAbs.y + " ev=" + me);
                                if (me != null) {
                                    if (WindowManagerPolicy.WATCH_POINTER) {
                                        Log.i(TAG, "Enqueueing: " + me);
                                    }
                                    addLocked(di, curTime, ev.flags,
                                            RawInputEvent.CLASS_TOUCHSCREEN, me);
                                }
                                me = di.mRel.generateMotion(di, curTime, false,
                                        mDisplay, mOrientation, mGlobalMetaState);
                                if (false) 
                                    Log.v(TAG, "Relative: x=" + di.mRel.x
                                        + " y=" + di.mRel.y + " ev=" + me);
                                if (me != null) {
                                   if ((classes & RawInputEvent.CLASS_TRACKBALL) != 0) {
                                        addLocked(di, curTime, ev.flags,
                                                 RawInputEvent.CLASS_TRACKBALL, me);
                                   } else {
                                       addLocked(di, curTime, ev.flags,
                                           RawInputEvent.CLASS_MOUSE, me);
                                   }

                                }
                            }
                        }

       }

    }; 

 

Thread를 수정해서 mouse 입력 처리를 할 수 있도록 한다. 크게 수정해야 될 곳은 3곳이다. 처음 두곳은 CLASS_TRACKBALL에 의해서만 relative evnet가 발생하는 것을 CLASS_MOUSE로도 가능하게 한다. 마지막은 원래는 relative event를 발생시키는 것이 CLASS_TRACKBALL 뿐이지만 이제는 CLASS_MOUSE도 발생시키므로 이를 구분해서 addLocked()을 호출하기 위한 코드이다.

 

/frameworks/base/services/java/com/android/server/WindowManagerService.java

 

마지막 수정이 실제 화면에 보여주기 위한 코드이다. 안드로이드의 경우 CLASS_TRACKBALL을 정의해서 mouse나 trackball에 대한 입력을 처리할 수 있도록은 해 두었는데 실제 화면상에 mouse cursor를 그리는 부분은 작업이 되어 있지 않다. 이를 처리하는 내용이 주된 내용이다.

   

import! android.graphics.Canvas;
import! android.graphics.Path;

 

화면상에 mouse cursor를 그려주기 위해서 Canvas와 Path를 import! 한다.

 

public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor {

    SurfaceSession mFxSession;
    private DimAnimator mDimAnimator = null;
    Surface mBlurSurface;
    boolean mBlurShown;
    Surface mMouseSurface;
    int mShowMouse = 0;
    int mMlx;
    int mMly;
    int mMlw;
    int mMlh;

    

    ...... 

 

}

 

mouse cursor는 새로운 surface에 그려주고 이를 surface flinger를 이용해서 합쳐주어야 하기 때문에 mMouseSurface를 추가한다. 다른 변수는 상대 좌표에서 마지막 그려진 좌표 등을 저장하기 위한 변수를 정의한다.

 

        private void process() {

                ...... 

                try {
                    if (ev != null) {
                        curTime = ev.when;
                        int eventType;
                        if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {
                            eventType = eventType((MotionEvent)ev.event);
                        } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
                                   ev.classType == RawInputEvent.CLASS_TRACKBALL || 
                                   ev.classType == RawInputEvent.CLASS_MOUSE) {

                            eventType = LocalPowerManager.BUTTON_EVENT;
                        } else {
                            eventType = LocalPowerManager.OTHER_EVENT;
                        }
                        try {
                            long now = SystemClock.uptimeMillis();

                            if ((now - mLastBatteryStatsCallTime)
                                    >= MIN_TIME_BETWEEN_USERACTIVITIES) {
                                mLastBatteryStatsCallTime = now;
                                mBatteryStats.noteInputEvent();
                            }
                        } catch (RemoteException e) {
                            // Ignore
                        }
                        mPowerManager.userActivity(curTime, false, eventType, false);
                        switch (ev.classType) {
                            case RawInputEvent.CLASS_KEYBOARD:
                                KeyEvent ke = (KeyEvent)ev.event;
                                if (ke.isDown()) {
                                    lastKey = ke;
                                    keyRepeatCount = 0;
                                    lastKeyTime = curTime;
                                    nextKeyTime = lastKeyTime
                                            + KEY_REPEAT_FIRST_DELAY;
                                    if (DEBUG_INPUT) Log.v(
                                        TAG, "Received key down: first repeat @ "
                                        + nextKeyTime);
                                } else {
                                    lastKey = null;
                                    // Arbitrary long timeout.
                                    lastKeyTime = curTime;
                                    nextKeyTime = curTime + LONG_WAIT;
                                    if (DEBUG_INPUT) Log.v(
                                        TAG, "Received key up: ignore repeat @ "
                                        + nextKeyTime);
                                }
                                dispatchKey((KeyEvent)ev.event, 0, 0);
                                mQueue.recycleEvent(ev);
                                break;
                            case RawInputEvent.CLASS_TOUCHSCREEN:
                                //Log.i(TAG, "Read next event " + ev);
                                dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
                                break;
                            case RawInputEvent.CLASS_MOUSE:
                                MotionEvent mmev = (MotionEvent)ev.event;
                                int mcx = mMlx + (int)(mmev.getX()* mmev.getXPrecision());
                                int mcy = mMly + (int)(mmev.getY()* mmev.getYPrecision());
                                mcx = ((mcx < 0) ? 0 :(mcx >= mDisplay.getWidth() ?(mDisplay.getWidth()-1):mcx));
                                mcy = ((mcy < 0) ? 0 :(mcy >= mDisplay.getHeight()?(mDisplay.getHeight() - 1):mcy));
                                
                                mmev.setLocation((float) mcx, (float) mcy);
                                dispatchPointer(ev, mmev, 0, 0);
                                if (mMouseSurface != null && (mMlx != mcx || mMly != mcy))            {                                                
                                    // Should we use lock? synchronized(mWindowMap) {
                                    Surface.openTransaction();
                                    if (DEBUG_INPUT)
                                        Log.i(TAG, 
                                              "Open transaction for the mouse surface");
                                    WindowState top = 
                                       (WindowState)mWindows.get(mWindows.size() - 1);
                                    try {
                                        if (DEBUG_INPUT)
                                            Log.i(TAG, "Move surf, x: " +  
                                                  Integer.toString(mcx) + " y:" 
                                                  + Integer.toString(mcy));
                                        
                                        mMouseSurface.setPosition(mcx,mcy);
                                        mMouseSurface.setLayer(top.mAnimLayer + 1);
                                        if (mShowMouse != 1) {
                                            mMouseSurface.show();
                                            mShowMouse = 1;
                                       }
                                        mMlx = mcx;
                                        mMly = mcy;
                                    } catch ( RuntimeException e) {
                                       Log.e(TAG, "Failure showing mouse surface",e);
                                   }
                                    Surface.closeTransaction();
                                }                                
                               break;


                            case RawInputEvent.CLASS_TRACKBALL:
                                dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
                                break;
                            case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
                                configChanged = true;
                                break;
                            default:
                                mQueue.recycleEvent(ev);
                            break;
                        }

                      }

                    }

                  } 

 

첫번째 수정은 위에서와 마찬가지로 CLASS_TRACKBALL로만 되어 있는 것에 CLASS_MOUSE에 대한 코드를 추가한 것이다.

두번째 위치는 mouse event 정보로 올라온 값으로부터 mouse가 그려져야 할 위치를 계산하는 코드이다.

setPosition으로 mMouseSurface의 위치를 설정하고 show()를 호출하여 mouse cursor를 보여준다. 한번 show되면 두번 호출하지 않기 위하여 mShowMouse를 이용해서 flag 관리를 한다.

 

    private final void performLayoutAndPlaceSurfacesLockedInner(
            boolean recoveringMemory) {
        final long currentTime = SystemClock.uptimeMillis();
        final int dw = mDisplay.getWidth();
        final int dh = mDisplay.getHeight();

        final int N = mWindows.size();
        int i;

        // FIRST LOOP: Perform a layout, if needed.
        performLayoutLockedInner();

        if (mFxSession == null) {
            mFxSession = new SurfaceSession();
        }

        if (mMouseSurface == null) {
            int mMx, mMy, mMw, mMh;
            Canvas mCanvas;
            Path mPath = new Path();

            if (DEBUG_INPUT)
                Log.i(TAG, "Create Mouse Surface");
            
            mMw = 20;
            mMh = 20;
            mMx = (mDisplay.getWidth() - mMw) / 2;
            mMy = (mDisplay.getHeight() - mMh) / 2;
            
            try {
               
                /*
                 *First Mouse event, create Surface
                 */
                
                mMouseSurface =
                    new Surface(mFxSession,
                                0,-1,mMw,mMy,
                                PixelFormat.TRANSPARENT,
                                Surface.FX_SURFACE_NORMAL);
                mCanvas = mMouseSurface.lockCanvas(null);
                mCanvas.drawColor(0x0);

                
                
                mPath.moveTo!(0.0f,0.0f);
                mPath.lineTo(16.0f, 0.0f);
                mPath.lineTo(0.0f, 16.0f);
                mPath.close();
                mCanvas.clipPath(mPath);
                mCanvas.drawColor(0x66666666);

                mMouseSurface.unlockCanvasAndPost(mCanvas);
                mMouseSurface.openTransaction();      
                mMouseSurface.setSize(mMw,mMh);
                mMouseSurface.closeTransaction();
                
            } catch (Exception e) {
                Log.e(TAG, "Exception creating mouse surface",e);
            }
            mMlx = mMx;
            mMly = mMy;
            mMlw = mMw;
            mMlh = mMh;
        }

        ...... 

}

 

여기에서는 mMouseSurface를 생성해 주고, mMouseSurface에서 mCanvas와 mPath를 이용해서 mouse cursor를 그려준다. 실제로 여기에서는 mMouseSurface에 그림 그려주는 것과 좌표를 초기화 해주는 것 외에는 특별한 작업은 하지 않는다. mouse 입력에 따른 동작은 이전 코드에서 처리된다.

출처 : http://blog.daum.net/_blog/BlogTypeView.do?blogid=0CNH3&articleno=16011039#ajax_history_home

+ Recent posts