출처 : http://dev.paran.com/2012/01/31/android-strict-mode-howto/

 

Android 어플리케이션의 반응 속도 및 효율성을 모니터링 할 수 Strict mode 에 대해서 이야기해 보려고 합니다. Strict mode 는 Android Developer 블로그 사이트에 Brad Fitzpatrick에 의해 이미 New Gingerbread API: StrictMode라는 제목으로 포스팅 되어있습니다. 이 후 Honeycomb 버전부터 신규 API 가 추가되고 Ice Cream Sandwich(이하 ICS) 에서의 변화가 있어 Brad Fitzpatrick 의 블로그 내용을 바탕으로 다시 한번 재조명하는 시간을 갖고자 합니다.

2. Strict Mode 의 필요성

이미 많은 개발자들이 아시다시피 Android 메인 thread는 라이프 사이클과 다양한 callback들을 처리하고 있습니다.
따라서 메인 thread에서 시간이 오래 걸리는 작업들을 하게 되면 화면 응답속도가 현저히 느려져 사용자들에게 불편함을 가중시키게 됩니다.
특히, Activity 에서는 이벤트등의 처리 시간이 5초, BroadcastReceiver 에서는 완료 시간이 10초 이상인 작업을 하게 되면 Android 에서는 ANR(Application Not Responding) 라는 팝업이 발생하게 됩니다. 이는 사용자들이 외면하는 어플리케이션으로 자리매김하도록 인도해 주니 공들여 개발 해놓고 아쉬운 소리 듣기 않기 위해서라도 어플리케이션 응답속도는 최대한 민감하게 반응할 수 있도록 설계와 개발을 진행해야 합니다.

Android 플랫폼에서는 이를 좀 더 쉽게 찾아 개선할 수 있도록 Strict Mode API를 제공하고 있습니다.
Strict mode 는 크게 Thread 와 VM에 관련된 정책을 만들 수 있고 ThreadPolicy를 통해 특정 Thread 에 File Access, Netwrok Access 부분을 감지하여 위반되는 위치를 찾아주고 VmPolicy를 통해 메모리 Leak의 위치를 감지해 주는데 개발자가 이를 인지 할 수 있도록 Log, 강제 종료, dropbox, dialog, splash의 방법으로 알림을 제공합니다.

그러나 모든 부분을 다 감지 하지는 못합니다. 특히 요즘 들어 native 를 사용하는 어플리케이션이 많이 증가하고 있는데 native 에서 사용하는 File, Network 는 감지 하지 못하는 case 가 있으니 참고하여 개발을 진행해야 합니다.

3. 왜 File, Network Access 가 위험한가?

앞서 말씀 드린 블로그 사이트에 가면 Android File System 에 언급이 되어 있습니다.
Android 에서 사용하는 YAFFS(Yet Another Flash File System) 파일 시스템은 File I/O 작업을 진행하면 Global 범위 lock이 사용됩니다.

즉, 한 프로세스가 백그라운드 I/O 작업을 하고 있으면 다른 Process 선행 작업이 끝나기 전까진 대기 상태가 됩니다. 때문에 운이 나쁘면 아주 오랜 시간 동안 대기를 할 수 있는 상황이 발생됩니다. Network 사용은 모든 개발자들이 인지하고 있듯이 시간이 매우 오래 걸릴 수 있고 예측 가능하지 않습니다. 따라서 File Access 보다 더 위험하니 절대로 메인 Thread 에서는 Network를 사용하면 안됩니다.

4. Strict Mode 사용법

Strict mode 는 아주 간단하게 선언하여 사용 가능합니다. 어플리케이션의 Activity 내의 onCreate() 메소드 내의 다음과 같이 선언만 해주면 됩니다.

    public static final boolean SUPPORT_STRICT_MODE = Build.VERSION_CODES.FROYO < Build.VERSION.SDK_INT;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        boolean debaggable = (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;

        if (SUPPORT_STRICT_MODE && debaggable) {
            StrictMode.ThreadPolicy.Builder builder = new StrictMode.ThreadPolicy.Builder();
            builder.detectCustomSlowCalls(); // api level 11
            builder.detectNetwork();
            builder.detectDiskReads();
            builder.detectDiskWrites();
            builder.detectNetwork();
            //위반시 로그로 알림
                    builder.penaltyLog();
            //위반시 dropbox 에 저장
            builder.penaltyDropBox();
            //위반시 다이얼로그로 알림
            builder.penaltyDialog();
            //위반시 강제종료
            builder.penaltyDeath();
            //네트워크 사용 위반시 강제종료 , 반드시 detectNetwork() 가 활성화 되어 있어야함.
            builder.penaltyDeathOnNetwork(); // api level 11
            //위반시 화면에 빨간 사각형 Spash로 알림.
            builder.penaltyFlashScreen(); // api level 11
            StrictMode.setThreadPolicy(builder.build());

            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .detectActivityLeaks() // api level 11
                    .detectLeakedClosableObjects() // api level 11
                    .setClassInstanceLimit(DummyClass.class, 1) // api level 11
                    .penaltyLog()
                    .penaltyDeath()
                    .penaltyDropBox()
                    .build());
        }
        super.onCreate(savedInstanceState);
    }

어플리케이션이 Debuggable 속성을 조회하여 true 일 때만 Strict Mode 를 활성화 시키도록 코드를 구성했습니다.
StrictMode API 는 Android OS 버전이 업그레이드 되면서 계속적으로 확장할 예정이기 때문에 여러분이 개발한 상용버전의 어플리케이션에 Strict mode 를 포함 할 경우 강제 종료 되는 현상을 야기 할 수 있습니다. 그러므로 상용버전에는 반드시 Strict mode 를 빼고 마켓에 등록해야 합니다. (설마 상용 바이너리에 debuggable 속성값을 true로 하진 않겠죠?)

API 를 보면 detectXXX() 는 정책을 적용하는 것이고 penaltyXXX() 는 정책에 위반 되었을 때 어떻게 알려줄 것인가를 지정해 주는 API 입니다.
penaltyXXX() 의 경우는 여러 가지를 한꺼번에 써도 무방하니 개발 환경에 알맞은 방법을 선택해서 사용하시면 됩니다.

위에서 선언한 방식은 조금 번거로운 방식입니다. 좀더 모든 기능을 사용 가능하도록 하는 간단한 API 가 있습니다.

    if (SUPPORT_STRICT_MODE
            && debaggable) {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectAll()
                .penaltyLog()
                .build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectAll()
                .penaltyLog()
                .build());
    }
    //or
    if (SUPPORT_STRICT_MODE
            && debaggable) {
         StrictMode.enableDefaults();
    }

만약 어플리케이션이 프로요 이하 버전에서만 작동된다면 다음과 같이 리플랙션을 사용하여 선언하고 Gingerbread 이상 버전의 에뮬레이터에서 테스트 하면 Strict mode 기능을 활용 할 수 있습니다.

    //Strict mode 가 지원되지 않는 os 버전에서 구동되는 어플리케이션의 경우
    //리플렉션을 이용하여 진저브레드 이상의 에뮬레이터 및 단말에서 테스트 진행 가능함.

    Class strictMode = null;
    Method enableDefaults = null;
    try {
        strictMode = Class.forName("android.os.StrictMode");
    } catch (ClassNotFoundException e) {
         Log.e(LOG_TAG, e.getMessage(), e);
    }

    if(strictMode != null){
        try {
           enableDefaults = strictMode.getMethod("enableDefaults", null);
        } catch (NoSuchMethodException e) {
            Log.e(LOG_TAG, e.getMessage(), e);
        }
    }

    if(enableDefaults != null){
        try {
            enableDefaults.invoke(null, null);
        } catch (IllegalArgumentException e) {
             Log.e(LOG_TAG, e.getMessage(), e);
        } catch (IllegalAccessException e) {
             Log.e(LOG_TAG, e.getMessage(), e);
        } catch (InvocationTargetException e) {
             Log.e(LOG_TAG, e.getMessage(), e);
        }
    }

5. 결과물 확인

  • penaltyLog() 사용: 개발자들이 흔히 쓰는 logcat 을 통해서 확인이 가능하고 “StrictMode” Tag 로 위반되는 부분만 필터링 또한 가능합니다.
  • null

  • panaltyDropBox() 사용: 안드로이드 OS 내의 DropboxManagerService 에 로그가 저장이 됩니다.
    이는 windows command 창에서 “adb shell dumpsys sropbox data_app_strictmode –print > strict.txt” text 파일로 저장하여 확인이 가능합니다. 정보 또한 detail 하여 가장 추천하는 방식입니다.
  • null

  • penaltyDialog() 사용: 설정한 정책에 위배됐을 경우 확인 dialog 창에 보여 줍니다.
    빈번하게 발생한다면 무척 번거로워 질 것입니다. 성능개선이 많이 이뤄지고 QC 를 진행할 때 사용하면 좋을 것으로 판단됩니다. 다이얼로그의 모습은 다음과 같습니다.(Honeycomb 기준)
  • null

6.Honeycomb 이상에서 제공되는 API

  • custom policy 의 적용
  • 특정 소스 위치에 StrictMode.noteSlowCall(“CustomTest”)를 선언하고 Thread 정책에 detectCustomSlowCalls() 를 넣어주게 되면 StrictMode 에서 noteSlowCall() 이 선언된 부분을 모두 감지하고 penaltyXXX() 정책에 따라 개발자에게 알려주니 사용하시면 됩니다.

  • penaltyDeathOnNetwork()
  • Main 쓰레드에서 Network Access 가 진행되면 무조건 어플리케이션이 강제 종료가 되도록 설정하는 것입니다.
    이 기능은 detectNetwork() 정책이 활성화 되어 있을 때만 유효합니다.

  • penaltyFlashScreen()
  • 정책이 위반 됐을 때 화면상으로 아래와 같은 빨간 테두리의 사각형이 splash 로 보여줘 인지하기가 수월해 집니다.
    null

  • VM policy
  • Activity 서브클래스의 leak 을 감지 해주는 detectActivityLeaks() 과
    File/Network IO 관련 API 사용시 close() 를 명시적으로 호출 안하고 종료할 경우 나오는 리소스 leak 을 감지 해주는 detectLeakedClosableObjects() 와
    특정 클래스가 어플리케이션 Heap 에 생성되는 최대 개수를 설정하여 object leak 현상을 모니터링 할 수 있도록 setClassInstanceLimit() 가 있습니다.

7. ICS에서 강화된 정책

Honeycomb 까지는 이상 없이 구동된 어플리케이션이 ICS 에서는 메인 thread 에서 File / Network Access 를 하게 되면 무조건 강제 종료가 됩니다.
어쩔 수 없이 써야 한다고 하면 Strict Mode 에서 permitAll() API 를 사용하면 죽는 현상을 막을 수는 있지만 근본적인 해결책은 아닙니다.
해당 어플리케이션에 Strict mode 를 적용하고 위반되는 부분을 찾아 로직 개선을 해야 합니다.

8. Strict Mode 에 위배된 로직의 개선

앞서 말씀드린 블로그에서 언급된 것 처럼 Thread 와 java.util.concurrent.*의 pacakge 및 HandlerThread, AsyncTask, AsyncQueryHandler, IntentService 등의 API 를 활용하시면 됩니다.

9. Strict Mode 샘플소스

제가 만든 간단 테스트 소스를 첨부 하였습니다.
Acitivity 내에서 Handler 를 만들어 놓고 메인 Thread 에 영향을 미치는 코드와 이를 개선하기 위해 HandlerThread 를 사용해서 간단히 로직 개선한 코드를 넣었으니 참고 하시면 됩니다.
또, Brad Fitzpatrick 이 간단하고 이해하기 쉽게 만들어 놓은 샘플이 있어서 링크 걸어 둡니다.
앞에서 언급한 API 레벨별 사용가능한 정책을 확인하시고 테스트 해보시면 됩니다.

10. 결론

안드로이드 어플리케이션을 개발할 때 Strict Mode 는 옵션이 아닌 필수사항입니다.
기존에 Strict mode 없이 개발된 어플리케이션이 있다면 지금 당장 Strict mode를 사용해서 검증하고 Stric mode 에 위반된 소스를 수정 해보세요~
어플리케이션이 눈에 띄게 빨라지는 효과를 경험하게 되실겁니다.

추가로 어플리케이션에 Strict Mode 를 Thread 와 VM 의 detectAll () 을 선언하고 penaltyDropBox()로 세팅 후에 “Monkey Test” 를 돌리면 정말 유용하지 않을 까 생각되어 집니다.

모두 즐코딩 하시기 바랍니다~ 감사합니다

 

'Android > Tip&Tech' 카테고리의 다른 글

Android HttpClient Get 과 Post 정리  (0) 2012.05.14
DB별 Auto_increatement 정리  (0) 2012.05.04
Listview 안에서 list 뒤집기  (0) 2012.04.18
Android/Intent 사용 예  (2) 2012.03.20
[펌]svn 구축  (0) 2012.02.23

2011년 2월 23일 수요일

[안드로이드] arraylist 정렬하기


Collections.sort
에다가 comparator 로 대상 변수만 지정해주면
중복없이 쉽게 정렬이 가능하다. 코딩도 짧아지고 속도도 빨라진다.

downMyData : ArrayList<>
형식의 데이터가 들어있다고 가정
myArrayData : sort
용 비어있는 ArrayList<>


@ custom Custom Class 를 뜻한다. 바꿀 수도 있음.
//비어있는 sort ArrayList
  List<custom> myArrayData = new ArrayList<custom>();


 //일단 데이터를 sort ArrayList<>에 넣는다.

  for(int i = 0 ; i < downMyData.size(); i++) {
   myArrayData.add(downMyData.get(i));

  }//end for



//Comparator 를 만든다.
    private final static Comparator<custom> myComparator= new Comparator<custom>() {

        private final Collator   collator = Collator.getInstance();
  @Override

  public int compare(custom object1,custom object2) {
   return collator.compare(object1.getData(), object2.getData());

  }
 };




// Collections.sort comparator 를 주어서 데이터를 정렬 시킨다.
Collections.sort(myArrayData ,myComparator);




//정렬된 ArrayList Adapter에 넣고 리스트뷰에 배치한다.
ListView MyListView = (ListView)findViewById(R.id.mylistview);

27
MyAdapter adapter = new MyAdapter (this,R.layout.my_item,myArrayData );
28
MyListView.setAdapter(adapter);

29
</custom></custom></custom></custom>



Comparator
에 있는 compare 함수에 ArrayList colum중 정렬시키고자 하는 데이터를 가져와서 비교하면 된다. 여기서 parameter가 두개인 이유는 해당 arraylist 내부의 두개의 값들을 비교하기 때문이다.

데이터 역순정렬 시 사용
Comparator comparator = Collections.reverseOrder();
Collections.sort(myArrayData ,comparator);

하지만 위의 역순정렬은 추천하지 않는다. 별도의 comparator 가 필요할경우 역순정렬comparator와 중복해서 사용할 수 없기 때문이다.

그래서 밑에 방법을 추천한다.
일단 정순정렬을 실시한 후 뒤집는 방법이다.
    Collections.sort(myArrayData, myComparator);
    Collections.reverse(myArrayData);
위 처럼 하면 정상적으로 역순정렬이 가능하다.
[
출처,원본
http://devbible.tistory.com , http://devbible.tistory.com/13, http://devbible.tistory.com/tag/ArrayList%20%EC%A0%95%EB%A0%AC
[
작성자www.ybinside.com


// 다른 사이트의 예시
Class Test {
public String name;
public String sirName;
}
ArrayList<Test> res = new ArrayList<Test>();
res.add(…);
res.add(…);
Comparator<Test> comperator = new Comparator<Test>() {
@Override
public int compare(Test object1, Test object2) {
return object1.name.compareToIgnoreCase(object2.name);
}
};
Collections.sort(res, comperator);

혹은 compareToIgnoreCase 대신 compareTo

그리고 int long을 비교할 경우에는

Return (Object1.getInt() > Object2.getInt() ? 1: -1);

이처럼 하면 된다.

'Android > Tip&Tech' 카테고리의 다른 글

DB별 Auto_increatement 정리  (0) 2012.05.04
[펌]Android Strict Mode 활용 가이드  (0) 2012.05.04
Android/Intent 사용 예  (2) 2012.03.20
[펌]svn 구축  (0) 2012.02.23
[펌]ViewPager 에 관한 설명  (0) 2012.02.17
출처 : http://dislab.hufs.ac.kr/lab/Android/Intent_%EC%82%AC%EC%9A%A9_%EC%98%88?rdfrom=http%3A%2F%2Fdislab.hufs.ac.kr%2Fw%2Findex.php%3Ftitle%3DAndroid%2FIntent_%25EC%2582%25AC%25EC%259A%25A9_%25EC%2598%2588%26redirect%3Dno#Camera_.EC.82.AC.EC.A7.84_.EC.B4.AC.EC.98.81

Android/Intent 사용 예

DISLab

목차

 [숨기기

내장 프로그램 띄우기

웹페이지 띄우기

Uri uri = Uri.parse("http://www.google.com");
Intent it  = new Intent(Intent.ACTION_VIEW,uri);
startActivity(it);

구글맵 띄우기

Uri uri = Uri.parse("geo:38.899533,-77.036476");
Intent it = new Intent(Intent.Action_VIEW,uri);
startActivity(it);


구글 길찾기 띄우기

Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=출발지주소&daddr=도착지주소&hl=ko");
Intent it = new Intent(Intent.ACTION_VIEW,URI);
startActivity(it);


SMS/MMS 발송

Intent it = new Intent(Intent.ACTION_VIEW);   
it.putExtra("sms_body", "The SMS text");   
it.setType("vnd.android-dir/mms-sms");   
startActivity(it);  
 
 
// SMS 발송
Uri uri = Uri.parse("smsto:0800000123");   
Intent it = new Intent(Intent.ACTION_SENDTO, uri);   
it.putExtra("sms_body", "The SMS text");   
startActivity(it);  
 
 
// MMS 발송
Uri uri = Uri.parse("content://media/external/images/media/23");   
Intent it = new Intent(Intent.ACTION_SEND);   
it.putExtra("sms_body", "some text");   
it.putExtra(Intent.EXTRA_STREAM, uri);   
it.setType("image/png");   
startActivity(it);


이메일 발송

Uri uri = Uri.parse("mailto:xxx@abc.com");
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(it);
 
 
Intent it = new Intent(Intent.ACTION_SEND);   
it.putExtra(Intent.EXTRA_EMAIL, "me@abc.com");   
it.putExtra(Intent.EXTRA_TEXT, "The email body text");   
it.setType("text/plain");   
startActivity(Intent.createChooser(it, "Choose Email Client"));  
 
 
Intent it = new Intent(Intent.ACTION_SEND);     
String[] tos = {"me@abc.com"};     
String[] ccs = {"you@abc.com"};     
it.putExtra(Intent.EXTRA_EMAIL, tos);     
it.putExtra(Intent.EXTRA_CC, ccs);     
it.putExtra(Intent.EXTRA_TEXT, "The email body text");     
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");     
it.setType("message/rfc822");     
startActivity(Intent.createChooser(it, "Choose Email Client"));   
 
 
// extra 추가하기
Intent it = new Intent(Intent.ACTION_SEND);   
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");   
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3");   
sendIntent.setType("audio/mp3");   
startActivity(Intent.createChooser(it, "Choose Email Client"));
 
 
// 첨부파일을 추가하여 메일 보내기
Intent it = new Intent(Intent.ACTION_SEND);  
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");  
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/eoe.mp3");  
sendIntent.setType("audio/mp3");  
startActivity(Intent.createChooser(it, "Choose Email Client"));


설치 어플 제거

Uri uri = Uri.fromParts("package", strPackageName, null);   
Intent it = new Intent(Intent.ACTION_DELETE, uri);   
startActivity(it);


APK파일을 통해 제거하기

Uri uninstallUri = Uri.fromParts("package", "xxx", null);
returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri);
 
 
// APK파일 설치
Uri installUri = Uri.fromParts("package", "xxx", null);
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);



마켓에서 어플리케이션 검색

Uri uri = Uri.parse("market://search?q=pname:pkg_name");  
Intent it = new Intent(Intent.ACTION_VIEW, uri);  
startActivity(it);  
// 패키지명은 어플리케이션의 전체 패키지명을 입력해야 합니다.
 
 
// 마켓 어플리케이션 상세 화면
Uri uri = Uri.parse("market://details?id=어플리케이션아이디");  
Intent it = new Intent(Intent.ACTION_VIEW, uri);  
startActivity(it);
// 아이디의 경우 마켓 퍼블리싱사이트의 어플을 선택후에 URL을 확인해보면 알 수 있습니다.


구글 검색

Intent intent = new Intent();
intent.setAction(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY,"searchString");


내장 사진 고르기 (Image Selection)

  • android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
  • android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
String action = Intent.ACTION_PICK;
Uri uri = android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI;
Intent intent = new Intent(action, uri);
startActivityForResult(intent, SELECT_IMAGE);
 
 
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == SELECT_IMAGE) {
        if (resultCode == Activity.RESULT_OK) {
            Uri selectedImage = data.getData();
            // TODO Do something with the select image URI
        }
    }
}

장치 사용

Camera 사진 촬영

Intent intent2 = new Intent("android.media.action.IMAGE_CAPTURE");
imgUri = Uri.fromFile(photofile);
intent2.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);
startActivityForResult(intent2, 1);


Video 영상 녹화

Intent i = new Intent("android.media.action.VIDEO_CAPTURE");
i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(videofile));
i.putExtra(android.provider.MediaStore.EXTRA_VIDEO_QUALITY, 0);
i.putExtra("android.intent.extra.durationLimit", 60);
startActivityForResult(i, 2);


Audio 음성 녹음

Intent i = new Intent("android.provider.MediaStore.RECORD_SOUND");

음악 파일 재생

Uri playUri = Uri.parse("file:///sdcard/download/everything.mp3");
returnIt = new Intent(Intent.ACTION_VIEW, playUri);


미디어파일 플레이 하기

Intent it = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/song.mp3");
it.setDataAndType(uri, "audio/mp3");
startActivity(it);
 
 
Uri uri = Uri.withAppendedPath(
  MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");   
Intent it = new Intent(Intent.ACTION_VIEW, uri);   
startActivity(it);


전화 걸기

Uri uri = Uri.parse("tel:xxxxxx");
Intent it = new Intent(Intent.ACTION_DIAL, uri);  
startActivity(it);  
 
 
Uri uri = Uri.parse("tel.xxxxxx");
Intent it = new Intent(Intent.ACTION_CALL,uri);
// 퍼미션을 잊지 마세요. <uses-permission id="android.permission.CALL_PHONE" />

'Android > Tip&Tech' 카테고리의 다른 글

[펌]Android Strict Mode 활용 가이드  (0) 2012.05.04
Listview 안에서 list 뒤집기  (0) 2012.04.18
[펌]svn 구축  (0) 2012.02.23
[펌]ViewPager 에 관한 설명  (0) 2012.02.17
ProgressBar 이미지 커스터마이징하기  (0) 2012.02.08

'Android > Tip&Tech' 카테고리의 다른 글

Listview 안에서 list 뒤집기  (0) 2012.04.18
Android/Intent 사용 예  (2) 2012.03.20
[펌]ViewPager 에 관한 설명  (0) 2012.02.17
ProgressBar 이미지 커스터마이징하기  (0) 2012.02.08
액티비티와 태스크  (0) 2012.02.01

출처 :  http://blog.naver.com/PostView.nhn?blogId=huewu&logNo=110116958816 


Horizontal View Swiping with ViewPager

[이 포스트는  Rich “geekyouup” Hyndman 에 의해 작성되었습니다. 인생을 조금 쉽게 만들기 위하여 노력하는 사람이지요.  — Tim Bray]



 여러분이 이제 막 안드로이드 어플리케이션 개발을 시작한 개발자이건, 능숙한 베테랑이건 상관 없이, 수평으로 스크롤 되는 View 를 만들 경우가 있습니다. 이미 많은 안드로이드 어플리케이션들이 이 기능을 활용하고 있습니다. 예를 들어 새로운 안드로이드 마켓 어플리케이션이나, 구글 닥스, 구글+ 등이 그렇지요. ViewPager 는 이런 기능을 구현하기 위한 표준 방법입니다.



 ViewPager 는 'Compatibility Package Revison 3' 라이브러리에 포함되었으며, 안드로이드 1.6 이 후 버전에서 정상적으로 동작합니다. 우선 이 링크에 나온대로 라이브러리를 설치 하세요. 그 이 후에, 여러분의 안드로이드 프로젝트에서 우클릭을 하신 후, 'Android Tools' 메뉴를 선택하고, 'Add Compatibility' 항목을 선택하시기 바랍니다. 이 후에는 이제 새로운 기능을 활용하실 수 있습니다. (주> 몇 가지 주의사항이 있습니다. 우선, Add Compatibility 항목은 최신 ADT(version 12) 를 사용하지 않으면 나타나지 않는 거 같습니다. 그리고, Compatibility Package 라이브러리 설치가 완료된 후에는, 위의 링크에 나온 것처럼 개별 프로젝트에 라이브러리 추가 메뉴를 통해, 설치된 android-support-v4.jar 파일을 명시적으로 추가하셔야 합니다.)



 ViewPager 는 ViewGroup 의 일종으로, Gallery 나 ListView 같은 AdapterView 와 유사한 형식으로 동작합니다. 따라서 쉽게 사용하실 수 있을 겁니다. 단, 안드로이드 라이브러리에 기본적으로 존재하는 View 는 아니기 때문에, ViewPager 를 xml 레이아웃 상에서 사용하실 때는, 클래스 풀네임을 정확히 적으셔야합니다. 아래와 같이 말이지요.

 <android.support.v4.view.ViewPager
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"
        …
/>

 ViewPagers 가 표시하는 View 는 PagerAdapter 를 통해 공급받습니다. PageAdapter 를 통해 개발자는 화면에 표시될 View 의 라이프사이클을 관리 할 수 있습니다. 일반적인 ListView 와 ListAdapter 의 관계와 동일하다고 이해하시면 됩니다. 한가지, PageAdapter 구현 클래스로 FragmentPagerAdapter 가 제공되는데, 이를 이용하면 여러분은 ViewPager 에서 Fragment 를 활용할 수 있습니다. 사용법은 getCount() 나 getItem() 을 구현하는 것 만큼이나 간단하지만 강력합니다. 이에 관한 샘플 코드가 Support Demos 항목에 이미 제공되고 있습니다. 그 중에서 관련된 부분을 조금 발쵀해 봤습니다.

    public static class MyAdapter extends FragmentPagerAdapter {
       
public MyAdapter(FragmentManager fm) {
           
super(fm);
       
}

       
@Override
       
public int getCount() {
           
return NUM_ITEMS;
       
}

       
@Override
       
public Fragment getItem(int position) {
           
return ArrayListFragment.newInstance(position);
       
}
   
}

 Fragment 를 처리하는 PagerAdapter 는 기본적으로 두 종류가 제공됩니다. 기본적으로 FragmentPagerAdapter 의 경우, 사용자가 어플리케이션 화면을 스윕해서 새로운 View 가 표시되면, 이전 Fragment 를 분리한 후, 메모리 상에 저장해 둡니다. 만일 사용자가 어플리케이션 화면을 반대로 스윕하면, 메모리 상에 보전된 Fragment 를 재활용하지요. 따라서, 여러분이 많은 수의 Fragment 를 지원할 필요가 있는 경우에는 FragmentStatePagerAdapter 를 고려할 필요가 있습니다. 이 경우, 개별 Fragment 는 화면에서 사라질 때 메모리 상에서 제거되며, 사용자가 화면을 반대로 스윕했을 떄, 기존에 저장된 상태 값을 기반으로 새롭게 재생성됩니다. 따라서, 만일 수는 적지만, 복잡한 구조의 Fragment 를 사용하는 경우에는 FragmentPagerAdpater 를, 많은 수의 Fragment 를 사용하는 경우에는 FragmentStatePagerAdapter 를 선택하시는 편이 좋습니다.


 또 한가지. 여러분의 이해를 돕기위하여 TextView 로 동작하는 ViewPager/PagerAdapter 예제 코드를 작성했습니다. 해당 예제를 기반으로 원하는 PagerAdapter 를 구현 하는 것은 여러분의 몫입니다. PageAdapter 의 instantiateItem() / destroyItem() 메서드를 통해 ViewPager 가 전달 됨으로, 해당 ViewPager 에 원하는 View 를 추가하거나 삭제할 수 있습니다.

    @Override
   
public Object instantiateItem(View collection, int position) {
       
View v = layoutInflater.inflate(...);
       
...
       
((ViewPager) collection).addView(v,0);
       
return tv;
   
}

   
@Override
   
public void destroyItem(View collection, int position, Object view) {
       
((ViewPager) collection).removeView((TextView) view);
   
}

 ViewPager 의 소스 코드는 <android-sdk>/extras/android/compatibility/v4/src. 에 포함되어 있습니다. 소스 코드와 Java Doc 문서를 잘 참고하시면, 여러가지 유용한 메서드들을 발견하실 수 있을 것 입니다. 예를 들어, setOnPageChangeListener() 같은 메서드는 현재 어플리케이션에서 어떤 View 가 화면 상에 표시되고 있는지 추적할 수 있게 해 줍니다.


 만일 여러분이 안드로이드 마켓에 ViewPager 를 이용한 어플리케이션을 출시하신다면, 구글+나 트위터를 통해 저에게 알려주세요. 저는 이 ViewPager 가 얼마나 널리 사용될 지, 그리고 어떤 멋진 시나리오를 가져올 지 기대하고 있답니다.

'Android > Tip&Tech' 카테고리의 다른 글

Android/Intent 사용 예  (2) 2012.03.20
[펌]svn 구축  (0) 2012.02.23
ProgressBar 이미지 커스터마이징하기  (0) 2012.02.08
액티비티와 태스크  (0) 2012.02.01
안드로이드 최신버전 확인하기  (0) 2012.01.30

1. layout/main.xml의 progress bar의 android:progressDrawable 속성을 설정한다.

 

<ProgressBar

android:progressDrawable="@drawable/new_drawable"

...

</ProgressBar>

 

2. res/drawable/new_drawable.xml 파일을 생성한다.

각 스타일을 설정한다.

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

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+android:id/background" android:drawable="@drawable/backgroundProgress" />
    <item android:id="@+android:id/secondaryProgress" android:drawable="@drawable/secondaryProgress" />
    <item android:id="@+android:id/progress" android:drawable="@drawable/progress" />
</layer-list>

3. res/drawable 디렉토리 밑에 다음의 파일을 생성한다.

 

backgroundProgress.png

secondaryProgress.png

progress.png

 

png파일을 생성하지 않고 drawable로 작성하려면

2b. res/drawable/new_drawable.xml 파일을 다음과 같이 설정한다.

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

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    
    <item android:id="@android:id/background">
        <shape>
            <corners android:radius="0dip" />
            <gradient
                    android:startColor="#ff9d9e9d"
                    android:centerColor="#ff5a5d5a"
                    android:centerY="0.75"
                    android:endColor="#ff747674"
                    android:angle="270"
            />
        </shape>
    </item>
    
    <item android:id="@android:id/secondaryProgress">
        <clip>
            <shape>
                <corners android:radius="0dip" />
                <gradient
                        android:startColor="#80ffd300"
                        android:centerColor="#80ffb600"
                        android:centerY="0.75"
                        android:endColor="#a0ffcb00"
                        android:angle="270"
                />
            </shape>
        </clip>
    </item>
    
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <corners android:radius="0dip" />
                <gradient
                        android:startColor="#ff0000"
                        android:centerColor="#00ff00"
                        android:centerY="0.75"
                        android:endColor="#0000ff"
                        android:angle="0"
                />
                <stroke android:width="3dp" color="#000000"/> 
                <padding android:left="10dp" android:top="10dp"         
      android:right="10dp" android:bottom="10dp" /> 
            </shape>     
        </clip>       
    </item>    
</layer-list>

'Android > Tip&Tech' 카테고리의 다른 글

[펌]svn 구축  (0) 2012.02.23
[펌]ViewPager 에 관한 설명  (0) 2012.02.17
액티비티와 태스크  (0) 2012.02.01
안드로이드 최신버전 확인하기  (0) 2012.01.30
마켓 링크 확인하기  (1) 2012.01.30

출처 :  http://chihun80.springnote.com/pages/5715069 

안드로이드 입문서 3rd Edition을 읽으며 요약한 내용입니다. 열공!

Section 2. 애플리케이션의 기초(3)



액티비티와 태스크

태스크 : 하나의 스택 내 정렬된 관련 액티비티들의 그룹

  • 스택 내 루트 (최하위) 액티비티 - 태스크를 시작했던 액티비티. 일반적으로 사용자가 애플리케이션 시작을 위해 런처에서 선택했던 액티비티
  • 스택 내 최상위 액티비티 - 현재 실행 중인 (포커스 된) 액티비티
  • 하나의 액티비티가 다른 액티비티를 시작하면 새로운 액티비티는 스택에 푸쉬됨 (따라서 최상위 액티비티가 됨)
  • 사용자가 BACK 키를 누르면 현재의 액티비티는 스택에서 팝되며, 이전 액티비티가 (최상위 액티비티가 되어) 실행이 재개됨. 스택에서 액티비티는 푸쉬되고 팝 될 뿐, 결코 재정렬되지 않음
  • 태스크가 동일 액티비티(오브젝트 형태)의 인스턴트를 한 개 이상 가지면 -> 스택이 각 인스턴트에 대한 별도의 시작점을 가짐
  • 태스크는 독립적으로 설정되지 않고, 루트 액티비티에 따라 값이 결정 됨

태스크와 액티비티 작동의 예

 



태스크와 액티비티 간의 동작방식설정 : 매니페스트 내 액티비티 엘리먼트 애트리뷰트 설정 + 인텐트 오브젝트 내 플래그

  • 디폴트 -  태스크 안에서 모드 액티비티가 하나의 단위로 움직임
  • 매 니페스트 내 액티비티 엘리먼트의 애트리뷰트 : taskAffinity, launchMode, allowTaskReparenting, clearTaskOnLaunch, alwaysRetainTaskState, finishOnTaskLaunch
  • 액티비티를 활성화할 때 사용되는 인텐트 오브젝트 내 설정된 플래그 : FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, FLAG_ACTIVITY_SINGLE_TOP


어피니티와 신규 태스크

  • 디폴트 - 동일 태스크 내에 속하는 액티비티의 어피니티는 동일함
  • But, 액티비티 엘리먼트의 taskAffinity 애트리뷰트를 이용, 각각의 액티비티에 대한 개별적인 어피니티 설정도 가능
    • 다른 애플리케이션들이 정의된 하나의 어피니티를 공유할 수도 있음
    • 같은 애플리케이션 내에 정의된 액티비티들에게 서로 다른 어피니티가 지정될 수도 있음
  • 어피니티가 동작할 수 있는 두 가지 조건
    • 액티비티를 런치하는 인텐트 오브젝트가 FLAG_ACTIVITY_NEW_TASK 플래그를 포함함
    • 매니페스트에서 액티비티의 allowTaskReparenting 애트리뷰트가 true로 설정됨

 

  • FLAG_ACTIVITY_NEW_TASK 플래그
    • 디폴트 - 새로운 액티비티는 startActivity()를 호출한 액티비티와 동일한 태스크 안에 런치됨
    • But, 인텐트 오브젝트에 FLAG_ACTIVITY_NEW_TASK 플래그가 포함되어 startActivity()의 파라미터로 전달된 경우
      • 신규 액티비티와 동일한 어피니티를 가지는 태스크가 존재 - 액티비티는 그 태스크 내에서 런치됨
      • 신규 액티비티와 동일한 어피니티를 가지는 태스크가 없음 - 신규 태스크가 런치됨

 

  • allowTaskReparenting 애트리뷰트
    • allowTaskReparenting = true : 원래 태스크에서 어피니티를 가진 태스크로 이동할 수 있음


런치 모드

  • 액티비티의 launchMode 애트리뷰트 - standard, singleTop, singleTask, singleInstance 중 하나의 값을 가짐

 

  • 어떤 태스크가 인텐트에 응답하는 액티비티를 보유하는가?
    • standard 혹은 singleTop 모드 액티비티가,
      • FLAG_ACTIVITY_NEW_TASK가 없는 인텐트를 받았을 때 -> startActivity()를 호출했던 액티비티가 있는 태스크에 인텐트에 응답하는 액티비티가 나타남
      • FLAG_ACTIVITY_NEW_TASK가 있는 인텐트를 받았을 때 -> 신규, 혹은 동일한 어피니티를 갖는 다른 태스크에 인텐트에 응답하는 액티비티가 나타남
    • singleTask 혹은 singleInstance 모드 액티비티는,
      • 항상 태스크의 루트 액티비티로 나타남. 결코 다른 태스크 안으로 런치되지 않음

 

  • 액티비티에 대한 인스턴스가 여러 개 존재할 수 있는가?
    • standard 혹은 singleTop 모드 액티비티는,
      • 여러 번 인스턴스화 될 수 있음
      • 다양한 태스크에 속할 수 있으며, 한 태스크가 동일한 액티비티에 대한 여러 개의 인스턴트를 가질 수 있음
    • singleTask 혹은 singleInstance 액티비티는,
      • 하나의 인스턴트로만 제한됨
      • 태스크 내 액티비티 인스턴트가 디바이스 상에 동시에 하나 이상 존재할 수 없음

 

  • 액티비티의 인스턴스가 그것의 태스크에 다른 액티비티를 가질 수 있는가?
    • singleInstance 모드 액티비티는,
      • 태스크 내에서 유일한 액티비티로 존재
      • 이 액티비티가 다른 액티비티를 호출하면 호출된 액티비티의 모드와 상관없이 별도의 태스크에서 런치됨
    • singleTask 모드 액티비티는
      • 항상 태스크의 루트 액티비티로 존재
      • singleInstance와 비슷하나 태스크 내 다른 액티비티들을 호출할 수 있음
    • standard와 singleTop 액티비티의 인스턴스는 스택 어디에나 존재 가능

 

  • 새로운 인스턴스가 새로운 인텐트를 처리하기 위해 런치될 것인가?
    • standard 모드 액티비티는,
      • 새로운 인스턴스가 모든 신규 인텐트에 응답하도록 생성됨
      • 각각의 인스턴스는 단지 하나의 인텐트를 처리함
    • singleTop 액티비티는,
      • 태스크의 최상위에 존재 - 이미 존재하는 인스턴스가 신규 인텐트 처리를 위해 재사용
      • 태스크의 최상위에 존재하지 않음 - 신규 인텐트를 위한 새 인스턴스 생성, 스택에 최상위로 푸쉬됨

 

액티비티의 위치 및 모드에 따른 인텐트 처리결과

 

  • singleInstance 모드 액티비티는,
    • 태스크에 있는 유일한 액티비티. 따라서 항상 최상위에 있으며, 모든 인텐트를 처리함
  • singleTask 모드 액티비티는,
    • 다른 액티비티가 최상위에 있는 경우는 인텐트가 누락될 수 있음
    • 인텐트가 누락되더라도 태스크 자체는 포어그라운드로 올라옴

 

  • BACK 키를 눌렀을 때,
    • 이전 액티비티 인스턴스로 돌아감 (최상위 인스턴스가 팝되어 나감)
    • 새 인텐트로 인해 기존 액티비티 인스턴트가 변하였을 때 BACK 키로 이전 상태로 가는 것은 불가능


스택 제거

  • 디폴트 - 사용자가 오랫동안 하나의 태스크를 떠나 있다면, 시스템이 루트 액티비티를 제외한 모든 액티비티를 제거함
  • alwaysRetainTaskState 애트리뷰트 = true - 긴 시간 방치된 태스크가 그대로 유지됨
  • clearTaskOnLaunch 애트리뷰트 = true - 사용자가 태스크를 떠나는 즉시 루트 액티비티를 제외한 모든 것을 지움
  • finishOnTaskLaunch 애트리뷰트 = true - 사용자가 태스크를 떠나면 루트 액티비티를 포함한 모든 것을 지움

 

  • FLAG_ACTIVITY_CLEAR_TOP 플래그
    • 인텐트 오브젝트가 이 플래그를 가지고 있고 타겟 태스크 스택 내에 인텐트를 처리할 수 있는 타입의 액티비티 인스턴트가 이미 있다면, 이 액티비티 인스턴트가 최상위 액티비티가 되어 응답하도록 그 위의 액티비티를 모두 제거
    • 만약 지정된 액티비티가 standard 모드이면 이 액티비티도 지워지고 새로운 액티비티 인스턴트가 생성됨
    • FLAG_ACTIVITY_CLEAR_TOP은 거의 대부분 FLAG_ACTIVITY_NEW_TASK와 함께 쓰임


태스크 시작하기

  • android.intent.action.MAIN + android.intent.category.LAUNCHER : 태스크에 대한 시작점이 됨
    • 액티비티의 아이콘과 라벨이 애플리케이션 런처에 보여지게 함
    • 언제든지 태스크로 다시 돌아갈 수 있게 함
  • singleTask와 singleInstance 모드 액티비티는 MAIN과 LAUNCHER 필터를 가지고 있을 때만 사용되어야 함
    • 만약 아니라면 백그라운드로 보낸 후 다시 포어그라운드로 불러올 수 없음 (태스크가 백그라운드에서 미아가 됨)
  • FLAG_ACTIVITY_NEW_TASK 플래그 사용 시에도 마찬가지
    • 이 플래그를 포함한 인텐트에 의해 호출될 수 있는 액티비티를 애플리케이션이 포함한다면, 사용자가 시작된 태스크로 다시 돌아갈 수 있는 방법을 제공해야 함
  • 돌아가기를 원치 않는다면 finishOnTaskLaunch를 true로 설정하여 태스크를 떠나는 즉시 지울 것

'Android > Tip&Tech' 카테고리의 다른 글

[펌]ViewPager 에 관한 설명  (0) 2012.02.17
ProgressBar 이미지 커스터마이징하기  (0) 2012.02.08
안드로이드 최신버전 확인하기  (0) 2012.01.30
마켓 링크 확인하기  (1) 2012.01.30
Lists and focuses in button  (1) 2012.01.26
출처 :  http://androi.tistory.com/9 

안드로이드 앱이 업그레이드가 되었는데도, 사용자들은 잘 확인하지도 않고, 설사 확인했다고 해도 큰 문제가 없으면 업데이트를 잘하지 않는다.


큰 노력과 수고없이, 앱 시작시 최신버젼을 확인하고  최신버젼이 있을 경우, 사용자에게 팝업을 띄우고, 업데이트 하는 방법을 알아보자.

1. 웹페이지에 최신 버젼을 표시
    간단하게 무료로 사용하고 싶으면 구글앱엔진을 추천한다. 그 외에 본인의 블로그 등을 사용해도 좋음
    ex)  http://checkappver.appspot.com/CheckBBVersion.html 

<html>
<body>
version:
1.11
</body>
</html>


2. 웹페이지의 html 파일을 가져와서 간단한 파싱으로 버젼 구하기

DownThread mThread;
String sVersion;  // 웹페이지에서 가져온 버젼이 저장됨

        // 웹서버에서 369 숫자야구 버젼 가져오기
mThread = new DownThread("http://checkappver.appspot.com/CheckBBVersion.html");
mThread.start();

         ...

class DownThread extends Thread {
String mAddr;
String mResult;

DownThread(String addr) {
mAddr = addr;
mResult = "";
}

public void run() {
StringBuilder html = new StringBuilder(); 
try {
URL url = new URL(mAddr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
if (conn != null) {
conn.setConnectTimeout(10000);
conn.setUseCaches(false);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
Boolean bVersion = false;
for (;;) {
String line = br.readLine();
if(bVersion){
mResult = line;
break;
}
if(line.equals("version:")){
bVersion = true;
}
if (line == null) break;
}
br.close();
}
conn.disconnect();
}
catch (Exception ex) {;}
mAfterDown.sendEmptyMessage(0);
}
}

Handler mAfterDown = new Handler() {
public void handleMessage(Message msg) {
// Toast.makeText(mContext, mThread.mResult, Toast.LENGTH_SHORT).show();
sVersion = mThread.mResult;
}
};

3. 현재 앱의 버젼 구하기
         나같은 경우는 리소스에 버젼을 넣고 불러서 확인 
               String sMyVersion = getResources().getString(R.string.check_version);


4. 현재 앱의 버젼과 웹페이지에서 가져온 버젼을 비교하기
           if(!sServerVersion.equals(sMyVersion)){ // 만약 서버와 버젼이 같지 않으면 업데이트 요청
           float fMyVersion = Float.parseFloat(sMyVersion);
           float fServerVersion = Float.parseFloat(sServerVersion);  // 서버 버젼이 클때만 업데이트 요청
           if(fServerVersion > fMyVersion){
           showDialog(9);
           }
           }


5. 최신 버젼이 있으면 팝업을 띄우고,  확인을 선택하면 해당 마켓으로 가기 
        ...
         case 9: // 업데이트 요청
            return new AlertDialog.Builder(mContext)
            .setTitle(R.string.str_updateinfo)
            .setMessage(R.string.str_updaterequestmsg)
            .setCancelable(true)
            .setPositiveButton(R.string.str_ok, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                 bCheckVersionDialog = true;
                 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=dingdong.game.bb"));
             startActivity(intent);
                }
            })
            .setNegativeButton(R.string.str_cancel, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                 bCheckVersionDialog = true;
                    /* User clicked cancel so do some stuff */
                }
            })
            .create();  
출처 :  http://androi.tistory.com/6 
<웹브라우져를 이용한 마켓의 html 설명 화면으로 가기>

 1. 특정 앱으로 바로가기 (패키지명 이용)
    
https://market.android.com/details?id=dingdong.game.bb 

 2. 제작자 검색하여 바로가기 
    https://market.android.com/search?q=pub:Heaven's tear
    
 3. 검색어로 검색하여 바로가기
    https://market.android.com/search?q=369
    (검색어가 369인 경우)

    https://market.android.com/search?q=369+heaven
    (검색어가 369 와 heaven 인 경우)


<마켓 앱을 이용여 상세 화면으로 가기>
 : java 소스에서 intent 이용 예
  ex) Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=dingdong.game.bb"));
       startActivity(intent);
 
 : xml 파일에서 intent 이용 예
  ex) <intent android:action="android.intent.action.VIEW" 
                  android:data="market://details?id=dingdong.game.bb" />

1. 특정 앱 다운로드 화면(상세화면)으로 가기
   market://details?id=dingdong.game.bb

2. 제작자 검색 목록으로 가기
   
market://search?q=pub:Heaven's tear

3. 검색어로 검색하여 가기
   market://search?q=369

 

<T Store 앱을 이용여 바로 상세 화면으로 가기>
1. 특정 앱 다운로드 화면(상세화면)으로 가기
    : AID가 
0000266823 인 경우
 
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setClassName("com.skt.skaf.A000Z00040", "com.skt.skaf.A000Z00040.A000Z00040");
intent.setAction("COLLAB_ACTION");
intent.putExtra("com.skt.skaf.COL.URI", "PRODUCT_VIEW/0000266823/0".getBytes());
intent.putExtra("com.skt.skaf.COL.REQUESTER", "A000Z00040");
startActivity(intent);

2. 검색어로 검색하여 가기
   : 검색어가 damianjj 인 경우
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setClassName("com.skt.skaf.A000Z00040", "com.skt.skaf.A000Z00040.A000Z00040");
intent.setAction("COLLAB_ACTION");
intent.putExtra("com.skt.skaf.COL.URI", "SEARCH_ACTION/0/damianjj".getBytes());
intent.putExtra("com.skt.skaf.COL.REQUESTER", "A000Z00040");
startActivity(intent);

<참고 사이트>
 
 http://developer.android.com/guide/publishing/publishing.html

 T Store 링크 연동
com : computer output microfilm 컴퓨터 출력 마이크로필름, 함께, 전혀

출처 :  http://mylifewithandroid.blogspot.com/2009/10/lists-and-focuses.html 
Lists and focuses

I received another seemingly trivial question in a comment. The situation is simple: we have a ListView and it contains TextViews. The user clicks (touches) a list row and the row gets highlighted until the user removes his or her finger (releases the mouse button in case of the emulator). Then comes the mistery. If you put a Button into the row, this highlight does not happen. How come?

Download the example program from here.

The secret is the quite complicated relationship between the ListView's selection algorithm and the focus. Button is focusable, basic TextView is not (Button is really just a specialized TextView). ViewGroup (whose child is LinearLayout which encapsulates one list row) delegates the focus to its first focusable child if it is not prevented to do so. As list row gives away the focus to the first focusable child (the Button) and the user did not touch the Button's area, neither the list row, nor the Button is selected. All this happens in "touch mode", try to select the row with the down/up key and you will see a completely different selection algorithm in action - the row is highlighted but the Button is not.



In order to achieve the selection effect you see in the screenshot, the ViewGroup must be prevented giving away the focus. The example program implements a quick and dirty solution: the Button is not focusable (ButtonListAdapter, line 47). More elegant solution would be to declare the ViewGroup (inflated from the buttonrow.xml layout) with FOCUS_BLOCK_DESCENDANTS flag. Conveniently, not being focusable does not prevent the Button in receiving events, click on the action button and the row counter increases nicel

출처 :  http://mgmblog.com/2009/02/06/export-an-android-sqlite-db-to-an-xml-file-on-the-sd-card/ 

Export an Android SQLite db to an XML file on the SD Card

I’m pretty syched about this code.  It took some figuring out and some hunting and asking questions on Google’s Android forum.  But thanks to the following posts

 File permissions for world read/write

SQLite3 sql command to show tables in database

I pieced together the beginnings of some code that will take an Android SQLite db and export it to XML data within a file on the SD Card.  This has a couple potential uses,

  • Backing up program data for importing later
  • Archiving some of the data for an application for possible reprocessing later
  • Archive some data to shrink the memory footprint of the application
  • Export data to a web server
    • Data could be further broken down so only updated rows since last upload would be sent to server
  • (fill in your idea here)

The code is still a little raw, not much error checking or exception handling, but I’m sure you can improve on it in those areas.  I just wanted to get it out so I didn’t have any more excuses.  The DatabaseAssistant class is called from your application and given the database to download.

Can anybody point out any obvious issues here (again, other then error/exception handling).  Hope you can make some use of it, sorry about the funky formatting, someday I’ll get around to a better way of adding code.

public class DatabaseAssistant
{
	private static final String EXPORT_FILE_NAME = "/sdcard/export.xml";

	private Context _ctx;
	private SQLiteDatabase _db;
	private Exporter _exporter;

	public DatabaseAssistant( Context ctx, SQLiteDatabase db )
	{
		_ctx = ctx;
		_db = db;

		try
		{
			// create a file on the sdcard to export the
			// database contents to
			File myFile = new File( EXPORT_FILE_NAME );
                        myFile.createNewFile();

                        FileOutputStream fOut =  new FileOutputStream(myFile);
                        BufferedOutputStream bos = new BufferedOutputStream( fOut );

			_exporter = new Exporter( bos );
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public void exportData( )
	{
		log( "Exporting Data" );

		try
		{
			_exporter.startDbExport( _db.getPath() );

			// get the tables out of the given sqlite database
	    	        String sql = "SELECT * FROM sqlite_master";

	    	        Cursor cur = _db.rawQuery( sql, new String[0] );
	    	        Log.d("db", "show tables, cur size " + cur.getCount() );
	    	        cur.moveToFirst();

	    	        String tableName;
	    	        while ( cur.getPosition() < cur.getCount() )
	    	        {
	    		        tableName = cur.getString( cur.getColumnIndex( "name" ) );
	    		        log( "table name " + tableName );

	    		        // don't process these two tables since they are used
	    		        // for metadata
	    		        if ( ! tableName.equals( "android_metadata" ) &&
	    				! tableName.equals( "sqlite_sequence" ) )
	    		        {
	    			        exportTable( tableName );
	    		        }

	    		        cur.moveToNext();
	    	        }
		        _exporter.endDbExport();
			_exporter.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	private void exportTable( String tableName ) throws IOException
	{
		_exporter.startTable(tableName);

		// get everything from the table
		String sql = "select * from " + tableName;
		Cursor cur = _db.rawQuery( sql, new String[0] );
		int numcols = cur.getColumnCount();

		log( "Start exporting table " + tableName );

//		// logging
//		for( int idx = 0; idx < numcols; idx++ )
//		{
//			log( "column " + cur.getColumnName(idx) );
//		}

		cur.moveToFirst();

		// move through the table, creating rows
		// and adding each column with name and value
		// to the row
		while( cur.getPosition() < cur.getCount() )
		{
			_exporter.startRow();
			String name;
			String val;
			for( int idx = 0; idx < numcols; idx++ )
			{
				name = cur.getColumnName(idx);
				val = cur.getString( idx );
				log( "col '" + name + "' -- val '" + val + "'" );

				_exporter.addColumn( name, val );
			}

			_exporter.endRow();
			cur.moveToNext();
		}

		cur.close();

		_exporter.endTable();
	}

	private void log( String msg )
	{
		Log.d( "DatabaseAssistant", msg );
	}

	class Exporter
	{
		private static final String CLOSING_WITH_TICK = "'>";
		private static final String START_DB = "<export-database name='";
		private static final String END_DB = "</export-database>";
		private static final String START_TABLE = "<table name='";
		private static final String END_TABLE = "</table>";
		private static final String START_ROW = "<row>";
		private static final String END_ROW = "</row>";
		private static final String START_COL = "<col name='";
		private static final String END_COL = "</col>";

		private BufferedOutputStream _bos;

		public Exporter() throws FileNotFoundException
		{
			this( new BufferedOutputStream(
					_ctx.openFileOutput( EXPORT_FILE_NAME,
					Context.MODE_WORLD_READABLE ) ) );
		}

		public Exporter( BufferedOutputStream bos )
		{
			_bos = bos;
		}

		public void close() throws IOException
		{
			if ( _bos != null )
			{
				_bos.close();
			}
		}

		public void startDbExport( String dbName ) throws IOException
		{
			String stg = START_DB + dbName + CLOSING_WITH_TICK;
			_bos.write( stg.getBytes() );
		}

		public void endDbExport() throws IOException
		{
			_bos.write( END_DB.getBytes() );
		}

		public void startTable( String tableName ) throws IOException
		{
			String stg = START_TABLE + tableName + CLOSING_WITH_TICK;
			_bos.write( stg.getBytes() );
		}

		public void endTable() throws IOException
		{
			_bos.write( END_TABLE.getBytes() );
		}

		public void startRow() throws IOException
		{
			_bos.write( START_ROW.getBytes() );
		}

		public void endRow() throws IOException
		{
			_bos.write( END_ROW.getBytes() );
		}

		public void addColumn( String name, String val ) throws IOException
		{
			String stg = START_COL + name + CLOSING_WITH_TICK + val + END_COL;
			_bos.write( stg.getBytes() );
		}
	}

	class Importer
	{

	}

}

'Android > Tip&Tech' 카테고리의 다른 글

마켓 링크 확인하기  (1) 2012.01.30
Lists and focuses in button  (1) 2012.01.26
리스트뷰에 텍스트 먼저 이미지 늦게~  (0) 2012.01.18
Multicolumn ListView in Android  (1) 2012.01.18
ProgressBar 커스터 이미징하기  (0) 2012.01.17

Multicolumn ListView in Android
 

출처 :  http://www.heikkitoivonen.net/blog/2009/02/15/multicolumn-listview-in-android/ 

Ever since I started programming on the Android platform, I have been wondering when the SDK would include a ready-made multicolumn ListView (or listbox as it is often called in other frameworks). One could of course construct such a thing by slapping regular ListViews side by side and then painfully adding code to keep them all in sync when scrolled and so on. It felt such a hack that I used GridView instead. GridView is not really a great list, though, because the cells will all be formatted the same. There are other issues besides that, but for me that was the main one.

I finally saw a blog post which customized ListView to put two TextViews vertically into one line. It was pretty simple going from there to one that would display three TextViews side-by-side. The main layout XML file could define ListView something like this:

<!-- main.xml -->
<ListView android:id="@+id/SCHEDULE" android:layout_width="wrap_content" android:layout_height="wrap_content">
</ListView>

Here is the XML for each row, main point being that we put three TextViews in LinearLayout with horizontal orientation:

<?xml version="1.0" encoding="utf-8"?>
<!-- row.xml -->
<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:paddingTop="4dip"
     android:paddingBottom="6dip"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal">
 
     <TextView android:id="@+id/TRAIN_CELL"
         android:layout_width="50dip"
         android:layout_height="wrap_content"/>
 
     <TextView android:id="@+id/FROM_CELL"
         android:layout_width="70dip"
         android:layout_height="wrap_content" android:layout_weight="1"/>
 
     <TextView android:id="@+id/TO_CELL"
         android:layout_width="60dip"
         android:layout_height="wrap_content"  android:layout_weight="1"/>
</LinearLayout>

Notice how you can format each TextView separately. There is no column separator, but something decent should not be too hard to whip up if desired. My understanding is that the screen width is supposed to be 160 dip, but for some reason I had to use width values that actually add up to more than that, as well as using layout weight to grow some fields proportionally so that when you switch to landscape mode things are still nicely aligned. I would have expected specifying widths in dip would have automatically done that.

Here is how you could populate that ListView with data:

ListView list = (ListView) findViewById(R.id.SCHEDULE);
 
ArrayList<HashMap<String, String>> mylist = new ArrayList<HashMap<String, String>>();
HashMap<String, String> map = new HashMap<String, String>();
map.put("train", "101");
map.put("from", "6:30 AM");
map.put("to", "7:40 AM");
mylist.add(map);
map = new HashMap<String, String>();
map.put("train", "103(x)");
map.put("from", "6:35 AM");
map.put("to", "7:45 AM");
mylist.add(map);
// ...
mSchedule = new SimpleAdapter(this, mylist, R.layout.row,
            new String[] {"train", "from", "to"}, new int[] {R.id.TRAIN_CELL, R.id.FROM_CELL, R.id.TO_CELL});
list.setAdapter(mSchedule);

The main point here is that the SimpleAdapter requires the data to be in a List, where each entry is a Map. In my case, I simulate the columns by putting in three entries into each of the maps, and the maps each have the same keys. The adapter then maps the key values (like "train") to the corresponding TextView (R.id.TRAIN_CELL).

Putting the above pieces of code into Caltroid produces results that look like this:

Multicolumn ListView in Android

Multicolumn ListView in Android

There is quite a bit of work to create the data structure for the adapter in this way, so for anything involving more than a trivial amount of data this is probably going to be too slow. With more data, a database is almost certainly involved and theSimpleCursorAdapter works almost exactly like SimpleAdapter, except that SimpleCursorAdapter maps column names from the query results to the appropriate views.

Similar Posts:

customized : 주문에 응하여 만들다, 커스터마이즈하다
출처 :http://pppplqqqq.blog.me/100107860575

ProgressBar 설정 

<ProgressBar android:layout_width="wrap_content"
android:layout_height="wrap_content" 
android:id="@+id/progress_load" 
      style="@style/ProgressBar_Large_new">
</ProgressBar>

styles.xml 파일에 추가 (res/values/styles.xml)

   <style name="ProgressBar_Large_new" parent="@android:style/Widget.ProgressBar"> 
        <item name="android:indeterminateDrawable">@drawable/progress_large_color</item>
        <item name="android:minWidth">76dip</item>
        <item name="android:maxWidth">76dip</item>
        <item name="android:minHeight">76dip</item>
        <item name="android:maxHeight">76dip</item>
    </style>

drawable 폴더에 progress_large_color.xml 추가 

<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/spinner" <!-- 변경할 이미지 -->
    android:pivotX="50%"
    android:pivotY="50%"
/>

참고 : 
android sdk 에 있는 progress_large.xml 를 참고하면 
android:framesCount="12"
android:frameDuration="100" 
위의 속성을 지정할수 있는데 사용자 지정 이미지로 변경할때는 왜 인지 이 두 속성을 설정할수 없었다. 

그래서 기본값으로 주기로 했다. 

출처 : http://jsharkey.org/blog/2008/08/18/separating-lists-with-headers-in-android-09/

Separating Lists with Headers in Android 0.9

Earlier today the latest Android 0.9 SDK was released, and it’s packed full of wonderful changes. As you play around, you might see ListViews split into sections using separating headers. (Example shown on the right is the browser settings list.)

There isn’t an easy way of creating these separated lists, so I’ve put together SeparatedListAdapter which does it quickly. To summarize, we’re creating a new BaseAdapter that can contain several other Adapters, each with their own section headers.

First let’s create some simple XML layouts to be used for our lists: first the header view, then two item views that we’ll use later for the individual lists. (Thanks to Romain Guy for helping me find existing styles to keep these XML layouts nice and tidy.)

  1. <!-- list_header.xml -->  
  2. <TextView  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     android:id="@+id/list_header_title"  
  5.     android:layout_width="fill_parent"  
  6.     android:layout_height="wrap_content"  
  7.     android:paddingTop="2dip"  
  8.     android:paddingBottom="2dip"  
  9.     android:paddingLeft="5dip"  
  10.     style="?android:attr/listSeparatorTextViewStyle" />  
  11.   
  12. <!-- list_item.xml -->  
  13. <TextView  
  14.     xmlns:android="http://schemas.android.com/apk/res/android"  
  15.     android:id="@+id/list_item_title"  
  16.     android:layout_width="fill_parent"  
  17.     android:layout_height="fill_parent"  
  18.     android:paddingTop="10dip"  
  19.     android:paddingBottom="10dip"  
  20.     android:paddingLeft="15dip"  
  21.     android:textAppearance="?android:attr/textAppearanceLarge"  
  22.     />  
  23.   
  24. <!-- list_complex.xml -->  
  25. <LinearLayout  
  26.     xmlns:android="http://schemas.android.com/apk/res/android"  
  27.     android:layout_width="fill_parent"  
  28.     android:layout_height="wrap_content"  
  29.     android:orientation="vertical"  
  30.     android:paddingTop="10dip"  
  31.     android:paddingBottom="10dip"  
  32.     android:paddingLeft="15dip"  
  33.     >  
  34.     <TextView  
  35.         android:id="@+id/list_complex_title"  
  36.         android:layout_width="fill_parent"  
  37.         android:layout_height="wrap_content"  
  38.         android:textAppearance="?android:attr/textAppearanceLarge"  
  39.         />  
  40.     <TextView  
  41.         android:id="@+id/list_complex_caption"  
  42.         android:layout_width="fill_parent"  
  43.         android:layout_height="wrap_content"  
  44.         android:textAppearance="?android:attr/textAppearanceSmall"  
  45.         />  
  46. </LinearLayout>  

Now let’s create the actual SeparatedListAdapter class which provides a single interface to multiple sections of other Adapters. After using addSection() to construct the child sections, you can easily use ListView.setAdapter() to present the now-separated list to users.

As for the Adapter internals, to correctly find the selected item among the child Adapters, we walk through subtracting from the original position until we find either a header (position = 0) or item in the current child Adapter (position < size).

Here’s the source for SeparatedListAdapter:

  1. public class SeparatedListAdapter extends BaseAdapter {   
  2.   
  3.     public final Map<String,Adapter> sections = new LinkedHashMap<String,Adapter>();   
  4.     public final ArrayAdapter<String> headers;   
  5.     public final static int TYPE_SECTION_HEADER = 0;   
  6.   
  7.     public SeparatedListAdapter(Context context) {   
  8.         headers = new ArrayAdapter<String>(context, R.layout.list_header);   
  9.     }   
  10.   
  11.     public void addSection(String section, Adapter adapter) {   
  12.         this.headers.add(section);   
  13.         this.sections.put(section, adapter);   
  14.     }   
  15.   
  16.     public Object getItem(int position) {   
  17.         for(Object section : this.sections.keySet()) {   
  18.             Adapter adapter = sections.get(section);   
  19.             int size = adapter.getCount() + 1;   
  20.   
  21.             // check if position inside this section   
  22.             if(position == 0return section;   
  23.             if(position < size) return adapter.getItem(position - 1);   
  24.   
  25.             // otherwise jump into next section   
  26.             position -= size;   
  27.         }   
  28.         return null;   
  29.     }   
  30.   
  31.     public int getCount() {   
  32.         // total together all sections, plus one for each section header   
  33.         int total = 0;   
  34.         for(Adapter adapter : this.sections.values())   
  35.             total += adapter.getCount() + 1;   
  36.         return total;   
  37.     }   
  38.   
  39.     public int getViewTypeCount() {   
  40.         // assume that headers count as one, then total all sections   
  41.         int total = 1;   
  42.         for(Adapter adapter : this.sections.values())   
  43.             total += adapter.getViewTypeCount();   
  44.         return total;   
  45.     }   
  46.   
  47.     public int getItemViewType(int position) {   
  48.         int type = 1;   
  49.         for(Object section : this.sections.keySet()) {   
  50.             Adapter adapter = sections.get(section);   
  51.             int size = adapter.getCount() + 1;   
  52.   
  53.             // check if position inside this section   
  54.             if(position == 0return TYPE_SECTION_HEADER;   
  55.             if(position < size) return type + adapter.getItemViewType(position - 1);   
  56.   
  57.             // otherwise jump into next section   
  58.             position -= size;   
  59.             type += adapter.getViewTypeCount();   
  60.         }   
  61.         return -1;   
  62.     }   
  63.   
  64.     public boolean areAllItemsSelectable() {   
  65.         return false;   
  66.     }   
  67.   
  68.     public boolean isEnabled(int position) {   
  69.         return (getItemViewType(position) != TYPE_SECTION_HEADER);   
  70.     }   
  71.   
  72.     @Override  
  73.     public View getView(int position, View convertView, ViewGroup parent) {   
  74.         int sectionnum = 0;   
  75.         for(Object section : this.sections.keySet()) {   
  76.             Adapter adapter = sections.get(section);   
  77.             int size = adapter.getCount() + 1;   
  78.   
  79.             // check if position inside this section   
  80.             if(position == 0return headers.getView(sectionnum, convertView, parent);   
  81.             if(position < size) return adapter.getView(position - 1, convertView, parent);   
  82.   
  83.             // otherwise jump into next section   
  84.             position -= size;   
  85.             sectionnum++;   
  86.         }   
  87.         return null;   
  88.     }   
  89.   
  90.     @Override  
  91.     public long getItemId(int position) {   
  92.         return position;   
  93.     }   
  94.   
  95. }  

As expected, it correctly prevents the section headers from being selected, and seamlessly stiches together the various Adapters.

This approach also uses convertView correctly as long as the child Adapters return getItemViewType() and getViewTypeCount() normally. No special changes are needed for an Adapter to become a child.

Now let’s use SeparatedListAdapter in some example code. We use the XML layouts defined earlier to create an ArrayAdapter and an advanced two-row SimpleAdapter, and then add both as sections to our SeparatedListAdapter.

  1. public class ListSample extends Activity {   
  2.   
  3.     public final static String ITEM_TITLE = "title";   
  4.     public final static String ITEM_CAPTION = "caption";   
  5.   
  6.     public Map<String,?> createItem(String title, String caption) {   
  7.         Map<String,String> item = new HashMap<String,String>();   
  8.         item.put(ITEM_TITLE, title);   
  9.         item.put(ITEM_CAPTION, caption);   
  10.         return item;   
  11.     }   
  12.   
  13.     @Override  
  14.     public void onCreate(Bundle icicle) {   
  15.         super.onCreate(icicle);   
  16.   
  17.         List<Map<String,?>> security = new LinkedList<Map<String,?>>();   
  18.         security.add(createItem("Remember passwords""Save usernames and passwords for Web sites"));   
  19.         security.add(createItem("Clear passwords""Save usernames and passwords for Web sites"));   
  20.         security.add(createItem("Show security warnings""Show warning if there is a problem with a site's security"));   
  21.   
  22.         // create our list and custom adapter   
  23.         SeparatedListAdapter adapter = new SeparatedListAdapter(this);   
  24.         adapter.addSection("Array test"new ArrayAdapter<String>(this,   
  25.             R.layout.list_item, new String[] { "First item""Item two" }));   
  26.         adapter.addSection("Security"new SimpleAdapter(this, security, R.layout.list_complex,   
  27.             new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.list_complex_title, R.id.list_complex_caption }));   
  28.   
  29.         ListView list = new ListView(this);   
  30.         list.setAdapter(adapter);   
  31.         this.setContentView(list);   
  32.   
  33.     }   
  34.   
  35. }  

The resulting interface behaves just like the browser preferences list, and you could easily create other custom Adapters to insert into the various sections, such as including icons or checkboxes.

These section headers can really help separate out otherwise-cluttered activities. I used them several places in my CompareEverywhere application which lets you easily compare prices and read reviews for any product with a barcode.



Copyright Jeff Sharkey. This is my personal website, and the views expressed on these pages are mine alone and not those of my employer.

출처 :  http://blogingtutorials.blogspot.com/2010/11/android-listview-header-two-or-more-in.html 
Hello Friends,


There are two or many more header listview in android. So Today we are discussed about the two header of android.

And See Also Simple Listview Display In Android Device.

So This are the all java and xml file given below. and this are the very useful projects.

main.xml


<?xml version="1.0" encoding="utf-8"?>
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="true"
/>

header.xml

<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center" android:scrollbars="none"
style="?android:attr/listSeparatorTextViewStyle" />

Now The java File code Given below.

SectionedAdapter .java

package com.android.listview;

import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import java.util.ArrayList;
import java.util.List;

abstract public class SectionedAdapter extends BaseAdapter {
abstract protected View getHeaderView(String caption, int index,
View convertView, ViewGroup parent);

private List
sections = new ArrayList
();
private static int TYPE_SECTION_HEADER = 1;

public SectionedAdapter() {
super();
}

public void addSection(String caption, Adapter adapter) {
sections.add(new Section(caption, adapter));
}

public Object getItem(int position) {
for (Section section : this.sections) {
if (position == 0) {
return (section);
}

int size = section.adapter.getCount() + 1;

if (position <>
return (section.adapter.getItem(position - 1));
}

position -= size;
}

return (null);
}

public int getCount() {
int total = 0;

for (Section section : this.sections) {
total += section.adapter.getCount() + 1; // add one for header
}

return (total);
}

public int getViewTypeCount() {
int total = 1; // one for the header, plus those from sections

for (Section section : this.sections) {
total += section.adapter.getViewTypeCount();
}

return (total);
}

public int getItemViewType(int position) {
int typeOffset = TYPE_SECTION_HEADER + 1; // start counting from here

for (Section section : this.sections) {
if (position == 0) {
return (TYPE_SECTION_HEADER);
}

int size = section.adapter.getCount() + 1;

if (position <>
return (typeOffset + section.adapter
.getItemViewType(position - 1));
}

position -= size;
typeOffset += section.adapter.getViewTypeCount();
}

return (-1);
}

public boolean areAllItemsSelectable() {
return (false);
}

public boolean isEnabled(int position) {
return (getItemViewType(position) != TYPE_SECTION_HEADER);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
int sectionIndex = 0;

for (Section section : this.sections) {
if (position == 0) {
return (getHeaderView(section.caption, sectionIndex,
convertView, parent));
}

int size = section.adapter.getCount() + 1;

if (position <>
return (section.adapter.getView(position - 1, convertView,
parent));
}

position -= size;
sectionIndex++;
}

return (null);
}

@Override
public long getItemId(int position) {
return (position);
}

class Section {
String caption;
Adapter adapter;

Section(String caption, Adapter adapter) {
this.caption = caption;
this.adapter = adapter;
}
}
}

Selection.java


package com.commonsware.android.listview;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class SectionedDemo extends ListActivity {
private static String[] items = { "US", "UK", "CANADA", "JAPAN", "SINGAPORE",
"INDIA", "CHINA" };

private static String[] Sect = { "GOOGLE", "FACEBOOK","DELL" };

private static String[] Doc = { "FRONT", "TOP","BACK" };

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

adapter.addSection("step 1", new ArrayAdapter(this,
android.R.layout.simple_list_item_1, items));

adapter.addSection("Step 2", new ArrayAdapter(this,
android.R.layout.simple_list_item_1, Sect));

adapter.addSection("Step 3", new ArrayAdapter(this,
android.R.layout.simple_list_item_1, Doc));

setListAdapter(adapter);
}

SectionedAdapter adapter = new SectionedAdapter() {
protected View getHeaderView(String caption, int index,
View convertView, ViewGroup parent) {
TextView result = (TextView) convertView;

if (convertView == null) {
result = (TextView) getLayoutInflater().inflate(
R.layout.header, null);
}

result.setText(caption);

return (result);
}
};
}


And Now The output given below.





출처 : http://blog.naver.com/PostView.nhn?blogId=kippee&logNo=130048583564

아직 까지는 이 세개의 관계가 정리가 안된다.

http://codinghard.wordpress.com/2009/05/16/android-thread-messaging/ 에서는

Handler와 Looper의 역할에 대하여 아래와 같이 예를 들어 설명한다. 

 안드로이드는 Thread간에 통신하기 위해서 Handler와 Looper를 제공하고 있다.

Child Thread가 웹으로 부터 이미지를 가져와 생성하려고 할때의 예를 제시하고 있다. 생성이 끝나고 (아마도 imageView형태로 바꾸어서) Main Thread의 Message Queue와 연동된 Handler를 이용하여 Message를 송신하는것으로 Main Thread에 알려준다. 물론 Child Thread에서 생성된 데이터 (ImageView)는 Message에 포함되어 있다.

그리고 간단한 Coding 예를 보여 주는 데

일단 두개의 Thread를 생성하고 (여기서 이미 Main은 기본적으로 Application이 Start 될때

Run 됨으로 Child Thread만 생성한다)

 new ChildThread().start();


 또한 들어오는 Message를 Listening 하면서 Looper 가

받아 먹어야 되는데 Main은 기본적으로 항상 Listening 상태이기 때문에 별도의 Looper가

필요 없으며 단지 들어온 Message를 처리할 수 있는 Handler만 생성한다.

   mMainHandler = new Handler() {

   public void handleMessage(Message msg) {

    mMainReceiveView.setText( (String)msg.obj );
   }
  };

이와는 반대로 Child Thread는 계속 Running 하면서 들어오는 Message를 받아 먹고 이를

Handler 하여금 처리해야 하기 때문에 아래의 코드처럼 전형적인 Format으로

Coding 된다.

  class ChildThread extends Thread {

  private static final String INNER_TAG = "ChildThread";

  public void run() {

   this.setName("Child");

   /*
    * You have to prepare the looper before creating the handler.
    */
   Looper.prepare();

   /*
    * Create the child handler on the child thread so it is bound to the
    * child thread's message queue.
    */
   mChildHandler = new Handler() {

    public void handleMessage(Message msg) {

     /*
      * Send the processing result back to the main thread.
      */
     mChildReceiveMsg = (String) msg.obj;
     showDialog(POPUP_CHILD_THREAD);
    }
   };

    /*
    * Start looping the message queue of this thread.
    */
   Looper.loop();
  }
 }

위에서 Override 된 Method handleMessage는 들어오는 Message를 처리하는 Logic이 들어간다.

여기서는 Message가 들어오면 Dialog 창으로 Popup이 되게 하였다

 

첨부파일 ThreadMessaging0905227.zip 은  사이트 http://codinghard.wordpress.com/2009/05/16/android-thread-messaging/ 을 Project로 만들어 실행본 것이고

이것을 내 입맛이 조금 Customizing 하여 바꾸어 본것인데 아래 그림은

Child에서 보낼 Message 입력하여 Send to Child를 Click하면

 

아래와 같은 Dialog 가 뜨고 다시 Main에 보내기 위해 "Hi Daddy"를 입력하고

Send to Main Button을 클릭하면

 

 

 

아래 그림과 같이 Main Thread의 UI에 Child가 보낸 Message가 표시된다.

 

위의 두번째 그림에서 AlertDialog Builder를 사용하였는데 기본적으로는 EditText Widget를 제공안한다. 따라서 별도의 Custom View를 사용하였는데

이에 자세한 내용은 http://openproject.tistory.com/tag/팝업 (내용이 아주 알차다)을 참조하길 바란다.

이 부분도 첨부파일로 올렸다 (두번째 파일)

 

이제 화두 Looper Handler Thread의 첫발자국인가?

아참!!! 생성된 Child Thread는 직접 UI를 Access 할 수 없다. 즉 Toast나 위의 예처럼 별도의 Dialog로 처리해야 됨.

[출처] Looper Handler Thread (1)|작성자 커트

Android: how to create transparent or opeque background 

출처 : http://zaman91.wordpress.com/2010/03/22/android-how-to-create-transparent-or-opeque-background/
 
 

You can implement application styles as you like in android platform. If you want to create a transparent background Activity then follow the steps below.

Transparency of background:

  1. create an activity
  2. set activity’s theme as “@android:style/Theme.Translucent” in AndroidManifest.xml
  3. that’s all

If it is needed to have a UI with semi-transparent background, and to define opacity then use this technique withTheme.Translucent. If you set the color of the UI layout as #29000000, the first hex 2-digit(#29) represents alpha channels of color and set opacity. If value is 00 that means 100% transparent. if value is set FF it will be opaque, it can be any value in 00 to FF.

Opaqueness of background / Opacity:

  1. create an activity
  2. set activity’s theme as “@android:style/Theme.Translucent” in AndroidManifest.xml
  3. declare opaque_back a color with setting alpha value such as #29000000(blackish), in …/res/colors.xml
  4. set activity’s layout background with this color in such as yourlayout.xml  <LinearLayout
    android:background=”@color/opaque_back”> … </…>
  5. that’s all

Have a nice Android journey.

References:

  • http://developer.android.com/guide/topics/ui/themes.html#Properties
  • http://brainflush.wordpress.com/2009/03/15/understanding-android-themes-and-styles/

You need to use the new android.animation framework (object animators) withFragmentTransaction.setCustomAnimations as well as withFragmentTransaction.setTransition.

Here's an example on using setCustomAnimations from ApiDemos' FragmentHideShow.java:

ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);

and here's the relevant animator XML from res/animator/fade_in.xml:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
   
android:interpolator="@interpolator/accelerate_quad"
   
android:valueFrom="0"
   
android:valueTo="1"
   
android:propertyName="alpha"
   
android:duration="@android:integer/config_mediumAnimTime" />

Note that you can combine multiple animators using <set>, just as you could with the older animation framework.


EDIT: Since folks are asking about slide-in/slide-out, I'll comment on that here.

Slide-in and slide-out

You can of course animate the translationXtranslationYx, and y properties, but generally slides involve animating content to and from off-screen. As far as I know there aren't any translation properties that use relative values. However, this doesn't prevent you from writing them yourself. Remember that property animations simply require getter and setter methods on the objects you're animating (in this case views), so you can just create your own getXFraction and setXFractionmethods on your view subclass, like so:

public class MyFrameLayout extends FrameLayout {
   
...
   
public float getXFraction() {
       
return getX() / getWidth(); // TODO: guard divide-by-zero
   
}

   
public void setXFraction(float xFraction) {
       
// TODO: cache width
       
final int width = getWidth();
        setX
((width > 0) ? (xFraction * width) : -9999);
   
}
   
...
}

Now you can animate the 'xFraction' property, like so:

res/animator/slide_in.xml:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
   
android:interpolator="@android:anim/linear_interpolator"
   
android:valueFrom="-1.0"
   
android:valueTo="0"
   
android:propertyName="xFraction"
   
android:duration="@android:integer/config_mediumAnimTime" />

Note that if the object you're animating in isn't the same width as its parent, things won't look quite right, so you may need to tweak your property implementation to suit your use case.

출처 :  http://www.androidpub.com/878847 
안녕하세요 그냥가자입니다.

오늘 만들어볼 것은 (무슨 오늘의 요리 삘이네요..) Sliding Toggle 버튼입니다.
토글버튼으로 만들면 드래그 효과가 안나죠?
그렇다고 그냥 직접 끌고 다니게 만들자니 코딩량이 만만찮습니다.

전 언제나 짧고 간단하게 만듭니다. 귀찮거든요 ㅡㅡㅋ

그럼 시작해보겠습니다.

SlideButton.java

01.public class SlideButton extends SlidingDrawer implements Checkable,
02.OnDrawerOpenListener, OnDrawerCloseListener{
03.int mButtonResource = 0;
04.OnCheckChangedListner mOnCheckChangedListner;
05.public SlideButton(Context context, AttributeSet attr){
06.super(context, attr);
07. 
08.setOnDrawerOpenListener(this);
09.setOnDrawerCloseListener(this);
10.}
11. 
12. 
13.@Override
14.public boolean isChecked() {
15.// TODO Auto-generated method stub
16.return !isOpened();
17.}
18. 
19.@Override
20.public void setChecked(boolean checked) {
21.// TODO Auto-generated method stub
22.if(!isOpened() != checked){
23.if(checked){
24.this.animateClose();
25.}else{
26.this.animateOpen();
27.}
28.}
29. 
30.}
31. 
32.@Override
33.public void toggle() {
34.// TODO Auto-generated method stub
35.if(!isOpened()){
36.this.animateOpen();
37.}else{
38.this.animateClose();
39.}
40. 
41.}
42. 
43.public interface OnCheckChangedListner{
44.public void onCheckChanged(View v, boolean isChecked);
45.}
46. 
47.@Override
48.public void onDrawerOpened() {
49.// TODO Auto-generated method stub
50.if(mOnCheckChangedListner != null){
51.mOnCheckChangedListner.onCheckChanged(this, isChecked());
52.}
53.}
54. 
55.@Override
56.public void onDrawerClosed() {
57.// TODO Auto-generated method stub
58.if(mOnCheckChangedListner != null){
59.mOnCheckChangedListner.onCheckChanged(this, isChecked());
60.}
61.}
62.public OnCheckChangedListner getOnCheckChangedListner() {
63.return mOnCheckChangedListner;
64.}
65. 
66.public void setOnCheckChangedListner(
67.OnCheckChangedListner onCheckChangedListner) {
68.this.mOnCheckChangedListner = onCheckChangedListner;
69.}
70.}



뭐 딴거 없구요... SlidingDrawer를 상속받아서 했다는게 잴큰 특징 되겠습니다.
그리고 Checkable을 구현한건 CompoundButton (라디오버튼 같은 넘들) 처럼 붙일려고 한겁니다.


slide_button.xml

01.<LinearLayout
03.android:layout_width="wrap_content"
04.android:layout_height="wrap_content"
05.>
06.<a.b.c.SlideButton
07.android:layout_width="112px"
08.android:layout_height="48px"
09.android:content="@+id/slideContentLayout"
10.android:handle="@+id/slideHandle"
11.android:background="@drawable/sns_toggle_btn_bg"
12.android:orientation="horizontal"
13.android:id="@+id/slideButton"
14. 
15.>
16.<Button
17.android:layout_width="wrap_content"
18.android:layout_height="wrap_content"
19.android:id="@+id/slideHandle"
20.android:background="@drawable/sns_toggle_btn_bar"
21.>
22.</Button>
23.<LinearLayout
24.android:layout_width="fill_parent"
25.android:layout_height="fill_parent"
26.android:layout_weight "1"
27.android:background="#00000000"
28.android:id="@+id/slideContentLayout"
29.>
30.</LinearLayout>
31.</a.b.c.SlideButton>
32.</LinearLayout>


버튼과 배경이 중요한데요...
배경은 On/Off을 하나로 가지고 있고
버튼으로 한쪽을 가리는 형태로 만드시면됩니다.
버튼이 길이가 1이라면 배경은 길이가 2정도 되겠죠


MainActivity.java

01.requestWindowFeature(Window.FEATURE_NO_TITLE);
02.setContentView(R.layout.slide_button);
03.sb = (SlideButton)findViewById(R.id.slideButton);
04. 
05. 
06.sb.setOnCheckChangedListner(new SlideButton.OnCheckChangedListner() {
07. 
08.@Override
09.public void onCheckChanged(View v, boolean isChecked) {
10.// TODO Auto-generated method stub
11.Log.i("aaa","bbb");
12.Toast t = Toast.makeText(context, Boolean.toString(isChecked), Toast.LENGTH_LONG);
13.t.show();
14.}
15.});


이부분은 onCreate의 일부인데요... 그냥 onCreate에 저것만 쓰셔도 나올겁니다.

스샷은 제가 사용한 이미지가 공개하면 좀 애매해서...
생략하도록하겠습니다. 써보신분들이 후기 올려주시면 감사하겠습니다

  1. Exceptions: 예외 무시하지말고 처리하기.
  2. Exceptions: 상위 Exception으로 싸잡아서 처리하지 않기.
  3. Finalizers: 왠만하면 쓰지않기 (언제 적용될지 모름)
  4. Imports: *쓰지말고 정확하게 풀네임 적기.

Java Library Rules

표준 코딩컨벤션이 바뀌어서 예전 코딩컨벤션과 충돌이 난다면 예전 코딩컨벤션으로 작성해서 일관성을 유지하기.

Java Style Rules

자바표준 컨벤션에서 추가사항:

  1. Comments/Javadoc: 표준대로 작성하기.
  2. Short methods: 메소드는 40줄이 넘지않게 짧게 작성하기
  3. Fields: 초기에 선언하기나 사용하기 바로 전에 선언할 것.
  4. Local variables: 지역변수 범위는 최소화하기.
  5. Imports: 안드로이드, 서드파티(알파벳 순), java, javax 순으로 import하기.
  6. Indentation: 탭안쓰고 공백 4개 사용하기.
  7. Line length: 한줄에 100칸 이하 유지하기.
  8. Field names: Non-public, non-static 변수는 m으로 시작하고, static변수는 s로 시작하기.
  9. Braces: { 는 줄넘기지말고 사용하기
  10. Annotations: 표준 어노테이션 사용하기.
  11. Acronyms are words: XMLHTTPReques처럼 적지말고 XmlHttpRequest로 적기
  12. TODO style: "TODO: write this description"
  13. Consistency: 일관적으로 작성하기
  14. Logging: 로그도 비용이 드니 적절하기 사용하기
    ERROR > WARNING > INFORMATION > DEBUG > VERBOSE 사용할 것.
    한줄에 출력할 수 있는 80~100글자가 적당.
    StringBuilder는 기본버퍼가 16character라 String보다 항상 좋다고 할 수 없으니 확신이 없으면 그냥 String연산이 무난.
    System.out.print는 어차피 /dev/null로 던져버리니 절대 쓰지말 것. 괜히 비용만 잡아먹음.


자바 코드 스타일 규칙인데요.. 대략 이렇게 하고 있는데 다른분들 코드를 보면 뭔가 깔끔하다고 생각이 되는데

제 코드를 보면 헐....소리밖에 안나옵니다..



혹시 코드 이쁘게 짜는 노하우 같은건 없을까요..ㅠ.ㅠ

<script src="http://givenjazz.tistory.com/plugin/CallBack_bootstrapper?&src=http://s1.daumcdn.net/cfs.tistory/v/0/blog/plugins/CallBack/callback&id=44&callbackId=givenjazztistorycom442238&destDocId=callbacknestgivenjazztistorycom442238&host=http://givenjazz.tistory.com&float=left&random=313"></script>
출처 : http://isulnara.com/tt/227

안드로이드(android) 앱이 실행되면서 스플래시(splash, 로딩) 화면을 띄우는 소스입니다.


여러가지 방법이 있겠지만 제가 사용하는 방법은 아래와 같습니다.

AndroidManifest.xml
<!-- 메인 액티비티 -->
<activity android:name="MainActivity" android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
</activity>
        
<!-- 스플래시(로딩) 액티비티 -->
<activity android:name="SplashActivity"/>

MainActivity .java
public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        startActivity(new Intent(this, SplashActivity.class));

        initialize();  // 시간이 걸리는 작업 처리
    }

    /**
     * 스플래시 표시하는 것과 초기화를 동시에 진행시키기 위하여 쓰레드 처리
     *
     */
    private void initialize()
    {
        InitializationRunnable init = new InitializationRunnable();
        new Thread(init).start();
    }


    /**
     * 초기화 작업 처리
     *
     */
    class InitializationRunnable implements Runnable
    {
        public void run()
        {
            // 여기서부터 초기화 작업 처리
            // do_something
        }
    }
}

SplashActivity.java
public class SplashActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splash);

        initialize();
    }

    private void initialize()
    {
        Handler handler =    new Handler()
                                     {
                                         @Override
                                         public void handleMessage(Message msg)
                                         {
                                             finish();    // 액티비티 종료
                                         }
                                     };

        handler.sendEmptyMessageDelayed(0, 3000);    // ms, 3초후 종료시킴
    }
}


* 화면을 가로/세로 모드로 전환할 경우 스플래쉬 액티비티가 보이는 문제가 발생할 경우 대처
- 여러가지 방법이 있겠지만 아래처럼 처리하면 됩니다.(2010/10/27 추가)

AndroidManifest.xml
<!-- 메인 액티비티 -->
<activity android:name="MainActivity" android:label="@string/app_name"android:configChanges="keyboardHidden|orientation">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
</activity>

<!-- 스플래시(로딩) 액티비티 -->
<activity android:name="SplashActivity"/>
예제 다운로드:

출처 : http://blog.naver.com/oklmg/70105002506

태스크란? (Task, Activity Stack)

어피니티란? (Android Affinity)
플래그란? (Android Flag)



안드로이드 태스크란? (Android Task, Activity Stack)

-  Task는 어플리케이션에서 실행되는 액티비티를 보관하고 관리하며 Stack형태의 연속된 Activity로 이루어진다

- 선입후출(First In Last Out)형태로 나중에 적재된 액티비티일 수록 가장 먼저 사용된다
  만약 1페이지>2페이지>3페이지 순으로 액티비티를 이동했을때 실행순서대로 Task에 push해 놓았다가
  back버튼을 누르면 3페이지>2페이지>1페이지순으로 Task에서 pop시켜 되돌아 간다고 생각하면 된다

- 서로 다른 어플리케이션간의 이동에도 Task를 이용해 사용자 경험(UX)를 유지시켜 준다

- 최초적재 액티비티는 Root Activity 라고 하며 어플리케이션 런처로부터 시작된다

- 마지막으로 적재되는 액티비티는 Top Activity 라고 하며 현재 화면에 활성화 되어있는 액티비티를 말한다

- Task내에는 서로 다른 어플리케이션의 액티비티들이 포함될 수 있어 어플리케이션에 경계없이 
  하나의 어플리케이션인것 처럼 보이게 해준다

- Task의 Stack내에 존재하는 액티비티들은 모두 묶여서 background와 foreground로 함께 이동한다
  홈버튼 클릭(task interrupt => background 이동), 홈버튼 롱클릭(recent task => foreground 이동)

- Flag를 사용하여 Task내 액티비티의 흐름을 제어할 수 있다


어피니티란? (Android Affinity)

- 어플리케이션 내의 액티비티들은 하나의 어피니티를(affinity:친화력) 가지고 있다

- AndroidManifest 에서 <activity> 요소의 taskAffinity 속성을 사용해 개별 affinity가 지정 가능하다

- FLAG_ACTIVITY_NEW_TASK 플래그를 가진 인텐트 객체로 부터 호출된 allowTaskReparenting 속성을
  True로 가지고 있는 액티비티에 한해 affinity가 동작한다

- 위 조건이 만족한 상황에서 시작된 액티비티는 자신과 동일한 어피니티를 갖는 태스크가 있을경우
  해당 태스크로 이동한다

- 즉, [b]어피니티를 가진 A액티비티가 호출되어 해당 태스크에 속해있을때 [b]어피니티를 가진 태스크가
  호출되면 A액티비티는 [b]어피니티를 가진 태스크로 이동한다

- 어피니티에 의해 태스크가 이동된 후에 back버튼으로 반환시 원래 해당하던 태스크로 돌아간다

- 하나의 어플리케이션내에서 하나 이상의 기능을 갖는 어플리케이션이 존재할경우 각 액티비티별로 다른
  어피니티를 지정해 관리할 수 있다


플래그란? (Android Flag)

- AndroidManifest 에서 플래그를 사용할때에는 <activity> 요소의 launchMode 속성을 사용하며
  launchMode에서 사용가능한 속성은 다음과 같이 4가지만 가능하다

  standard: 
  스택중 어느곳에나 위치 가능하며 여러개의 인스턴스가 생성가능하다

  singleTop: 
  스택중 어느곳에나 위치 가능하며 여러개의 인스턴스가 생성가능하고 호출한 activity와 현재
  최상위 activity가(top activity) 동일한 경우 최상위 activity가 재사용 된다(기존 최상위 activity는 pop)

  singleTask: 
  루트 액티비티로만 존재하며 하나의 인스턴스만 생성가능하다(타 task에서 동일 activity 사용불가)
  다른 액티비티 실행시 동일 Task내에서 실행이 가능하다
 
 singleInstance: 루트 액티비티로만 존재하며 하나의 인스턴스만 생성가능하고 태스크내에 해당
  액티비티 하나만 속할 수 있어
 다른 액티비티를 실행시키면 새로운 Task가 생성되어
  (FLAG_ACTIVITY_NEW_TASK와 동일) 그 Task내에 포함된다


- 소스코드에서 플래그를 사용하고 싶을때에는 Intent에 addFlags() 또는 setFlags() 메소드를 사용한다

  FLAG_ACTIVITY_NEW_TASK:
  동일 affinity의 task가 있으면 그곳에 실행되고 아니면 새로운 task를 실행

  FLAG_ACTIVITY_SINGLE_TOP:
  상단 singleTop과 같으며, 실행시 재사용 액티비티의 실행은 onPause(), onNewIntent(), onResume()
  순으로 호출된다
  ☞ [B]를 single top설정: [A][B] 상태에서 [B] 호출시 => [A][재사용된B]
  ☞ [B]를 single top설정: [B][A] 상태에서 [B] 호출시 => [B][A][B]

  FLAG_ACTIVITY_NO_HISTORY:
  해당 액티비티는 재활성화시(back키를 눌러 다시 활성화될때) pop 된다
  ☞ [B]를 no history설정: [A][B][A] 상태에서 back키 사용시 [A]가 pop 되고 [B] 역시
  no history에 의해 pop => [A]

  FLAG_ACTIVITY_REORDER_TO_FRONT:
  activity 호출시 이미 같은 activity가 task내에 있으면 같은 activity는 pop 시키고 해당 activity가 push 된다
  ☞ [A]를 reorder to front설정: [A][B] 상태에서 [A] 호출시 같은 activity인 [A]가 pop되고 => [B][A]

  FLAG_ACTIVITY_CLEAR_TOP:
  해당 task에 있는 모든 activity를 pop 시키고 해당 activity가 root activity로 task에 push된다
  ☞ [A]를 clear top설정: [A][B] 상태에서 [A] 호출시 모두 pop되고 => [A]
  단, 해당 플래그는 액티비티를 모두 onDestroy() 시킨 후 새롭게 onCreate() 시키기 때문에 [A]를
  유지하려면 FLAG_ACTIVITY_SINGLE_TOP 플래그와 함께 사용하면 된다

http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_BROUGHT_TO_FRONT


Clear Task

- Task를 오랫동안 사용하지 않고 방치해 두면 시스템은 Root Activity를 제외한 모든 액티비티를 Clear 시킨다
- 이러한 동작은 Activity의 속성을 수정하여 제어할 수 있다

  alwaysRetainTaskState:
  Task의 Root Activity에 true로 설정되어 있다면 상단에 언급되었던 동작은 발생하지 않으며 Task는
  오랜 시간 이후에도 Stack에 있는 모든 Activity를 유지한다

  clearTaskOnLaunch:
  이 속성이 true로 설정되어 있으면 alwaysRetainTaskState 와 정반대로 사용자가 Task를 떠났다가
  다시 돌아올 때마다 항상 Stack은 Root Activity로 정리된다

  finishOnTaskLaunch:
  이 속성은 clearTaskOnLaunch와 유사하지만 전체 Task가 아닌 단일 Activity에서 동작한다
  그리고 그것은 Root Activity를 포함한 어떤 Activity가 사라지는 원인이 될 수도 있다
  true로 설정되어 있을 때, Activity는 현재 Session 동안 Task의 일부만 유지한다
  만일 사용자가 해당 Task를 벗어났다가 다시 돌아오면 더이상 존재하지 않는다

 

 

 

 

출처 : http://blog.softwaregeeks.org/archives/616

리플렉션(Reflection)을 활용한 안드로이드 위젯 매핑(Mapping) 간소화 하기

안드로이드 어플을 만들다보면 안드로이드 위젯(Widget)을 매핑하는 코드를 작성합니다.

위젯이 3~4개 정도면 쉽게 작성할 수 있지만 복잡한 액티비티(Activity)의 경우는 십여개가 넘는 위젯이 존재하고 아래와 같은 코드의 반복이 일어납니다.

자바의 리플렉션을 활용하면 아래의 코드를 간소화 할 수 있습니다.

1
2
3
4
EditText trackEditText = (EditText) findViewById(R.id.title);
EditText artistEditText = (EditText) findViewById(R.id.artist);
EditText albumEditText = (EditText) findViewById(R.id.album);
TextView pathTextView = (TextView) findViewById(R.id.path);



리플렉션을 활용하여 자바 객체의 필드를 모두 가져온 후 activity.getResources().getIdentifier(identifierString, “id”, activity.getPackageName()) 메소드를 활용하여 R.java 에 정의된 id 값을 추출 후 이 값으로 다시 findViewById 메소드로 View를 다시 조회하여 해당 객체를 필드에 값을 주입(Injection)하는 단계를 거칩니다.

이 방법의 전제는 위젯의 id 값과 필드의 이름이 일치한다는 규칙을 정한 것이여서 사용하려면 유의해야 합니다. 하단의 XML의 id값과 필드명을 유심히 살펴보세요~

src/org/softwaregeeks/needletagger/Editor.java

1
2
3
4
5
6
7
8
9
10
11
private EditText trackEditText;
private EditText artistEditText;
private EditText albumEditText;
private TextView pathTextView;
 
@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.editor);
  UIHelper.mappingViews(this);
}

res/layout/editor.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent">
  <EditText android:id="@+id/trackEditText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
  <EditText android:id="@+id/artistEditText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
  <EditText android:id="@+id/albumEditText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
  <TextView android:id="@+id/pathTextView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
</LinearLayout>



실제 리플렉션을 활용하여 매핑하는 메소드는 다음과 같습니다. 그럼 도움이 되셨길 바랍니다~ :D


org/softwaregeeks/framework/android/util/UIHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.softwaregeeks.framework.android.util;
 
import java.lang.reflect.Field;
 
import android.app.Activity;
import android.view.View;
import android.widget.TextView;
 
public class UIHelper
{
  public static void mappingViews(Object object)
  {
    if( !(object instanceof Activity) )
      return;
 
    Activity activity = (Activity)object;
    Field[] fields = activity.getClass().getDeclaredFields();
    for (Field field : fields)
    {
      String identifierString = field.getName();
      int identifier = activity.getResources().getIdentifier(identifierString, "id", activity.getPackageName());
      if( identifier == 0 )
        continue;
 
      View findedView = activity.findViewById(identifier);
      if( findedView == null )
        continue;
 
      if( findedView.getClass() == field.getType() ) {
        try
        {
          field.setAccessible(true);
              field.set(object, findedView);
        }
        catch (IllegalArgumentException e)
        {
          e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
          e.printStackTrace();
        }
      }
    }
  }
}




Android 에서는 thread 간에 통신을 하기 위해서 handler 와 looper 를 제공하고 있다.

child thread 에서 어떤 작업 결과를 main thread 에 알려주는 방법은 handler 를 이용해서 message 를 보내는 것이다.

main thread 는 message queue 와 연결되어 있기 때문이다.

child thread 에서도 main thread 와 같이 handler 를 이용해 message 를 받고자 한다면, looper 를 써야 한다.

looper 가 없으면 이런 실행 에러가 난다.
("Can't create handler inside thread.....)

실행 중인 child thread 로 뭔가 message 를 보내고 싶으면 child thread 에 handler 와 looper 를 이용해서 구현해야 한다..

아래 원문이 이런 내용인 것 같다...

원문 참조...



Android provides Handler and Looper for threads to communication with each other

For example, a child thread is launched to create an image from the web. After it is done, it notifies the main thread (or the UI thread) by sending a message using the handler that’s bound to the main thread’s message queue. 

The data produced by the child thread can also be included in the message. I often use this pattern if I want the main thread to update the UI with the data produced by the child thread (you probably already know why if you have been playing with threads in Android).

When a Handler is created, it’s bound to the message queue of the thread that created it. 

If you create it in the main thread, you don’t need any extra code to loop the message queue for the main thread since it’s already been started when you run your application. However, if you are creating a Handler in a child thread, you need to initialize the thread to listen to its message queue before creating the Handler.


Whenever the Handler receives a message, it would run the handleMessage(…). You can do some expensive operations in there. For example, you need to constantly send some data to the server. It probably would be more efficient if you have a thread listening for the messages to do the job instead of creating and running a new thread each time you need to do so.

If you are done with the looper, don’t forget to stop it by using its quit() method. For example:

mChildHandler.getLooper().quit();

Here is an example of creating a two-way communication between the main thread and a child thread:

example)


public class ThreadMessaging extends Activity {


    private static final String TAG = "ThreadMessaging";

    private TextView mTextView;

    private Handler mMainHandler, mChildHandler;

    private Handler mh;


    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        mTextView = (TextView)findViewById(R.id.text);


        /*

         * Start the child thread.

         */

        new ChildThread().start();


        /*

         * Create the main handler on the main thread so it is bound to the main

         * thread's message queue.

         */

        mMainHandler = new Handler() {


            public void handleMessage(Message msg) {


                Log.i(TAG, "Got an incoming message from the child thread - "  + (String)msg.obj);


                /*

                 * Handle the message coming from the child thread.

                 */

                mTextView.setText(mTextView.getText() + (String)msg.obj + "\n");

            }

        };


        Log.i(TAG, "Main handler is bound to - " + mMainHandler.getLooper().getThread().getName());


        Button button = (Button)findViewById(R.id.button);

        button.setOnClickListener(new OnClickListener() {


            public void onClick(View v) {


                /*

                 * Send a message to the child thread.

                 */

                Message msg = mChildHandler.obtainMessage();

                msg.obj = mMainHandler.getLooper().getThread().getName() + " says Hello";

                mChildHandler.sendMessage(msg);

                Log.i(TAG, "Send a message to the child thread - " + (String)msg.obj);

            }

        });

    }


    @Override

    protected void onDestroy() {


        Log.i(TAG, "Stop looping the child thread's message queue");


        /*

         * Remember to stop the looper

         */

        mChildHandler.getLooper().quit();


        super.onDestroy();

    }


    class ChildThread extends Thread {


        private static final String INNER_TAG = "ChildThread";


        public void run() {


            this.setName("child");


            /*

             * You have to prepare the looper before creating the handler.

             */

            Looper.prepare();


            /*

             * Create the child handler on the child thread so it is bound to the

             * child thread's message queue.

             */

            mChildHandler = new Handler() {


                public void handleMessage(Message msg) {


                    Log.i(INNER_TAG, "Got an incoming message from the main thread - " + (String)msg.obj);


                    /*

                     * Do some expensive operation there. For example, you need

                     * to constantly send some data to the server.

                     */

                    try {


                        /*

                         * Mocking an expensive operation. It takes 100 ms to

                         * complete.

                         */

                        sleep(100);


                        /*

                         * Send the processing result back to the main thread.

                         */

                        Message toMain = mMainHandler.obtainMessage();

                        toMain.obj = "This is " + this.getLooper().getThread().getName() +

                            ".  Did you send me \"" + (String)msg.obj + "\"?";

                        mMainHandler.sendMessage(toMain);

                        Log.i(INNER_TAG, "Send a message to the main thread - " + (String)toMain.obj);


                    } catch (InterruptedException e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }


                }

            };


            Log.i(INNER_TAG, "Child handler is bound to - " + mChildHandler.getLooper().getThread().getName());


            /*

             * Start looping the message queue of this thread.

             */

            Looper.loop();

        }

    }

}

출처 : http://www.gaanza.com/blog/android-autocompletetextview-mysql/
 

Well i have been playing around a lot with android client connectivity with remote MySQL database. Lately i wanted a UI/View, something input textfield, i wanted to show completion suggestions automatically in a drop down menu while the user is typing, suggestions would be coming from MySQL database. Basically whenever i type, i am calling php sending the input and php will query database and return the values(in my case just names) that have “input” as substring (like if i type ‘pa’ then it would return names that have ‘pa’ as substring). I am sending results to android client via encoded json. In android side i am decoding json and filling my ArrayAdapter. Lets see the points that are being done:

1) PHP

<?php
mysql_connect(“localhost”,“root”,“”);
mysql_select_db(“dalalstreet”);
 
$st = $_REQUEST[‘st’];

$qmysql_query(“SELECT * FROM world WHERE company LIKE ‘%”.$st.“%’”);

while($e = mysql_fetch_assoc($q))
        $output[]=$e;
 
print(json_encode($output));
 
mysql_close();
?>

I somehow looking at google i wrote this code. This php gets the input text from my android and it runs query at database for records whose company names has “input text” as a substring. You can see the query statement above. In the end it encodes the result in json.

2) Main.xml (my layout)
Here is the xml layout file for my ui.

<?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”
    >

<TextView  
    android:layout_width=“wrap_content” 
    android:layout_height=“wrap_content” 
    android:text=“@string/country_label”
    />

<CustomAutoCompleteView
        android:id=“@+id/autoCompleteCountry”
        android:layout_width=“fill_parent”
        android:layout_height=“wrap_content” />

</LinearLayout>

3) Main Activity class
Here is the main class that extends Activity. Here is the code of calling php module and parsing json. I have explained this all in my previous post so you could go through it. i’l explain just AutoCompleteTextView part here.

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.ArrayAdapter;

public class AutoComptest extends Activity {
    
        private CustomAutoCompleteView autoComplete;        
         
        private ArrayAdapter<String> autoCompleteAdapter;
        
        /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        autoCompleteAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line)
        autoCompleteAdapter.setNotifyOnChange(true)// This is so I don’t have to manually sync whenever changed  
        autoComplete = (CustomAutoCompleteView)  findViewById(R.id.autoCompleteCountry);
        autoComplete.setHint(“Country”);
        autoComplete.setThreshold(3);
        autoComplete.setAdapter(autoCompleteAdapter);
        
        autoComplete.addTextChangedListener(textChecker);
        
    }
    
    final TextWatcher textChecker = new TextWatcher() {
        public void afterTextChanged(Editable s) {}
 
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
 
        public void onTextChanged(CharSequence s, int start, int before, int count)
        {
                   
                autoCompleteAdapter.clear();
                
                callPHP();
                               
        }     
    };
    
    private void callPHP(){
        String result = “”;         
        InputStream is=null;        
        try{
                ArrayList<NameValuePair> nameValuePairs = newArrayList<NameValuePair>();
                nameValuePairs.add(newBasicNameValuePair(“st”,autoComplete.getText().toString()));
                HttpClient httpclient = new DefaultHttpClient();
                HttpPost httppost = newHttpPost(“http://10.0.2.2/gaanza_android/android_database.php”);
                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
                HttpResponse response = httpclient.execute(httppost);
                HttpEntity entity = response.getEntity();
                is = entity.getContent();
        }catch(Exception e){
                Log.e(“log_tag”“Error in http connection “+e.toString());
        }
        
        try{                        
                BufferedReader reader = new BufferedReader(newInputStreamReader(is,“iso-8859-1″),8);
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {                                    
                            sb.append(line + \n);
                }
                is.close();
         
                result=sb.toString();           
        }catch(Exception e){
                Log.e(“log_tag”“Error converting result “+e.toString());
        }
        
        //parse json data
        try{
                JSONArray jArray = new JSONArray(result);
                for(int i=0;i<jArray.length();i++){
                        JSONObject json_data = jArray.getJSONObject(i);                                
                      
                        autoCompleteAdapter.add(json_data.getString(“country”));
                }
        }
        catch(JSONException e){
                Log.e(“log_tag”“Error parsing data “+e.toString());
        }
    }
}

Note: remember “http://10.0.2.2/” in my last post. I had to use 10.0.2.2 instead of localhost because emulator consider itself as localhost and my xammp local server is not in emulator localhost.

Ok so i want to detect an event when a text is typed/deleted from my autocompletetextview, basically i want a listener which detects a change in text of my view. So what should we do ? Well in my case i searched the android developer documentation :D, there i found something called TextWatcher. What i am doing is attach a TextWatcher object to editable like my AutoCompleteTextView so whenever any text changes in my editable the functions/methods of TextWatcher will be called and in these methods i had to write my logic codes which is what i want to achieve if text changes in autocompletetextview ?. There are three abstract methods in TextWatcher class so you have to override them, well anyway thats what we wanted. Methods are:

afterTextChanged(Editable s)
beforeTextChanged(CharSequence c, int start, int count, int after)
onTextChanged(CharSequence c, int start, int before, int count)

Best way to understand them is going through the document here. Anyway i had to write my logic code on beforeTextChanged() method so that it performs action whenever there is a change in text of autocompletetextview.

In above code sample i am calling PHP whenever i am typing something. So i am calling callPHP function at onTextChanged() method. So whenever i type something it will call PHP giving text to the function in PHP and PHP will query MySql database for results and PHP will send results to me in json. This is it. So i decode json and fill the results in ArrayAdapter after clearing it, whenever ArrayAdapter changes it notify the changes being made and AutoCompleteView will show the new drop down suggestions.

There was still one very minor problem and i solved it with my another very stupid solution :P So problem was that whenever the results from database gets filled in ArrayAdapter and AutoCompleteTextView shows drop down suggestions, it showed me filtered suggestions like suppose i type “can” and i get results(which has “can” as substring) from database, so for “can” ArrayAdapter contents get filtered and i get suggestion starting with text “can” which i didn’t wanted because i already has got filtered results from database and i wanted to show all results that i am receiving from database. So for this i had to write my custom AutoCompleteTextView. It isn’t hard at all, i just had to extend AutoCompleteTextView and override the methods.

4) CustomAutoCompleteView which extends AutoCompletetextView

public class CustomAutoCompleteView extends AutoCompleteTextView {
        
        public CustomAutoCompleteView(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
        }

        public CustomAutoCompleteView(Context context, AttributeSet attrs) {
                super(context, attrs);
                // TODO Auto-generated constructor stub
        }

        public CustomAutoCompleteView(Context context, AttributeSet attrs,
                        int defStyle) {
                super(context, attrs, defStyle);
                // TODO Auto-generated constructor stub
        }
        
        @Override
        protected void performFiltering(final CharSequence text, final int keyCode) {
                String filterText = “”;
                super.performFiltering(filterText, keyCode);
        }
    /**
    * After a selection, capture the new value and append to the existing
    * text
    */

    @Override
    protected void replaceText(final CharSequence text) {
        super.replaceText(text);
    }

}

How to remove the filter on an ArrayAdapter used in an AutoCompleteTextView? I had to override performFiltering() method. In this method i am telling AutoCompletetextView to not filter any suggestions, just show all suggestions. I searched on google for this topic but i didn’t find any good solution, maybe i didn’t searched it properly. Whatever, i just wrote a stupid trick to remove any filter, actually instead of technically removing filter i am adding a filter which does a job to show all suggestions :D . I defined a filter which filters all suggestion based on “” yeah a blank string :D . But this works like a charm. Just try it.

So now your are getting results suggestion from database dynamically to AutoCompleteTextView.

But But But But But ok enough, In above code way of fetching data from database to AutoCompletetextView just sucks. Never never ever call a http PHP call in UI Thread. I did a lot of testing with this code. When i was getting small number of results from Database(live database not local) it was working fine but when i was getting large number of results say more than 150 and it was taking a bit of time which caused my UI to be non-responsive and thus famous FORCE CLOSE occured. So never call it from UI thread because u don’t know how much time it will take to complete whole http connection. So always call it in separate thread that i will discuss in Part II of this subject. yes i’l be posting new better version of same problem.

+ Recent posts