U E D R , A S I H C RSS

Android/Wallpaper Changer


1. 설명


  • 2012년 4월 중순 김준석이 영어공부하기 싫어서 만드는 어플
  • 안드로이드 배경화면이 한개인게 너무 싫어서 여러개 선택후 바꾸게 하는 어플을 만들고 싶어서 만들게됨.
    • 근데 난 배경화면 5개인데 -ㅅ-ㅋ - 권순의

2. 제작 과정

  1. 어플에 필요한 배울것 체크
    • Android의 기본 어플로 장착되어있는 Gallery 어플로 Intent넘긴후 리스트 다시 받아오기.
    • Android의 Wallpaper 바꾸는 API찾아보기
    • 특정시간되면 작동되게하는 알람기능 써보기
  2. 설계 후 통합 제작
  3. 디버그를 하겠지

3. 어플에 필요한 배울것 체크

3.1. Android의 기본 어플로 장착되어있는 Gallery 어플로 Intent넘긴후 리스트 다시 받아오기



   package june.my.testdroid;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.MediaStore.Images.Media;

public class TestdroidActivity extends Activity {

	private static final int SELECT_PICTURE = 1;

	private String selectedImagePath;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        openPhotoLibrary();
               
    }

	private void openPhotoLibrary() {
		//Intent i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
		Intent i = new Intent();
		
		i.setType("image/*");
		//i.setType(MediaStore.Images.Media.CONTENT_TYPE);
		i.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(i, "Select Picture"), SELECT_PICTURE);
	}
	
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
	    if (resultCode == RESULT_OK) {
	        if (requestCode == SELECT_PICTURE) {
	            Uri selectedImageUri = data.getData();
	            selectedImagePath = getPath(selectedImageUri);
	        }
	    }
	}

	public String getPath(Uri uri) {
	    String[] projection = { MediaStore.Images.Media.DATA };
	    Cursor cursor = managedQuery(uri, projection, null, null, null);
	    int column_index = cursor
	            .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
	    cursor.moveToFirst();
	    return cursor.getString(column_index);
	}
    
}



3.2. Android의 Wallpaper 바꾸는 API찾아보기


  • Wallpaper바꾸는데 Permission 필요하더라 (Androidmenifest.xml파일 수정 요함)
  • WallpaperManager 클래스를 통해 할수 있더라
  • BitmapFactory를 통해 이미지 바꿀수 있더라
  • manager.setBitmap(Bitmap.createScaledBitmap(b, d.getWidth()*4, d.getHeight(), true)); 왜 * 4냐면 내 폰에 배경화면이 4칸이라 답하겠더라


package june.mywallpaper.com;

import java.io.IOException;

import android.app.Activity;
import android.app.WallpaperManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class MywallpaperActivity extends Activity {
    private static final Button Button = null;

	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ImageView img1 = (ImageView)findViewById(R.id.imageView1);
        img1.setImageResource(R.raw.wall1);
        img1.setVisibility(View.VISIBLE);
        
        
        Button btn1 = (Button)findViewById(R.id.button1);
        btn1.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	Bitmap b = BitmapFactory.decodeStream(getResources().openRawResource(R.raw.wall1));
            	WallpaperManager manager = WallpaperManager.getInstance(MywallpaperActivity.this);
            	final Display d = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
            	try{
               		manager.setBitmap(Bitmap.createScaledBitmap(b, d.getWidth()*4, d.getHeight(), true));
            		Toast.makeText(getApplicationContext(), "배경화면 지정 성공", 1).show();
            	}catch(IOException e){
            		e.printStackTrace();
            		Toast.makeText(getApplicationContext(), "배경화면 지정 실패", 1).show();
            	}
            	
            }
        });
    }
}

3.3. 특정시간되면 작동되게하는 알람기능 써보기


  • 안드로이드의 서비스를 이용해서 구현.
  • Service는 안드로이드 Activity와 별도로 구현해야 하며 2.3.3버전 이후 단독 실행이 불가능하다고 하다.
  • 또한 Service는 AndroidManifest.xml파일에 당연히 등록을 해야한다. (Application탭 -> Service등록)

  • 액티비티 클래스

package june.myservice.com;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MyserviceActivity extends Activity {
	 
    ComponentName mService;    // 시작 서비스의 이름
     TextView mTextView;              // 서비스 상태 표시
 
    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         
        mTextView = (TextView)findViewById(R.id.text_view);
         
        Button start = (Button)findViewById(R.id.start_button);
         start.setOnClickListener(new View.OnClickListener(){
             public void onClick(View v) {
                 startHelloService();
             }});
         
        Button stop = (Button)findViewById(R.id.stop_button);
         stop.setOnClickListener(new View.OnClickListener(){
             public void onClick(View v) {
                 stopHelloService();
             }});
     }
 



    // 서비스 시작 요청
     private void startHelloService() {
         mService = startService(new Intent(this, MyService.class));
         mTextView.append(mService.toShortString()+" started.\n");
     }
 
 
 
    // 실행한 서비스 중지 요청
 
    private void stopHelloService() {
         if (mService == null) {
             mTextView.append("No requested service.\n");
             return;
         }
         
        Intent i = new Intent();
         i.setComponent(mService);
         if (stopService(i))
             mTextView.append(mService.toShortString()+" is stopped.\n");
         else
             mTextView.append(mService.toShortString()+" is alrady stopped.\n");
     }
 }
 


  • 서비스 클래스



  package june.myservice.com;

import java.io.IOException;

import android.app.Service;
import android.app.WallpaperManager;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;

public class MyService extends Service implements Runnable{

	// 시작 ID
    private int mStartId;
    // 서비스에 대한 스레드에 연결된 Handler. 타이머 이용한 반복 처리시 사용.
    private Handler mHandler;
    // 서비스 동작여부 flag
    private boolean mRunning;
    // 타이머 설정 (2초)
    private static final int TIMER_PERIOD = 2 * 1000; 
    private static final int COUNT = 10;
    private int mCounter;


   // 서비스를 생성할 때 호출
    public void onCreate() {
        Log.e("MyService", "Service Created.");
        super.onCreate();
        mHandler = new Handler();
        mRunning = false;
    }


   // 서비스 시작할 때 호출. background에서의 처리가 시작됨.
    // startId : 서비스 시작요구 id. stopSelf에서 종료할 때 사용. 

    //onStart는 여러번 호출될 수 있기 때문에 식별자로 사용.

   public void onStart(Intent intent, int startId) {
        Log.e("MyService", "Service startId = " + startId);
        super.onStart(intent, startId);
        mStartId = startId;
        mCounter = COUNT;

       // 동작중이 아니면 run 메소드를 일정 시간 후에 시작
        if (!mRunning) {
              // this : 서비스 처리의 본체인 run 메소드. Runnable 인터페이스를 구현 필요.
              // postDelayed : 일정시간마다 메소드 호출
              mHandler.postDelayed(this, TIMER_PERIOD);
              mRunning = true;
        }
    }



   // 서비스의 종료시 호출
    public void onDestroy() {
        // onDestroy가 호출되어 서비스가 종료되어도 
       // postDelayed는 바로 정지되지 않고 다음 번 run 메소드를 호출.
        mRunning = false;
        super.onDestroy();
    }




   // 서비스 처리
    public void run() {
        if (!mRunning) {
            // 서비스 종료 요청이 들어온 경우 그냥 종료
            Log.e("MyService", "run after destory");
            return;
        } else if (--mCounter <= 0) {
            // 지정한 횟수 실행하면 스스로 종료
            Log.e("MyService", "stop Service id = "+mStartId);
            stopSelf(mStartId);
        } else {        	
            // 다음 작업을 다시 요구
            Log.e("MyService", "mCounter : " + mCounter);
            mHandler.postDelayed(this, TIMER_PERIOD);
        }
    }

   // 원격 메소드 호출을 위해 사용
    // 메서드 호출을 제공하지 않으면 null을 반환
    public IBinder onBind(Intent intent) {
        return null;
    }


}

3.4. 배워보기에서 하지 않은 예상 고려점


  • 그림파일을 불러왔을경우 BitmapFactory에서의 이미지 사이즈 변경은 이미지를 늘리고 줄이기 때문에 기존 안드로이드 어플 배경화면에서 자르기로 들어간것과는 다르다.
  • 서비스를 등록시키고 Reciever를 통해 안드로이드 시작후에 자동 작동하게 해놓는것.
  • Intent 사이간에 데이터를 주고받는 방법 설정해야함.
  • Picture Gallery 불러오는 방법 및 여러개 파일 받아오는 방법

4. 프로그램 시나리오


  • 프로그램을 켠다.
  • 창이 뜬다.
    1. 현재 설정되어있는 파일 리스트 보기
      1. 파일 리스트 화면
      2. 파일 리스트 추가/삭제
    2. 시간 주기 설정
      1. 몇초 주기로 바뀌는지. 10초 20초 30초 60초. 120초 180초.
      2. 현재 서비스 해지.

5. 프로그램 작업현황 및 로그 기록소

5.1. 선행학습후 작업현황 로그


날짜 로그
2012/4/10 Initial Import
4/10 Test버전 WallpaperChanger작동
4/15 리스트 액티비티를 기본으로 만들고 기본 프로그램 구조를 잡음
4/17 Layout추가 및 기본 Activity및 Service 파일 만듬
4/18 WallPapermanagerActivity실행 및 파일 경로 받아오기 완성
4/18 MywallpaperActivity(MainActivity)에서 WallPapermanagerActivity로 넘겨주는 배경화면 리스트를 Prototype형으로 만들어놓음. WallPapermanagerActivity에서 MywallpaperActivity(MainActivity)로부터 리스트를 받아 Set하고 버튼 입력에 따라 Set과 add를 하게 해놓음. Delete의 추가 구현이 필요함.
4/19 MywallpaperActivity에서 TimeCycleActivity로 현재 값과 함께 넘어가는 기능 구현. TimeCycleActivity에 enum리스트로 현재 setting된 값을 single_choice list로 선택되고 setting버튼 cancle버튼을 통해 다시 돌아오는것 구현.
4/22 커니의 안드로이드에 있는 예제는 Adapter + SQL을 써서 따로 분리된 예제로 실습후 커니의 안드로이드 예제 실습
4/22 DB연동 성공 Activity간 통신 확인 Service 실행 성공 개선점 : 1. DB중복현상 2. 삭제가 안됨. 3. 크기 조절.
4/25 PathRepositoryArrayList로 Parcelable객체로 만드는것을 성공 순서도상의 DB접근을 제한을 두어야할것 같음. 문제점 : WallpaperManagerActivity에서 Add시키고 setting하는데 객체가 날아감. 우짬.. 아! 우선 만들어놓고 setting할때만 DB에 저장시키는 방식으로 해야겠다.그리고 0으로 index가 없는것과 있는것을 표기해서 update혹은 새로 만들기를 실행하도록 하고.
4/26 전체 Activity간의 Parcel데이타를 넘길수 있게 코드를 리펙토링(Refactoring)함. DB의 연결문제를 삭제 삽입 목록에 flag를 달아 해결. 파일을 선택해서 Path와 Name을 보여주는 Activity의 Thumnail을 만들어 보여주게함. Refactoring후 Service 잘작동 확인.
4/28 WallPaperAndroidService에서 Bitmap Loading방식 바꿈. 먼저 Loading을 해서 준비해놓고 순서가 오면 화면이 바뀌는 형식으로 바꿔놓음.시간 설정 저장 DB adapter생성 및 DB새로 만들어서 저장함.사용자의 편의를 위한 TextView설명 추가
4/30 UI개설 및 Splash Activity 작성. Loading 까지 기다리는 기능은 없음. 개선사항 : Image Crop을 지원해야함.
5/1 Image Gallery에서 불러와서 크기조절 해주는 Crop 작성. File 입출력을 지원하면서 Side Effect로 DB 기록과 실제 File의 존재 유무를 판단해야하는 경우가 생김

5.2. Reference


6. 잡지식


  • enum의 iteration 가능


public class fdfdf {

	public static void main(String[] args){
		STATE s[] = STATE.values();
		
		for(STATE s2 : s){
			System.out.println(s2.getState());
		}
		
	}
	
	private enum STATE{
		
		IMAGE_PICK_OK(0),
		FAIL(-1);
	
		private int state;
		
		private STATE(int arg){
			this.state = arg;
		}
		
		int getState(){
			return state;
		}
	}
}


  • 이승한님의 Java상의 enum은 문자열 비교로 인해 임베이디드와 반복코드에서는 성능을 저해시키는 요인이 될수 있다.
    • 사실 확인


성능을 위한 설계
안드로이드 애플리케이션의 속도는 빨라야만 합니다. 음, 효율적이어야 한다고 말하는 쪽이 더 정확할 듯싶네요. 다시 말해, 제한된 컴퓨팅 파워와 데이터 저장소, 작은 화면, 갑갑한 배터리 수명 같은 모바일 장치 환경에서 가능한 한 효율적으로 실행되어야 한다는 것입니다.

애플리케이션을 개발할 때에는 이것을 명심하세요. 듀얼코어 개발 컴퓨터에서 실행하는 에뮬레이터에서는 충분히 잘 작동할지도 모르지만, 모바일 기기에서 실행할 때엔 그리 잘 되지 않을 것입니다. — 최고 성능의 모바일 기기라도 일반적인 데스크탑 시스템의 성능을 따라잡을 수는 없습니다. 그런 이유로, 다양한 모바일 기기들에게 최상의 성능을 보장하기 위해 여러분은 효율적인 코드를 작성하도록 열심히 노력하셔야 합니다.

일반적으로, 빠르거나 효율적인 코드라는 것은 메모리 할당을 최소화 하고, 꽉 짜인 코드를 작성하고, 특정 프로그래밍 언어나 잠재적으로 성능상 문제가 될만한 프로그래밍 어법들을 피하는 것을 말합니다. 객체지향 용어로 말하자면, 이러한 일이 가장 빈번히 일어나는 곳은 메소드 레벨이며, 이와 비슷하게 실제 코드 라인들과 반복문 등에서 발생합니다 .

이 문서는 다음 주제들을 다룹니다:

소개
객체 생성을 피하라
네이티브 메소드를 사용하라
인터페이스보다 가상 연결을 선호하라
가상 연결보다 정적 연결을 선호하라
내부에서 Getter/Setter 사용을 피하라
필드 참조들을 캐시하라
상수를 Final로 선언하라
주의 깊게 향상된 반복문(Enhanced For Loop)을 사용하라
열거형(Enum)을 피하라
내부 클래스와 함께 패키지 범위를 사용하라
Float를 피하라
성능 예시 숫자 몇 개
맺는 말
소개

자원 제한적 시스템에는 두 가지 기본 규칙이 있습니다:

필요 없는 일은 하지 말 것
메모리 할당을 피할 수 있다면 그렇게 할 것
아래의 모든 팁들은 이 두 가지 기본 주의를 따르고 있습니다.

누군가는 이 페이지상의 많은 조언이 "섣부른 최적화"나 마찬가지라고 비판할지도 모릅니다. 미시 최적화는 때로는 효율적인 데이터 구조와 알고리즘을 개발하는 것을 더 어렵게 만든다는 것은 사실입니다. 하지만, 핸드셋과 같은 임베디드 기기에서는 때로는 별다른 선택지가 없습니다. 예를 들어, 여러분이 데스크탑에서 개발할 때 생각하는 VM의 성능에 대한 가정을 안드로이드에도 적용한다면, 여러분은 시스템 메모리를 소진해버리는 코드를 꽤나 작성해 버리고 말 것입니다. 이것은 여러분의 애플리케이션이 바닥을 기도록 할 수 있습니다 — 시스템에서 동작하는 다른 프로그램들에게 무엇을 하는지 지켜보세요!

이것이 바로 이 가이드라인이 중요한 이유입니다. 안드로이드의 성공은 여러분의 애플리케이션이 제공하는 사용자 경험(UX)에 달렸고, 사용자 경험이란 것은 여러분의 코드가 빠르고 팔팔하게 반응하는지, 아니면 느리고 무거운지에 달렸습니다. 모든 우리의 애플리케이션들은 같은 장치에서 동작할 것이기 때문에, 어떤 의미로, 우리 모두 함께 이 것들을 지키도록 최선을 다해야 합니다. 이 문서를 운전면허를 딸 때 배워야만 하는 도로교통법이라고 생각하세요: 모든 이가 따르면 문제없이 원활하겠지만, 따르지 않는다면 사고가 날 것처럼 말입니다.

자세한 내용을 다루기 전에, 간단한 주의사항입니다: 아래 설명된 대부분의 이슈들은 VM이 JIT 컴파일러이든 아니든 효과적입니다. 같은 기능을 수행하는 두 메소드가 있고 interpret 방식에서 foo()의 실행속도가 bar()보다 빠르다면, 컴파일 된 버전에서도 아마 foo()가 bar()과 비슷하거나 더 빠른 속도를 보여줄 것입니다. 컴파일러가 여러분을 "구해줄"것이라던가 충분히 빠르게 만들어줄 것이라고 의존하는 건 현명하지 못하다는 것이죠.

객체 생성을 피하라

객체의 생성은 결코 공짜가 아닙니다. 임시 객체들을 위해 쓰레드-당(per-thread) 할당 풀을 사용하는 세대형(generational) GC는 더 낮은 비용으로 할당 할 수 있지만, 메모리를 할당한다는 것은 메모리를 할당하지 않는 것 보다 언제나 더 높은 비용이 듭니다.

만약 사용자 인터페이스 루프에서 객체를 할당한다면, 주기적으로 가비지 컬렉션을 강요하게 될 것이고 사용자 경험에 있어서 조그마한 "딸꾹질(거북함)"을 만들게 될 겁니다.

그러므로, 필요로 하지 않는 객체 생성을 피해야 합니다. 도움이 될 몇 가지 예제들이 있습니다.

입력 데이터 셋에서 문자열을 추출할 때, 복사 생성된 것 대신 원본 데이터의 부분문자열을 받으십시오. 새로운 String 객체가 만들어졌더라도 원본 데이터의 char[]을 공유할 것입니다.
문자열을 반환하는 메소드가 있고 그 결과가 언제나 StringBuffer에 더해지게 되는 경우에 있다면, 짧은 수명의 임시 객체를 생성하는 대신 직접적으로 더해지는 방식으로 식별자와 구현방식을 바꾸세요.
좀 더 급진적인 아이디어는 다차원 배열을 병렬의 단일 일 차원 배열로 잘라내는 것입니다:

int 배열은 Integer 배열보다 더 좋습니다만, 이것으로 또한 int형의 두 병렬 배열이 (int,int) 객체의 배열보다 더 많이 효과적이라는 사실을 추론할 수 있습니다.
만약 (Foo,Bar) 튜플로 저장하는 컨테이너를 구현할 필요가 있다면, 직접 만든 (Foo,Bar) 객체의 단일 배열보다 두 개의 병렬 Foo[] 와 Bar[] 배열이 일반적으로 더욱 더 좋다는 것을 기억하십시오. (물론, 다른 코드들이 접근해야 하는 API를 설계할 때에는 예외가 있습니다; 이 경우 작은 속도 향상을 노리는 것 보다 좋은 API설계가 항상 좋습니다. 그러나 여러분의 내부 코드를 작성할 때에는 가능한 한 효율적인 코드가 되도록 해야 하겠습니다.)
일반적으로, 가능하다면 짧은 수명의 임시 객체 생성을 피하십시오. 더 적은 객체들을 만든다는 것은 사용자 경험에 직접적인 영향을 주는 가비지 컬렉션 줄여줌을 뜻합니다.

네이티브 메소드를 사용하라

문자열을 처리할 때, String.indexOf(), String.lastIndexOf() 와 그 밖의 특별한 메소드를 사용하는 것을 주저하지 마십시오. 이 메소드들은 대체적으로, 자바 루프로 된 것 보다 대략 10-100배 빠른 C/C++ 코드로 구현이 되어있습니다.

이 조언의 반대적 측면은 네이티브 메소드를 호출하는 것이 interpret방식의 메소드 호출보다 더 비용이 높다는 것입니다. 피할 수 있다면, 사소한 계산에는 네이티브 메소드를 사용하지 마십시오.

인터페이스보다 가상 연결을 선호하라

여러분이 HashMap 객체를 하나 가지고 있다고 합시다. 여러분은 HashMap이나 제네릭 Map 으로 선언을 할 수 있습니다.

Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();
어떤것이 더 좋은가요?

전통적인 지혜에서는 Map을 사용해야 한다고 할 것입니다. Map 인터페이스를 구현한 어떤 것으로라도 구현체를 바꿀 수 있기 때문입니다. 전통적인 지혜는 전통적인 프로그래밍에는 맞습니다만, 임베디드 시스템에는 그다지 대단하지 않습니다. 인터페이스 참조를 통해 호출하는 것은 명확한 참조를 통한 가상 메소드 호출보다 2배 더 소요될 수 있습니다.

여러분이 하는 일에 적합하여 HashMap사용을 선택했다면 Map으로 호출하는 것은 거의 가치가 없습니다. 코드를 리팩터링 해 주는 IDE의 가능성을 고려해 보더라도, Map으로 호출하는 것은 큰 가치가 없습니다. 여러분이 코드의 방향을 확신하지 못한다 해도 말입니다. (다시금 이지만, 공용 API는 예외입니다: 작은 성능 고려보다 좋은 API가 언제나 으뜸입니다.)

가상 연결보다 정적 연결을 선호하라

만약 객체의 필드에 접근할 필요가 없다면, 여러분의 메소드를 정적(static)으로 만드세요. 가상 메소드 테이블을 필요로 하지 않기 때문에 그게 더 빠르게 불려집니다. 또한, 메소드 식별자를 보고 메소드 호출이 객체의 상태를 바꿀 수 없다고 말할 수 있으므로, 좋은 관습이 됩니다.

내부에서 Getter/Setter 사용을 피하라

C++와 같은 네이티브 언어에서 필드에 직접적으로 접근하는 것 (예. i = mCount) 보다 getter를 사용하는 것 (i = getCount())은 일반적인 관습입니다. 이 방법은 C++에서는 훌륭한 습관입니다. 왜냐하면 항상 접근을 inline화 할 수 있는 컴파일러를 사용하고 있고, 필드에 접근을 제한하거나 디버그 해야 한다면 언제나 코드를 추가할 수 있기 때문입니다.

안드로이드에서는 나쁜 생각입니다. 가상 메소드 호출은 인스턴스 필드 참조보다 더 비용이 높습니다. 일반적인 객체 지향 프로그래밍 관습에 따르거나, 공용 인터페이스에서 getter, setter을 가지는 것은 이치에 맞습니다. 그러나 클래스 내부에서는 언제나 직접적으로 필드 접근을 해야합니다.

필드 참조들을 캐시하라

객체의 필드에 접근하는 것은 지역 변수에 접근하는 것 보다 더 느립니다. 이렇게 작성하는 것 대신:

for (int i = 0; i < this.mCount; i++)
      dumpItem(this.mItems[i]);
이렇게 하십시오:

  int count = this.mCount;
  Item[] items = this.mItems;
 
  for (int i = 0; i < count; i++)
      dumpItems(items[i]);
(멤버 변수라는 것을 명확히 하기 위해 명시적인 "this"를 사용하고 있습니다.)

유사한 가이드라인은, 결코 "for"문의 두 번째 조건에서 메소드를 호출하지 말라는 것입니다. 예를 들어, 다음 코드는 간단하게 int 값으로 캐쉬 할 수 있는 경우임에도 큰 낭비가 되는 getCount()메소드를 매번 반복 마다 실행하게 됩니다:

for (int i = 0; i < this.getCount(); i++)
    dumpItems(this.getItem(i));
인스턴스 필드를 한번 이상 접근해야 한다면, 지역 변수를 만드는 것 또한 좋은 생각입니다. 예를 들어:

    protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
        if (isHorizontalScrollBarEnabled()) {
            int size = mScrollBar.getSize(false);
            if (size <= 0) {
                size = mScrollBarSize;
            }
            mScrollBar.setBounds(0, height - size, width, height);
            mScrollBar.setParams(
                    computeHorizontalScrollRange(),
                    computeHorizontalScrollOffset(),
                    computeHorizontalScrollExtent(), false);
            mScrollBar.draw(canvas);
        }
    }
mScrollBar 멤버 필드에 네 개의 분리된 참조가 있습니다. 지역 스택 변수로 mScrollBar를 캐싱 함으로써, 네 개의 멤버 필드 참조가 더욱 효율적인 네 개의 스택 변수 참조로 바뀌었습니다.

덧붙여 말하자면, 메소드 인자들은 지역 변수와 같은 성능 특성을 가집니다.

상수를 Final로 선언하라

클래스의 상단에 있는 다음 선언을 고려해 봅시다:

static int intVal = 42;
static String strVal = "Hello, world!";
컴파일러는 클래스가 처음 사용될 때 실행하게 되는 <clinit>라 불리는 '클래스 초기화 메소드'를 생성합니다. 이 메소드가 intVal에 42 값을 저장하고, strVal에는 클래스파일 문자 상수 테이블로부터 참조를 추출하여 저장합니다. 나중에 참조될 때 이 값들은 필드 참조로 접근됩니다.

이를 "final" 키워드로 향상시킬 수 있습니다:

static final int intVal = 42;
static final String strVal = "Hello, world!";
클래스는 더이상 <clinit> 메소드를 필요로 하지 않습니다. 왜냐하면 상수들은 VM에 의해 직접적으로 다루어 지는 '클래스파일 정적 필드 초기자'에 들어가기 때문입니다.intVal의 코드 접근은 직접적으로 정수 값 42를 사용할 것이고, strVal로의 접근은 필드 참조보다 상대적으로 저렴한 "문자열 상수" 명령을 사용하게 될 것입니다.

"final"으로 메소드나 클래스의 선언을 하는 것은 즉각적인 성능 이득을 주지는 못하지만, 특정한 최적화를 가능하게 합니다. 예를 들어, 컴파일러가 서브클래스에 의해 오버라이드될 수 없는 "getter"메소드를 알고 있다면, 메소드 호출을 inline화 할 수 있습니다.

여러분은 또한 지역 변수를 final로 선언할 수 있습니다. 하지만 이것은 결정적인 성능 이득은 없습니다. 지역 변수에는 오직 코드를 명확히 하기 위해서 "final"을 사용합니다 (또는 예를 들어 익명 내부 클래스를 사용해야 한다면 가능).

주의 깊게 향상된 반복문(Enhanced For Loop)을 사용하라

향상된 반복문(때로 "for-each"로 알려진 반복문)은 Iterable 인터페이스를 구현한 컬렉션들을 위해 사용될 수 있습니다. 이러한 객체들로, 반복자는 hasNext() 와 next()을 호출하는 인터페이스를 만들기 위해 할당됩니다. ArrayList의 경우 여러분이 직접 탐색하는 것이 좋을 수 있습니다만, 다른 컬렉션들에서는 향상된 반복문 구문이 명시적인 반복자의 사용과 동등한 성능을 보여줍니다.

그럼에도, 다음 코드로 향상된 반복문의 만족스러운 사용법을 볼 수 있습니다:

public class Foo {
    int mSplat;
    static Foo mArray[] = new Foo[27];

    public static void zero() {
        int sum = 0;
        for (int i = 0; i < mArray.length; i++) {
            sum += mArray[i].mSplat;
        }
    }

    public static void one() {
        int sum = 0;
        Foo[] localArray = mArray;
        int len = localArray.length;

        for (int i = 0; i < len; i++) {
            sum += localArray[i].mSplat;
        }
    }

    public static void two() {
        int sum = 0;
        for (Foo a: mArray) {
            sum += a.mSplat;
        }
    }
}
zero() 는 반복되는 매 주기마다 정적 필드를 두 번 부르고 배열의 길이를 한번 얻습니다.

one() 은 참조를 피하기 위해 지역 변수로 모든 것을 끌어냈습니다.

two() 는 자바 언어의 1.5버전에서 소개된 향상된 반복문 구문을 사용합니다. 컴파일러에 의해 생성된 코드는 배열 참조와 배열의 길이를 지역 변수로 복사해주어, 배열의 모든 원소를 탐색하는데 좋은 선택이 될 수 있습니다. 주 루프 내에 추가적인 지역 읽기/저장이 만들어지고(명백하게 "a"에 저장), one()보다 쪼금 느리고 4 바이트 길어지게 하긴 합니다.

좀 더 명확하게 모든 것을 종합하자면: 향상된 반복문 구문은 배열과 잘 동작하지만, 추가적인 객체 생성이 있게 되는 Iterable 객체와 함께 사용할 때엔 조심해야 합니다.

열거형(Enum)을 피하라

열거형은 매우 편리합니다, 그러나 불운하게도 크기와 속도 측면에서 고통스러울 수 있습니다. 예를들어, 다음의 코드는:

public class Foo {
   public enum Shrubbery { GROUND, CRAWLING, HANGING }
}
900 바이트의 클래스 파일 (Foo$Shrubbery.class) 로 변환됩니다. 처음 사용할 때, 클래스 초기자는 각각의 열거화된 값들을 표기화 하는 객체상의 <init>메소드를 호출합니다. 각 객체는 정적 필드를 가지게 되고 총 셋은 배열("$VALUES"라 불리는 정적 필드)에 저장됩니다. 단지 세 개의 정수를 위해 많은 코드와 데이터를 필요로 하게 됩니다.

다음 코드:

Shrubbery shrub = Shrubbery.GROUND;
는 정적 필드 참조를 야기합니다. "GROUND"가 정적 final int 였더라면, 컴파일러는 알려진 상수로서 다루고, inline화 했을 수도 있습니다.

물론, 반대적 측면에서 열거형으로 더 좋은 API를 만들 수 있고 어떤 경우엔 컴파일-타임 값 검사를 할 수 있습니다. 그래서 통상의 교환조건(trade-off)이 적용됩니다: 반드시 공용 API에만 열거형을 사용하고, 성능문제가 중요할 때에는 사용을 피하십시오.

어떤 환경에서는 ordinal() 메소드를 통해 정수 값 열거를 갖는 것이 도움이 될 수 있습니다. 예를 들어, 다음 코드를:

for (int n = 0; n < list.size(); n++) {
    if (list.items[n].e == MyEnum.VAL_X)
       // do stuff 1
    else if (list.items[n].e == MyEnum.VAL_Y)
       // do stuff 2
}
다음 코드로 대신합니다:

   int valX = MyEnum.VAL_X.ordinal();
   int valY = MyEnum.VAL_Y.ordinal();
   int count = list.size();
   MyItem items = list.items();

   for (int  n = 0; n < count; n++)
   {
        int  valItem = items[n].e.ordinal();

        if (valItem == valX)
          // do stuff 1
        else if (valItem == valY)
          // do stuff 2
   }
때로는, 보장할 수 없습니다만, 이것이 더 빠를 수 있습니다.

내부 클래스와 함께 패키지 범위를 사용하라

다음 클래스 정의를 고려해 봅시다:

public class Foo {
    private int mValue;

    public void run() {
        Inner in = new Inner();
        mValue = 27;
        in.stuff();
    }

    private void doStuff(int value) {
        System.out.println("Value is " + value);
    }

    private class Inner {
        void stuff() {
            Foo.this.doStuff(Foo.this.mValue);
        }
    }
}
여기서 주목해야 할 중요한 것은, 외부 클래스의 private 메소드와 private 인스턴스 필드에 직접 접근하고 있는 내부 클래스(Foo$Inner)를 정의했다는 것입니다. 이것은 적법하고, 코드는 기대했던 대로 "Value is 27"을 출력합니다.

문제는 Foo$Inner는 기술적으로는 (비밀로써) 완전히 분리된, Foo의 private 멤버로 직접적인 접근을 하는 것은 적법하지 못한 클래스라는 것 입니다. 이 차이를 연결짓기 위해, 컴파일러는 두 개의 합성 메소드를 만듭니다:

/*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}
내부 클래스 코드는 외부 클래스에 있는 "mValue" 필드에 접근하거나 "doStuff" 메소드를 부르기 위해 이 정적 메소드를 부릅니다. 이것은 이 코드가 결국은 직접적인 방법 대신 접근자 메소드를 통해 멤버 필드에 접근하고 있다는 것을 뜻합니다. 이전에 우리는 어째서 접근자가 직접적인 필드 접근보다 느린지에 대해 이야기 했었는데, 이 문제로서 "보이지 않는" 성능 타격 측면에서 특정 언어의 어법이 야기하게 되는 문제에 대한 예제가 될 수 있겠습니다.

이 문제는 내부 클래스가 접근하는 필드와 메소드 선언에 private 범위가 아닌 package 범위를 가지도록 함으로써 피할 수 있습니다. 이로써 더욱 빠르게 동작하게 되고 자동 생성되는 메소드에 의한 오버헤드를 제거할 수 있습니다. (불운하게도 이 또한 직접적으로 같은 패키지 내의 다른 클래스들이 필드들에 접근할 수 있다는 것을 뜻하게 되며, 모든 필드들은 private로 해야 한다는 표준적인 OO 관습에 거스르게 됩니다. 다시 한번 더 말하자면, 공용 API를 설계하게 된다면 이 최적화를 사용하는 것을 조심스럽게 고민해야만 할 것입니다.)

Float를 피하라

펜티엄 CPU가 출시되기 전, 게임 제작자들에겐 정수 계산에 최선을 다하는 것이 일반적이었습니다. 펜티엄과 함께 부동소수점 계산 보조 프로세서는 일체형이 되었고, 정수와 부동소수점 연산을 넣음에 따라 순수하게 정수 계산만을 사용하는 것 보다 게임은 더 빠르게 되었습니다. 자유롭게 부동소수점을 사용하는 것은 데스크탑 시스템에서는 일반적입니다.

불운하게도, 임베디드 프로세서에게는 빈번하게 하드웨어적으로 부동소수점 계산이 제공되지 않고 있어, "float" 와 "double"의 모든 계산이 소프트웨어적으로 처리됩니다. 어떤 기초적인 부동소수점 계산은 완료까지 대략 일 밀리 초 정도 걸릴 수 있습니다.

또한, 정수에서도 어떤 칩들은 하드웨어 곱셈을 가지고 있지만 하드웨어 나눗셈이 없기도 합니다. 이러한 경우, 정수 나눗셈과 나머지 연산은 소프트웨어적으로 처리됩니다 — 만약 해시 테이블을 설계하거나 많은 계산이 필요하다면 생각해 보아야 할 것입니다.

성능 예시 숫자 몇 개

우리의 몇 가지 아이디어를 설명하기 위해, 약간의 기초적인 행동들에 대해 대략적인 실행시간을 나열한 테이블을 만들었습니다. 이 값들은 절대적인 숫자가 아니라는 것을 주목해 주십시오: CPU시간과 실제 구동 시간의 조합이고, 시스템의 성능 향상에 따라 변화할 수 있습니다. 그러나 이 값들 사이에 관계를 적용해 보는 것은 주목할 만한 가치가 있습니다 — 예를 들어, 멤버 변수를 더하는 것은 지역 변수를 더하는 것보다 대략 네 배가 걸립니다.

행동	시간
지역 변수 더하기	1
멤버 변수 더하기	4
String.length() 호출	5
빈 정적 네이티브 메소드 호출	5
빈 정적 메소드 호출	12
빈 가상 메소드 호출	12.5
빈 인터페이스 메소드 호출	15
HashMap의 Iterator:next() 호출	165
HashMap의 put() 호출	600
XML로부터 1 View 객체화(Inflate)	22,000
1 TextView를 담은 1 LinearLayout 객체화(Inflate)	25,000
6개의 View 객체를 담은 1 LinearLayout 객체화(Inflate)	100,000
6개의 TextView 객체를 담은 1 LinearLayout 객체화(Inflate)	135,000
빈 activity 시작	3,000,000
맺음 말

임베디드 시스템을 위해 좋고 효율적인 코드를 작성하는 최선의 방법은 여러분이 작성하는 코드가 실제로 무엇을 하는지 이해하는 것 입니다. 여러분이 정말로 반복자를 할당하기를 원한다면, List에 향상된 반복문을 반드시 사용하십시오; 부주의한 부작용이 아닌 신중한 선택을 통해서 말입니다.

유비무환입니다! 무엇을 하는지 알고 하세요! 좋아하는 좌우명을 여기에 넣으세요, 그러나 언제나 여러분의 코드가 무엇을 하는지 주의 깊게 생각하고, 속도를 높이는 방법을 찾도록 경계하십시오.

7. 중얼거림

  1. fetch맞는데.. 불러오는거
  2. 리펙토링은 새로 만드는것보다 쉽구나.
  3. 근데 안드로이드는 복잡하구나.. 아직 멀었네
  4. 어플이 좋아지면 좋아질수록 내 머리는 터지는군.
  5. 앗 아이디어가 떠올랐어 S-Pen으로 만든 스케치 어플. 왼쪽오른쪽은 손가락으로할때 이동. OK
  6. 아악................................ DB동기화 왜이래

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2012-05-02 16:42:16
Processing time 0.5922 sec