[[TableOfContents]] = 설명 = * 2012ë…„ 4ì›” 중순 [김준ì„]ì´ ì˜ì–´ê³µë¶€í•˜ê¸° 싫어서 만드는 어플 * 안드로ì´ë“œ ë°°ê²½í™”ë©´ì´ í•œê°œì¸ê²Œ 너무 싫어서 여러개 ì„ íƒí›„ 바꾸게 하는 ì–´í”Œì„ ë§Œë“¤ê³ ì‹¶ì–´ì„œ 만들게ë¨. * ê·¼ë° ë‚œ 배경화면 5ê°œì¸ë° -ã……-ã…‹ - [권순ì˜] = ì œìž‘ ê³¼ì • = 1. ì–´í”Œì— í•„ìš”í•œ 배울것 ì²´í¬ * Androidì˜ ê¸°ë³¸ 어플로 장착ë˜ì–´ìžˆëŠ” Gallery 어플로 Intent넘긴후 리스트 다시 받아오기. * Androidì˜ Wallpaper 바꾸는 API찾아보기 * íŠ¹ì •ì‹œê°„ë˜ë©´ ìž‘ë™ë˜ê²Œí•˜ëŠ” 알람기능 ì¨ë³´ê¸° 1. 설계 후 통합 ì œìž‘ 1. 디버그를 í•˜ê² ì§€ = ì–´í”Œì— í•„ìš”í•œ 배울것 ì²´í¬ = == Androidì˜ ê¸°ë³¸ 어플로 장착ë˜ì–´ìžˆëŠ” Gallery 어플로 Intent넘긴후 리스트 다시 받아오기 == * Android Provider 믿ì„ê²ƒì´ ëª»ë˜ë”ë¼. * Stack Overflowì—서 참조 * http://stackoverflow.com/questions/2169649/open-an-image-in-androids-built-in-gallery-app-programmatically {{{ 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); } } }}} == 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(); } } }); } } }}} == íŠ¹ì •ì‹œê°„ë˜ë©´ ìž‘ë™ë˜ê²Œí•˜ëŠ” 알람기능 ì¨ë³´ê¸° == * 안드로ì´ë“œì˜ 서비스를 ì´ìš©í•´ì„œ 구현. * 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; } } }}} == 배워보기ì—서 하지 ì•Šì€ ì˜ˆìƒ ê³ ë ¤ì == * 그림파ì¼ì„ 불러왔ì„경우 BitmapFactoryì—ì„œì˜ ì´ë¯¸ì§€ 사ì´ì¦ˆ ë³€ê²½ì€ ì´ë¯¸ì§€ë¥¼ ëŠ˜ë¦¬ê³ ì¤„ì´ê¸° ë•Œë¬¸ì— ê¸°ì¡´ 안드로ì´ë“œ 어플 배경화면ì—서 ìžë¥´ê¸°ë¡œ 들어간것과는 다르다. * 서비스를 등ë¡ì‹œí‚¤ê³ Reciever를 통해 안드로ì´ë“œ ì‹œìž‘í›„ì— ìžë™ ìž‘ë™í•˜ê²Œ 해놓는것. * Intent 사ì´ê°„ì— ë°ì´í„°ë¥¼ ì£¼ê³ ë°›ëŠ” 방법 ì„¤ì •í•´ì•¼í•¨. * Picture Gallery 불러오는 방법 ë° ì—¬ëŸ¬ê°œ íŒŒì¼ ë°›ì•„ì˜¤ëŠ” 방법 = 프로그램 시나리오 = * í”„ë¡œê·¸ëž¨ì„ ì¼ ë‹¤. * ì°½ì´ ëœ¬ë‹¤. 1. 현재 ì„¤ì •ë˜ì–´ìžˆëŠ” íŒŒì¼ ë¦¬ìŠ¤íŠ¸ 보기 1. íŒŒì¼ ë¦¬ìŠ¤íŠ¸ 화면 1. íŒŒì¼ ë¦¬ìŠ¤íŠ¸ 추가/ì‚ì œ 1. 시간 주기 ì„¤ì • 1. 몇초 주기로 바뀌는지. 10ì´ˆ 20ì´ˆ 30ì´ˆ 60ì´ˆ. 120ì´ˆ 180ì´ˆ. 1. 현재 서비스 í•´ì§€. = 프로그램 작업현황 ë° ë¡œê·¸ 기ë¡ì†Œ = http://nforge.zeropage.org/projects/wallchanger == ì„ í–‰í•™ìŠµí›„ 작업현황 로그 == || ë‚ ì§œ || 로그 || || 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 || {{{ PathRepository를 ArrayList로 Parcelableê°ì²´ë¡œ ë§Œë“œëŠ”ê²ƒì„ ì„±ê³µ 순서ë„ìƒì˜ DBì ‘ê·¼ì„ ì œí•œì„ ë‘ì–´ì•¼í• ê²ƒ ê°™ìŒ. ë¬¸ì œì : WallpaperManagerActivityì—서 Addì‹œí‚¤ê³ settingí•˜ëŠ”ë° ê°ì²´ê°€ 뻑남=ã…‚= 우짬.. ì•„! ìš°ì„ ë§Œë“¤ì–´ë†“ê³ settingí• ë•Œë§Œ DBì— ì €ìž¥ì‹œí‚¤ëŠ” ë°©ì‹ìœ¼ë¡œ í•´ì•¼ê² ë‹¤.ê·¸ë¦¬ê³ 0으로 indexê°€ 없는것과 ìžˆëŠ”ê²ƒì„ í‘œê¸°í•´ì„œ updateí˜¹ì€ ìƒˆë¡œ 만들기를 실행하ë„ë¡ í•˜ê³ . OK }}} || || 4/26 || ì „ì²´ Activityê°„ì˜ Parcelë°ì´íƒ€ë¥¼ 넘길수 있게 코드를 ë¦¬íŽ™í† ë§(Refactoring)함. DBì˜ ì—°ê²°ë¬¸ì œë¥¼ ì‚ì œ 삽입 목ë¡ì— flag를 달아 í•´ê²°. 파ì¼ì„ ì„ íƒí•´ì„œ Path와 Nameì„ ë³´ì—¬ì£¼ëŠ” Activityì˜ Thumnailì„ ë§Œë“¤ì–´ 보여주게함. Refactoring후 Service ìž˜ìž‘ë™ í™•ì¸. || == Reference == * ì»¤ë‹ˆì˜ ì•ˆë“œë¡œì´ë“œ ì´ì•¼ê¸° : http://androidhuman.tistory.com * Enum공부하다가 ìž ê¹ ë³¸ Enumì˜ ë³€íƒœì„± : http://devyongsik.tistory.com/297 * ?-? ë³€íƒœì„±ì´ ì–´ë””ì— ì–¸ê¸‰ë˜ì–´ìžˆì§• - [서지혜] * Enum으로 interfaceë„ ë§Œë“¤ê³ . ê·¸ì•ˆì— ì—¬ëŸ¬ê°€ì§€ ì•Œê³ ë¦¬ì¦˜ì„ ë‚˜ì—´í•´ì„œ ì“°ëŠ”ê±°ì— ì—„ì² ë†€ëž¨.ã…‹ã…‹ã…‹ - [김준ì„] * ì•„ã…‹ã…‹ interface를 ë§Œë“ ê±°ê°™ì§„ 않지만.. enumì€ ê°ì²´ê°™ì§€ë§Œ 타입임ㅋㅋ 오늘부터 jcpì—서 표준 문서 ì½ê¸°ë¡œ 했다는 - [서지혜] * jcpê°€ ì–´ë””ëƒëŠ¥... - [김준ì„] * 안드로ì´ë“œ ë°ì´í„°ë² ì´ìФ ë° ì“°ë ˆë“œì— ê´€í•´ : http://www.vogella.com/articles/AndroidSQLite/article.html * 안드로ì´ë“œ XMLì„ ì–¸ë•Œ 버튼 ìž…ë ¥ 처리해줄 메소드 ì„ ì–¸ : http://shinehand.egloos.com/568288 * ìžë°” OGG 사운드 처리 방법 ìƒê° : http://www.gpgstudy.com/forum/viewtopic.php?t=20662 * DB Repository ë°ì´í„°ë¥¼ 왔다갔다 intent사ì´ë¥¼ ë„˜ê²¨ì£¼ë ¤ë©´ parcel해야함 : http://arsviator.blogspot.com/2010/10/parcelable%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EB%8B%AC-object.html * Custom Android List Adapter : http://androidhuman.tistory.com/entry/11-List-%EC%A7%91%EC%A4%91%EA%B3%B5%EB%9E%B5-3-Custom-ArrayAdapter%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-ListView * Thumnailì œìž‘ì„ ìœ„í•œ Multi-Threadë°©ì‹ Image Loading : http://lifesay.springnote.com/pages/6615799 * Thumnailìžë™ ì œìž‘í•´ì£¼ëŠ” API. ì„±ëŠ¥ì€ ëª¨ë¥´ê² ë‹¤ : http://code.google.com/p/thumbnailator/ = ìž¡ì§€ì‹ = * 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ì— í–¥ìƒëœ ë°˜ë³µë¬¸ì„ ë°˜ë“œì‹œ 사용하ì‹ì‹œì˜¤; 부주ì˜í•œ ë¶€ìž‘ìš©ì´ ì•„ë‹Œ ì‹ ì¤‘í•œ ì„ íƒì„ 통해서 ë§ìž…니다. ìœ ë¹„ë¬´í™˜ìž…ë‹ˆë‹¤! ë¬´ì—‡ì„ í•˜ëŠ”ì§€ ì•Œê³ í•˜ì„¸ìš”! 좋아하는 ì¢Œìš°ëª…ì„ ì—¬ê¸°ì— ë„£ìœ¼ì„¸ìš”, 그러나 ì–¸ì œë‚˜ ì—¬ëŸ¬ë¶„ì˜ ì½”ë“œê°€ ë¬´ì—‡ì„ í•˜ëŠ”ì§€ ì£¼ì˜ ê¹Šê²Œ ìƒê°í•˜ê³ , ì†ë„를 높ì´ëŠ” ë°©ë²•ì„ ì°¾ë„ë¡ ê²½ê³„í•˜ì‹ì‹œì˜¤. }}} * ì›ë¬¸ì¶œì²˜ : [http://developer.android.com/guide/practices/design/performance.html] = 중얼거림 = 1. fetch맞는ë°.. 불러오는거 1. ë¦¬íŽ™í† ë§ì€ 새로 만드는것보다 쉽구나. 1. ê·¼ë° ì•ˆë“œë¡œì´ë“œëŠ” 복잡하구나.. ì•„ì§ ë©€ì—ˆë„¤ 1. ì–´í”Œì´ ì¢‹ì•„ì§€ë©´ ì¢‹ì•„ì§ˆìˆ˜ë¡ ë‚´ 머리는 터지는군. ---- [2012년활ë™ì§€ë„] [김준ì„]