AsyncTask는 이전 글인 [안드로이드]AsyncTask에 대하여...(전체소스포함) 에서 설명을 했으며 Thread에 대해서는 저의 다음블로그에서 자바 스레드(Threads)에 대해(http://blog.daum.net/dayhyub/56)에서 이야기를 했는데 기본적인 안드로이드 Thread의 이야기만 조금만 더 해보고 프로그래스바 구현 두개의 소스로 비교를 해보도록 하겠습니다.

Thread의 정의

● 프로세스보다 작은 단위이기 때문에 프로세스보다 가볍다.

● Thread는 서로의 자원을 공유하면서 동작한다.

● 한꺼번에 여러개의 작업을 동시에 실행할 때 사용된다.

Thread생성방법

● Thread클래스를 상속하는 방법

● Runnable인터페이스를 구현하는 방법

(1) Progressbar구현을 위해 함수 call방법비교

● Thread

startProgressBarThread2(); 

● AsyncTask

new ImageCallTask().execute(limgurl);
(2) Progressbar구현 사전 준비

● Thread

	// 막대형 진행상자//////////////////////////
	public ProgressDialog progressDialog;
	private volatile Thread theProgressBarThread2;
	
	public synchronized void startProgressBarThread2() {
		if (theProgressBarThread2 == null) {
			theProgressBarThread2 = new Thread(null, backgroundTread2,
					"startProgressBarThread2");
			theProgressBarThread2.start();

			progressDialog = new ProgressDialog(act);
			progressDialog.setCanceledOnTouchOutside (false);
			progressDialog.setCancelable(false);
			progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			progressDialog.setMessage("최신데이타로 업데이트중입니다.잠시만 기다리십시요 ...");
			progressDialog.incrementProgressBy(1);
			progressDialog.setMax(100);
			progressDialog.setProgress(0);
			progressDialog.show();
		}
	}

● AsyncTask

		private ProgressDialog dialog;
		
		// 이곳에 포함된 code는 AsyncTask가 execute 되자 마자 UI 스레드에서 실행됨.
		// 작업 시작을 UI에 표현하거나
		// background 작업을 위한 ProgressBar를 보여 주는 등의 코드를 작성.
		@Override
		protected void onPreExecute() {
	           // 작업을 시작하기 전 할일
	        dialog = new ProgressDialog(DetailActivity.con);
	        dialog.setTitle("이미지 다운로드중");
	        dialog.setMessage("잠시만 기다리세요...");
	        dialog.setIndeterminate(true);
	        dialog.setCancelable(true);
	        dialog.show();
	        			
			super.onPreExecute();
		}	
(3) Progressbar구현 작업

● Thread

	private Runnable backgroundTread2 = new Runnable() {
		@Override
		public void run() {
			if (Thread.currentThread() == theProgressBarThread2) {
				int prate= (int)(100/AString.length);
			    for(int i =0; i < AString.length;i++){
					DataInfo[] datainfo3 = new DataInfo[PublicCall.datacnt];
					String url="http://openapi.seoul.go.kr:8088/6461656879756232303536/xml/ListLostArticleService/1/"+DATACALL+"/"+AString[i];
					datainfo3 = PublicCall.publiccall(url,DATACALL);
					InsertCall.insertcall(datainfo3);
					//Log.d(TAG,"InsertCall :["+i+"]prate=["+prate+"]");
					if(i == (AString.length-1)) progressrate = 100;
					else progressrate = (int)(prate*(i+1));
					progressBarHandle2.sendMessage(progressBarHandle2
							.obtainMessage());					
				}	
			}
		}

● AsyncTask

				@Override
		protected Bitmap doInBackground(String... urls) {
			/* http://snowbora.com/417 참고 
			 * http://android-developers.blogspot.kr/2010/07/multithreading-for-performance.html
			 * */
			final HttpClient client			= AndroidHttpClient.newInstance("Android");
	        final HttpGet getRequest 		= new HttpGet(urls[0]);
	        final int IMAGE_MAX_SIZE 		= 1280;
	        
	        try 
	        {
	            HttpResponse response = client.execute(getRequest);
	            final int statusCode = response.getStatusLine().getStatusCode();
	            
	            if (statusCode != HttpStatus.SC_OK) 
	            {
	                return null;
	            }

	            final HttpEntity entity = response.getEntity();
	            
	            if (entity != null) 
	            {
	                InputStream inputStream = null;
	                
	                try 
	                {
	                	inputStream = entity.getContent();
	                	
	                	BitmapFactory.Options bfo 	= new BitmapFactory.Options();
	                	bfo.inJustDecodeBounds 		= true;

	                    BitmapFactory.decodeStream(new FlushedInputStream(inputStream), null, bfo);
	                    
	                    if(bfo.outHeight * bfo.outWidth >= IMAGE_MAX_SIZE * IMAGE_MAX_SIZE)
	                    {
	                    	bfo.inSampleSize = (int)Math.pow(2, (int)Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(bfo.outHeight, bfo.outWidth)) / Math.log(0.5)));
	                    }
	                    bfo.inJustDecodeBounds = false;
	                    
	                    response = client.execute(getRequest);
	                    final int nRetryStatusCode = response.getStatusLine().getStatusCode();
	                    
	                    if (nRetryStatusCode != HttpStatus.SC_OK) 
	                    {
	                        return null;
	                    }
	                    
	                    final HttpEntity reEntity = response.getEntity();
	                    
	                    if (reEntity != null)
	                    {
	                    	InputStream reInputStream = null;
	                    	
	                    	try
	                    	{
	                    		reInputStream = reEntity.getContent();
	                    		final Bitmap imgBitmap = BitmapFactory.decodeStream(new FlushedInputStream(reInputStream), null, bfo);
	                            
	                            return imgBitmap;
	                    	}
	                    	finally 
	                        {
	                    		 if (reInputStream != null) 
	                             {
	                    			 reInputStream.close();
	                             }
	                    		 
	                    		 reEntity.consumeContent();
	                        }
	                    }
	                } 
	                finally 
	                {
	                    if (inputStream != null) 
	                    {
	                        inputStream.close();
	                    }
	                    
	                    entity.consumeContent();
	                }
	            }
	        } 
	        catch (IOException e) 
	        {
	            getRequest.abort();
	        } 
	        catch (IllegalStateException e) 
	        {
	            getRequest.abort();
	        } 
	        catch (Exception e) 
	        {
	            getRequest.abort();
	        } 
	        finally 
	        {
	            if ((client instanceof AndroidHttpClient)) 
	            {
	                ((AndroidHttpClient)client).close();
	            }
	        }
	        
	        return null;
	        
	    }	
(4) Progressbar작업진행상태 표시

● Thread

Handler progressBarHandle2 = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				
				progressDialog.setProgress(progressrate);
				progressDialog.setMessage("데이타를 저장중입니다.잠시만 기다리십시요 ..."
						+ progressrate + "%");
				if (progressrate == 100) {
					stopProgressBarThread2();
					datainfo = selectData();
					appendRowFirst(datainfo);					
				}
			}
		};

● AsyncTask

// onInBackground(...)에서 publishProgress(...)를 사용하면
		protected void onProgressUpdate(Integer... progress) {
			Log.d("LOST","onProgressUpdate:["+progress[0]+"]");
			dialog.setProgress(progress[0]);
		}
		
(5) Progressbar작업 완료 작업

● Thread

public synchronized void stopProgressBarThread2() {
		if (theProgressBarThread2 != null) {
			Thread tmpThread = theProgressBarThread2;
			theProgressBarThread2 = null;
			tmpThread.interrupt();
		}
		if (progressDialog != null)
			progressDialog.dismiss();
	}

● AsyncTask

// onInBackground(...)가 완료되면 자동으로 실행되는 callback
		// 이곳에서 onInBackground가 리턴한 정보를 UI위젯에 표시 하는 등의 작업을 수행함.
		// (예제에서는 작업에 걸린 총 시간을 UI위젯 중 TextView에 표시함)
		@Override
		protected void onPostExecute(Bitmap imgBitmap) {
			if (imgBitmap != null)
			{
				imgView.setImageBitmap(imgBitmap);
				imgView.setVisibility(ImageView.VISIBLE);
				textview.setVisibility(TextView.GONE);
			}else{
				//textview = (TextView)findViewById(R.id.textView1);
				textview.setText("등록된 사진이 없습니다.");
				textview.setTextSize(20);
				textview.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
				textview.setVisibility(TextView.VISIBLE);
				imgView.setVisibility(ImageView.GONE);
			}
		
			dialog.dismiss();
		}

AsyncTask를 이야기하려다 보니 프로그래스바 하나 넣어보겠다고 3~4일을 고생해서 겨우 찾아냈던 기억이 새록새록하면서 애증이 담겨있는 class라는 생각이 듭니다. 하지만 AsyncTask는  thread+handler라는 나름 강력한 기능을 가지고 있어서 제대로만 알아두면 유용한 클래스가 되지 않을까 싶습니다.

혹시 책만 보고 도전했다가 저같이 고생하지 않았으면 하는 맘에 이 글을 적어봅니다.

왜 AsyncTask인가?

AsyncTask라는 클래스 이름은 Asynchronous Task의 줄임이며, UI스레드의 입장에서 볼 때 비동기적으로 작업이 수행되기 때문에 붙여진 이름이다.

sync 와 async의 용어는 네트워크이나 커널 프로세스에서 많이 나오는 용어인데요.저야 뭐 이론적으로는 공부한지 하도 오래되어서 그냥 프로그램 하면서 경험으로 이야기를 해보겠습니다.

보통 sync는 직렬회로라고 보시면 될듯합니다. 반면 async는 병렬회로라고 보시면 될 듯 합니다.

일이 순차적으로 진행되면서 하나가 해결되면 그다음 일이 진행되는 식으로 네트워크에서는 요청(request)를 보내면 항상 응답(response)을 받아야 진행하는 방식으로 구현하면 sync방식,아니고 계속 요청을 보내는 통로와 응답을 받는 통로를 따로 만들어두면 async방식이라고 불리죠.

AsyncTask는 UI 처리 및 Background 작업 등 을 하나의 클래스에서 작업 할 수 있게 지원해 줍니다. 쉽게말해 메인Thread와 일반Thread를 가지고 Handler를 사용하여 핸들링하지 않아도 AsyncTask 객체하나로 각각의 주기마다 CallBack 메서드가 편하게 UI를 수정 할 수 있고, Background 작업을 진행 할 수 있습니다.

AsyncTask사용법

Object로부터 상속하는 AsyncTask는 Generic Class이기 때문에 사용하고자 하는 type을 지정해야 합니다.
AsyncTask클래스는 기본적으로 3개의 제네릭 타입(Params,Progress,Result)을 제공하고 있는 추상 클래스다.Generic type은 실행시간에 데이터 타입을 정할 수 있는 특수한 타입이라고 이해하시면 될 듯 합니다.

Params: background작업 시 필요한 data의 type 지정
Progress: background 작업 중 진행상황을 표현하는데 사용되는 data를 위한 type 지정
Result: 작업의 결과로 리턴 할 data 의 type 지정

예)

AsyncTask<DataInfo[], Integer, Void> InsertTask = new InsertCallTask().execute(datainfo4);

미사용 타입에 대해서는 Void를 전달하면 된다.  

AsyncTask Callbak 함수

언제 호출되여 무슨 작업을 하는가와 함께 각 메서드가 어떤 스레드에서 실행되는가도 중요하다. doInBackground메서드 외에는 모두 UI스레드에서 실행되므로 메인스레드의 view들을 안전하게 참조할 수 있습니다.

주로 쓰게 되는 콜백메스드 4개에 대해 설명해보겠습니다.

● void onPreExecute()

작업이 시작되기 전에 호출되며 UI스레드에서 실행되는 메소드,계산을 위한 초기화나 프로그래스 대화상자를 준비하는 등의 작업을 수행합니다.

		// 이곳에 포함된 code는 AsyncTask가 execute 되자 마자 UI 스레드에서 실행됨.
		// 작업 시작을 UI에 표현하거나
		// background 작업을 위한 ProgressBar를 보여 주는 등의 코드를 작성.
		@Override
		protected void onPreExecute() {
	           // 작업을 시작하기 전 할일
	        dialog = new ProgressDialog(DetailActivity.con);
	        dialog.setTitle("이미지 다운로드중");
	        dialog.setMessage("잠시만 기다리세요...");
	        dialog.setIndeterminate(true);
	        dialog.setCancelable(true);
	        dialog.show();
	        			
			super.onPreExecute();
		}	

Result doInBackground(Params... params)

백그라운드 스레드로 동작해야 하는 작업을 실행한다. execute메서드로 전달한 data tye이 params 인수로 전달되는데 여러개의 인수를 전달할 수 있으므로 배열 타입으로 되어 있습니다. 그래서 하나의 인수만 필요하다면 params[0]만 사용하면 됩니다. 작업 중에 publishProgress 메소드를 호출하여 작업 경과를 UI스레드로 display할 수 있으며 작업결과는 Result타입으로 리턴됩니다.

		@Override
		protected Bitmap doInBackground(String... urls) {
			/* http://snowbora.com/417 참고 
			 * http://android-developers.blogspot.kr/2010/07/multithreading-for-performance.html
			 * */
			final HttpClient client			= AndroidHttpClient.newInstance("Android");
	        final HttpGet getRequest 		= new HttpGet(urls[0]);
	        final int IMAGE_MAX_SIZE 		= 1280;
	        
	        try 
	        {
	            HttpResponse response = client.execute(getRequest);
	            final int statusCode = response.getStatusLine().getStatusCode();
	            
	            if (statusCode != HttpStatus.SC_OK) 
	            {
	                return null;
	            }

	            final HttpEntity entity = response.getEntity();
	            
	            if (entity != null) 
	            {
	                InputStream inputStream = null;
	                
	                try 
	                {
	                	inputStream = entity.getContent();
	                	
	                	BitmapFactory.Options bfo 	= new BitmapFactory.Options();
	                	bfo.inJustDecodeBounds 		= true;

	                    BitmapFactory.decodeStream(new FlushedInputStream(inputStream), null, bfo);
	                    
	                    if(bfo.outHeight * bfo.outWidth >= IMAGE_MAX_SIZE * IMAGE_MAX_SIZE)
	                    {
	                    	bfo.inSampleSize = (int)Math.pow(2, (int)Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(bfo.outHeight, bfo.outWidth)) / Math.log(0.5)));
	                    }
	                    bfo.inJustDecodeBounds = false;
	                    
	                    response = client.execute(getRequest);
	                    final int nRetryStatusCode = response.getStatusLine().getStatusCode();
	                    
	                    if (nRetryStatusCode != HttpStatus.SC_OK) 
	                    {
	                        return null;
	                    }
	                    
	                    final HttpEntity reEntity = response.getEntity();
	                    
	                    if (reEntity != null)
	                    {
	                    	InputStream reInputStream = null;
	                    	
	                    	try
	                    	{
	                    		reInputStream = reEntity.getContent();
	                    		final Bitmap imgBitmap = BitmapFactory.decodeStream(new FlushedInputStream(reInputStream), null, bfo);
	                            
	                            return imgBitmap;
	                    	}
	                    	finally 
	                        {
	                    		 if (reInputStream != null) 
	                             {
	                    			 reInputStream.close();
	                             }
	                    		 
	                    		 reEntity.consumeContent();
	                        }
	                    }
	                } 
	                finally 
	                {
	                    if (inputStream != null) 
	                    {
	                        inputStream.close();
	                    }
	                    
	                    entity.consumeContent();
	                }
	            }
	        } 
	        catch (IOException e) 
	        {
	            getRequest.abort();
	        } 
	        catch (IllegalStateException e) 
	        {
	            getRequest.abort();
	        } 
	        catch (Exception e) 
	        {
	            getRequest.abort();
	        } 
	        finally 
	        {
	            if ((client instanceof AndroidHttpClient)) 
	            {
	                ((AndroidHttpClient)client).close();
	            }
	        }
	        
	        return null;
	        
	    }

void onProgressUpdate(Progress... values)

doInBackground에서드에서 publishProgress(Progress...) 메소드를 호출할 때 호출되며 작업의 진행사항을 표시하기 위해 호출됩니다. UI스레드에서 프로그래스바에 진행 상태 표시하는 역할 수행합니다.

		// onInBackground(...)에서 publishProgress(...)를 사용하면
		protected void onProgressUpdate(Integer... progress) {
			Log.d("LOST","onProgressUpdate:["+progress[0]+"]");
			dialog.setProgress(progress[0]);
		}

 

void onPostExecute(Result result)

doInBackground에서드의 작업 결과를 UI반영하는 역할을 담당하는 메소드입니다.

		// onInBackground(...)가 완료되면 자동으로 실행되는 callback
		// 이곳에서 onInBackground가 리턴한 정보를 UI위젯에 표시 하는 등의 작업을 수행함.
		// (예제에서는 작업에 걸린 총 시간을 UI위젯 중 TextView에 표시함)
		@Override
		protected void onPostExecute(Bitmap imgBitmap) {
			if (imgBitmap != null)
			{
				imgView.setImageBitmap(imgBitmap);
				imgView.setVisibility(ImageView.VISIBLE);
				textview.setVisibility(TextView.GONE);
			}else{
				//textview = (TextView)findViewById(R.id.textView1);
				textview.setText("등록된 사진이 없습니다.");
				textview.setTextSize(20);
				textview.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
				textview.setVisibility(TextView.VISIBLE);
				imgView.setVisibility(ImageView.GONE);
			}
		
			dialog.dismiss();
		}
		

일반적으로 AsyncTask를 구현할 때 별도 스레드로 동작해야 할 코드는 doInBackground()메소드에 작성하고, 이 메소드가 실행되기 전에 먼저 처리할 코드가 있다면 onPreExecute()메소드에 작성하면 됩니다. 그리고 doInBackground()메소드가 완료된 후에 처리해야할 코드는 onPostExecute()메소드에 작성하고, doInBackground()메소드가 동작하는 동안 주기적으로 반영해야 할 작업이 있다면 onProgressUpdate()메소드에 작성하면 됩니다.

이 때 주의사항은 3개의 제네릭 타입과 메소드 타입을 잘 맞춰어야 원하는 결과가 나온다는 겁니다.이게 잘 안 맞아서 저는 코딩하면서 구현이 제대로 안되고 한참 고생했던 기억이 있습니다. 꼭 doInBackground, onPreExecute, onPostExecute의 리턴값과 파라미터를 잘 매칭해야 합니다.

 

참고사이트

activity에서는 onWindowFocusChanged를 사용하여 생성되었던 textview에 대한 사이즈를 구해서 재조정하는 resize함수를 사용했었는데 이걸 fragment에서는 어떻게 구현할까 하는 문제에 부딪쳐 인터넷 검색하여 얻어낸 소스를 공개할까합니다.

Activity 소스

	⁄*
	 * (non-Javadoc)
	 * @see android.app.Activity#onWindowFocusChanged(boolean)
	 * 다 그려지기 전에는 textview의 높이와 길이를 불러올 수 없어 
	 * 다 그리고 불러지는 함수인 onWindowFocusChanged로 
	 * resize함
	 *⁄
	public void onWindowFocusChanged(boolean hasFocus) {
		if(datainfo !=null){
			if(datainfo.length !=0)
				resizeRow(datainfo.length);

		}
	}

 Fragment 소스

	public void onResume() {
		super.onResume();

		//mStatus.setText("현재 상태 : 서비스 시작");
		cellwidth = new int[5];
		desity = this.getResources().getDisplayMetrics().density;
		appendHeader();
		appendRowBlank();
		datainfo = selectData();
		appendRow(datainfo);	
		ViewTreeObserver vto = headerView[0].getViewTreeObserver();
		vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
		  @SuppressWarnings("deprecation")
		@Override
		  public void onGlobalLayout() {
		    //Log.d(TAG, "Height = " + headerView[0].getWidth() + " Width = " + tv[0][0].getWidth());
		    ViewTreeObserver obs = headerView[0].getViewTreeObserver();
		    resizeRow();
		    obs.removeGlobalOnLayoutListener(this);
		  }
		});		
		
	}	

위와같이 ViewTreeObserver 인스턴스를 하나 만들어서 뷰의 계층의 글로벌 변경에 대한 리스너를 등록한다.

ViewTreeObserver.OnGlobalLayoutListener
Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.
어떤 뷰의 레이아웃이나 가시성이 변경되는 것을 알 수 있다.

이 리스너를 등록하여 다시 resize하도록 한다.

전체소스

 

PublicWifi_0409.zip

 

액션바 taps+swipe에서 잠시 언급했던 fragment에 대해 이번엔 정리해 보도록 하겠습니다.

안드로이드 플랫폼 3.0 버전인 허니컴Honeycomb부터 큰 화면의 태블릿을 위해 도입된 대표적인 컴포넌트가 프래그먼트(Fragment)와 액션바(ActionBar)입니다.그 중 액션바는 이전 게시판에서 이야기했으니 오늘은 프래그먼트에 대해서입니다.

 프래그먼트 개요

만약 스마트 폰 앱을 개발할 때 세로모드(Portrait)에서는 단순하게 화면을 구성하지만 가로모드(Landscape) 화면 구성을 좀더 다양한 화면으로 구성 하고 싶다면 어떻게 할지 고민할 수 있습니다. 또는 해상도나 디바이스의 스크린크기, 디바이스 종류에 따라서 보여지는 것들을 다르게 구성하고 싶어할 수도 있습니다.

안드로이드에서는 이러한 요구사항을 만족시켜줄 수 있는 것을 fragment라는 개념으로 추가하게 되었습니다.

안드로이드 3.0이전 버전에서는 한 화면에 보이는 모든 것을 관장하는 것이 Activity라는 개념이였는데 이러한 Activity는 하나의 화면에 여러개 사용할 수 없게 설계가 되어있습니다.

그러나 하나의 액티비티로 상호작용하는 것은 큰 화면의 기기에서 비효율적이며 자원 낭비가 심하고 또한 일부 화면을 위한 자원을 재사용할 수 없고, 다양한 내용을 표시하려면 구성이 매우 복잡했습니다.

그래서 하나의 화면에 Activity와 비슷한 개념을 가지면서도 여러가지 화면을 넣을 수 있는 방법으로 fragment가 등장하게 되었습니다. 

 프래그먼트 특징

1) 프래그먼트는 액티비티 조각 혹은 서브액티비티(subactivity)

2) 하나의 액티비티에서 다수의 프래그먼트를 결합하여 다중 패널multi-pane UI를 생성할 수 있음

3) 여러 액티비티에서 하나의 프래그먼트를 재활용 가능

4) 프래그먼트 자신만의 생명주기 가짐

5) 입력 이벤트를 받아들이며, 액티비티가 실행하는 동안 동적으로 추가 혹은 삭제 가능

6) activity 안에서만 존재할 수 있고 단독으로 존재할 수 없다.

7) activity 안에서 다른 view와 함께 존재 가능

8) back stack을 사용가능

9) 반드시 default 생성자 존재

 프래그먼트 디자인 철학

애플리케이션을 태블릿과 핸드셋에 모두 지원하도록 설계하고자 한다면 화면의 사이즈에 따라 최적의 UX을 맛볼 수 있도록 프래그먼트를 다양한 레이아웃 구성에 재사용할 수 있습니다.

작은 화면사이즈 기기에는 하나의 액티비티에 하나의 프래그먼트를 사용하는 1-패널 UI를 제공하고, 태블릿과 같은 큰 화면사이즈 기기에서는 2개의 프래그먼트를 하나의 액티비티에 조합하여 2-패널UI를 제공하면 됩니다. 

 프래그먼트 생명주기

● 활성resumed 상태 : 실행 중인 액티비티에서 프래그먼트가 보이는 상태

● 중지paused 상태 : 다른 액티비티가 포그라운드 상태이며 포커스를 가지고 있지만 프래그먼트가 거주하는 액티비티가 여전히 보이는 상태

● 정지stopped 상태 : 프래그먼트가 보이지 않는다. 호스트 액티비티가 정지되거나 프래그먼트가 액티비티에서 제거되고 백스택에 추가되어 있는 상태 정지된 프래그먼트는 여전히 살아 있어 모든 상태와 멤버 정보가 시스템에 보관되어 있으나 사용자에게 더 이상 보이지 않고 액티비티가 종료되면 프래그먼트도 같이 종료됨

아래 소스에서 사용한 메소드는 onActivityCreated,onResume,onPause, onCreateView정도여서 http://blog.saltfactory.net/190 의 설명을 인용하도록 하겠습니다.

● onAttach()

onAttach() 콜백 메소드는 fargment가 activity에 추가되고 나면 호출된다. 이때 Activity가 파라미터로 전달되게 된다. Fragment는 Activity가 아니다. 다시말해서 Activity는 Context를 상속받아서 Context가 가지고 있는 많이 있지만 Fragment는 android.app 의 Object 클래스를 상속받아서 만들어진 클래스이다. 그래서 Fragment에서는 Context의 기능을 바로 사용할 수 없다. 뿐만 아니라 Fragment는  Activity 안에서만 존재하기 때문에 Activity를 onAttach() 콜백으로 받아올 수 있다.

 public void onAttach(Activity activity) {
      super.onAttach(activity);
 }



onCreate()

프래그먼트가 생성될 때 호출된다. 프래그먼트가 중지 혹은 정지된 후 재개될 때 보유하기 원하는 프래그먼트의 필수 컴포넌트을 초기화한다.

Fragment의 onCreate() 메소드는 Activity의 onCreate() 메소드와 비슷하지만 Bundle을 받아오기 때문에 bundle에 대한 속성을 사용할 수 있다.

 	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}


onCreateView()

Fragment의 onCreateView()는 Fragment에 실제 사용할 뷰를 만드는 작업을 하는 메소드이다. LayoutInflater를 인자로 받아서 layout으로 설정한 XML을 연결하거나 bundle에 의한 작업을 하는 메소드이다. 

	public View onCreateView(LayoutInflater inflater, ViewGroup container,
	                Bundle savedInstanceState)
	{
	        view = inflater.inflate(R.layout.table, container, false);
	        return view;
	}


onActivityCreated()

onActivityCreate() 메소드는 Activity에서 Fragment를 모두 생성하고 난 다음에 (Activity의 onCreate()가 마치고 난 다음)에 생성되는 메소드이다. 이 메소드가 호출될 때에서는 Activity의 모든 View가 만들어지고 난 다음이기 때문에 View를 변경하는 등의 작업을 할 수 있다.그러나, fragment에서 생성된 view의 size를 알려고 하니 이 메소드에서는 getWidth가 0으로 나와 따로 메소

    public void onActivityCreated(Bundle savedInstanceState) {
    	//Log.d(TAG,"onActivityCreated");
	    super.onActivityCreated(savedInstanceState);
    }


onStart()

이 메소드가 호출되면 화면의 모든 UI가 만들어진 지고 호출이 된다.

    public void onStart(){
    	super.onStart();
    }


onResume()

이 메소드가 호출되고 난 다음에 사용자와 Fragment와 상호작용이 가능하다. 다시 말해서 이 곳에서 사용자가 버튼을 누르거나 하는 이벤트를 받을 수 있게 된다.

	public void onResume() {
		super.onResume();

		⁄⁄mStatus.setText("현재 상태 : 서비스 시작");
		cellwidth = new int[5];
		desity = this.getResources().getDisplayMetrics().density;
		appendHeader();
		appendRowBlank();
		datainfo = selectData();
		appendRow(datainfo);	
		ViewTreeObserver vto = headerView[0].getViewTreeObserver();
		vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
		  @SuppressWarnings("deprecation")
		@Override
		  public void onGlobalLayout() {
		    ⁄⁄Log.d(TAG, "Height = " + headerView[0].getWidth() + " Width = " + tv[0][0].getWidth());
		    ViewTreeObserver obs = headerView[0].getViewTreeObserver();
		    resizeRow();
		    obs.removeGlobalOnLayoutListener(this);
		  }
		});		
		
	}	


onPause()

이 메소드는 Fragment가 다시 돌아갈 때 (Back) 처음으로 불려지는 콜백 메소드이다. 

    
   @Override
    public void onPause() {
        super.onPause();
    }


onSaveInstanceState()

이 메소드에서는 Activity와 동일하게 Fragment가 사라질때 현재의 상태를 저장하고 나중에 Fragment가 돌아오면 다시 저장한 내용을 사용할 수 있게해주는 메소드이다.

    
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putInt("text", "savedText");
    }


● onStop()

Fragment의 onStop() 메소드는 Activity의 onStop()메소드와 비슷하다. 이 콜백 메소드가 호출되면 Fragment가 더이상 보이지 않는 상태이고 더이상 Activity에서 Fragment에게 오퍼레이션을 할 수 없게 된다.

    
    @Override
    public void onStop() {
        super.onStop();
    }


onDestroyView()

Fragment의 View가 모두 소멸될 때 호출되는 콜백 메소드이다. 이때 View에 관련된 모든 자원들이 사라지게 된다.

    
    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }


onDestroy()

Fragment를 더이상 사용하지 않을 때 호출되는 콜백 메소드이다.  하지만 Activity와의 연결은 아직 끊어진 상태는 아니다.

    @Override
    public void onDestroy() {
        super.onDestroy();
    }


onDetach()

Fragment가 더이상 Activity와 관계가 없을 때 두 사이의 연결을 끊으며 Fragment에 관련된 모든 자원들이 사라지게 된다.

   @Override
    public void onDetach() {
       super.onDetach();
       activity = null;
    }

 전체소스

PublicWifi_0409.zip

참고사이트

http://developer.android.com/guide/components/fragments.html

http://blog.saltfactory.net/190

swipe기능이 뭔지 어색할지 모르지만 스마트폰 사용자라면 지금도 많이 사용하고 있는 기능입니다. 특히 안 가르쳐줘도 아이들은 정말 잘 사용하는 기능이죠. 손가락으로 클릭후 오른쪽/왼쪽으로 움직하면 화면이 변경되는 기능입니다.

Android 3.0 부터 제공되는 Swipe Views와 Tabs + Swipe를 편리하게 생성하고 사용할 수 있습니다. 

프로젝트 생성시 다른 프로젝트와 조금 다르게 설정만 해주면 됩니다.

그럼 작성도 안 했는데 많은 소스의 내용이 생깁니다. 왠지 뿌듯하지만 작동로직을 제대로 이해하지 못하면 남이 짠 소스이니 구현에 힘이 들 수 있습니다.

이제부터 자동생성 소스 중 변경부분을 위주로 설명하도록 하겠습니다.

 

 변경전 소스

		@Override
		public Fragment getItem(int position) {
			⁄⁄ getItem is called to instantiate the fragment for the given page.
			⁄⁄ Return a DummySectionFragment (defined as a static inner class
			⁄⁄ below) with the page number as its lone argument.
			Fragment fragment = new DummySectionFragment();
			Bundle args = new Bundle();
			args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
			fragment.setArguments(args);
			return fragment;
		}

 변경후 소스

		@Override
		public Fragment getItem(int position) {
			Fragment fragment = null;
			
			⁄⁄ getItem is called to instantiate the fragment for the given page.
			⁄⁄ Return a DummySectionFragment (defined as a static inner class
			⁄⁄ below) with the page number as its lone argument.
			switch (position) {
			case 0:			
				fragment = new TabOne();
				break;
			case 1:			
				fragment = new TabTwo();
				break;
			}
			return fragment;
		}

메인activity는 이렇게 만 변경하고 물론 tab이 기본이 3개인데 2개 바꾸는 기본적인 변경의 내용은 그냥 생략했습니다. 보면 직관적으로 알 수 있기 때문에 전체 소스를 참고하시면 될 듯 합니다.

public class TabOne extends Fragment implements MapView.OpenAPIKeyAuthenticationResultListener, 
MapView.MapViewEventListener,
MapView.CurrentLocationEventListener,
MapView.POIItemEventListener {

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);

	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
	                Bundle savedInstanceState)
	{
	        view = inflater.inflate(R.layout.map, container, false);
	        linearLayout = (LinearLayout)view.findViewById(R.id.maplayout);
	}	

}

public class TabTwo extends Fragment {

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
	    super.onActivityCreated(savedInstanceState);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
	                Bundle savedInstanceState)
	{
	        view = inflater.inflate(R.layout.table, container, false);
	        return view;
	}
}

이렇게 2개의 fragment를 생성해주면 됩니다. 프래그먼트에 대해서는 다음에 한번 이론적으로 접근해보고 지금은 이렇게 activity와 같은 식으로 추가해주면 아래와 같은 기능을 가진 앱이 만들어집니다.

그래도 이 기능은 기본 소스를 만들어주어서 참 편하게 구현할 수 있는 기능입니다.

 

 

 

PublicWifi_0409.zip

아직 생각한 모든 기능을 구현한 것은 아니지만 이 주제는 다 구현했으니 전체 소스도 첨부합니다.

액션바에 아래와 같이 검색창을 넣을 수 있는데 이를 위해서는 3가지 작업이 필요하다.

1) menu xml에 SearchView item추가

    <item
        android:id="@+id⁄action_search"
 
        android:showAsAction="ifRoom"
        android:actionViewClass="android.widget.SearchView"
        android:title="@string⁄search"
       android:icon="@drawable⁄search"        
    ><⁄item>

2) onCreateOptionsMenu 에 search view 항목추가 및 Listener 설정

@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_lost, menu); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { searchItem = menu.findItem(R.id.action_search); searchView = (SearchView) searchItem.getActionView(); searchView.setQueryHint("물품명 또는 분류"); searchView.setOnQueryTextListener(queryTextListener); SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); if(null!=searchManager ) { searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); } searchView.setIconifiedByDefault(true); } return true; }

 

개발참고사이트 : http://developer.android.com/reference/android/widget/SearchView.html 

 setQueryHint(CharSequence)

 쿼리 필드가 비어있을 때 나타나는 문장 설정

 setOnQueryTextListener(SearchView.OnQueryTextListener listener)

 쿼리 변경시 사용자 액션의 listener 설정

setSearchableInfo(SearchableInfo searchable)  SearchableInfo 설정
 setIconifiedByDefault(boolean iconified)  검색창의 기본상태 설정
true : 필드가 보임
false : 아이콘으로 보임

3) 리스너에 처리로직 구현

	private OnQueryTextListener queryTextListener = new OnQueryTextListener() {
		@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
		@Override
		public boolean onQueryTextSubmit(String query) {
			⁄⁄ TODO Auto-generated method stub
			⁄⁄searchItem.collapseActionView();
			⁄⁄Log.d(TAG,"onQueryTextSubmit");
			Cursor mCount= db.rawQuery("select count(*) from datainfo where GetName like '%" + query+"%' or cate like '%"+ query+"%'", null);
			mCount.moveToFirst();
			count= mCount.getInt(0);
			mCount.close();
			if(count <=0){
				Toast.makeText(LostActivity.con, "검색된 데이타가 없습니다.", Toast.LENGTH_LONG).show();
				return false;
			}
			datainfo = new DataInfo[count];
			Cursor result = db.rawQuery("SELECT * from datainfo where GetName like '%" + query+"%' or cate like '%"+ query+"%' order by GetDate desc", null);
			result.moveToFirst();	
			int i =0;
			while (i<count){
				datainfo[i] = new DataInfo();
				datainfo[i].SetLostId(result.getString(1));
				datainfo[i].SetLostName(result.getString(2));
				datainfo[i].SetLostURL(result.getString(3));
				datainfo[i].SetLostDate(result.getString(5));
				datainfo[i].SetLostTitle(result.getString(4));
				datainfo[i].SetTakePlace(result.getString(6));
				datainfo[i].SetTakeContact(result.getString(7));
				datainfo[i].SetLostPos(result.getString(9));
				datainfo[i].SetLostPlace(result.getString(10));
				datainfo[i].SetLostThing(result.getString(11));
				datainfo[i].SetLostStatus(result.getString(12));
				datainfo[i].SetLostImageUrl(result.getString(14));
				i++;
				result.moveToNext();
			}
			result.close();	
			appendRow(datainfo);
			resizeRow(count);
			⁄⁄Log.d(TAG,"onQueryTextSubmit"+count);
			imm= (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
			imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
			searchView.setQuery("", false);
			searchView.setIconified(true);
			⁄⁄Toast.makeText(LostActivity.con, "onQueryTextSubmit:["+count+"]", Toast.LENGTH_LONG).show();
			return false;
		}
		@Override
		public boolean onQueryTextChange(String newText) {
			// TODO Auto-generated method stub
			return false;
		}
	};

 

 

Lost_completed.zip

 

 

 

 

내가 지금까지 개발한 주차장앱,분실물찾기,지금 만들고 있는 공공WIFI앱에는 모두 액션바에 액션항목들이 하나씩 들어가 있습니다.

주차장앱에는 refresh 버튼이, 분실물찾기앱에는 search view, 공공WIFI앱에는 fixed tab과 swipe기능이 추가되었습니다.

이에 대한 이론적인 설명과 소스를 통한 예제를 통한 설명을 해보고자 합니다.

액션 항목(action item) : 액션바에서의 메뉴 혹은 버튼과 같은 뷰를 추가하여 사용자의 편의를 제공하는 UI

액션바와 메뉴 

액션바는 메뉴와 밀접한 관계가 있는데 안드로이드 3.0 이상이 탑재된 단말기의 경우 대부분 메뉴 버튼이 없습니다. 액션바는 다양한 역할을 담당하지만 주된 역할이 메뉴 버튼을 대신하는 것입니다. 따라서 액션바에 메뉴를 추가할 수 있습니다.

	<item 
	    android:id="@+id⁄menu_refresh"
		android:icon="@drawable⁄refresh11"
		android:title="@string⁄menu_refresh"
		android:showAsAction="ifRoom|withText" ⁄>

일반 메뉴 항목을 액션 아이템으로 표시되도록 하는 방법은 크게 두 가지입니다. 하나는 메뉴 XML을 사용하는 방법, 다른 하나는 코드로 구현하는 방법입니다. 두 가지 방법 모두 동일한 결과를 내는 데는 문제가 없지만, 코드를 통해 구현하는 경우 SDK 버전에 맞추어 별도로 코드를 작성해야 하므로 XML을 통해 설정하는 방법을 권장합니다.

ActionItem의 추가와 사용방법은 OptionMenu와 다르지 않습니다. 거의 똑같으나 showAsAction 속성만 추가되었습니다.

속성값은 아래와 같습니다.

 ActionItem showAsAction

never

 ActionBar에서는 보이지 않음

ifRoom   ActionBar에 공간이 있을경우 표시
always  ActionBar에 항상 표시
withText  ActionBar에 텍스트와 아이콘이 함께 표시


	⁄* (non-Javadoc)
	 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
	 * 다시 api로 데이타 불러와 DB에 update하고 
	 * 화면에 update 표시한다.
	 *⁄
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		⁄⁄ TODO Auto-generated method stub
		switch(item.getItemId()){
		case R.id.menu_refresh:
			Callhtml();
			calcDistance();
			refreshflag = true;
			updateDatainfo(datainfo);
			⁄** 계속 가끔 죽어서 원래 데이타 저장하도록해서 null이 리턴 되면 이전 데이타를 뿌려준다. *⁄
⁄⁄			System.arraycopy(datainfo, 0, datainfo2, 0, datacnt);
			datainfo2 = selectData();
⁄⁄			if(datainfo == null) System.arraycopy(datainfo2, 0, datainfo, 0, datacnt);
			if(datainfo2 == null) Log.d(TAG,"datainfo2 is null");
			appendHeader();
			appendRow(datainfo2);
			this.onWindowFocusChanged(true);
			break;
		}
		return super.onOptionsItemSelected(item);
	}
	
	⁄*
	 * (non-Javadoc)
	 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
	 * 메뉴를 parking이라는 메뉴로 설정한다.
	 *⁄
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		⁄⁄ Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.parking, menu);
		return true;
	}

위와 같이 메뉴 항목이나 액션 항목을 선택했을 때 수행할 작업 구현은 기존과 동일하게 onOptionsItemSelected() 메서드에서 처리합니다.

SeoulParking_completed.zip

안드로이드 3.0, 허니컴(Honeycomb)에서는 태블릿 단말에 최적화된 컴포넌트들이 많이 추가되었는데, 프래그먼트(Fragment)와 함께 허니컴에서 추가된 대표적인 UI 컴포넌트 중 하나가 바로 액션바(Action bar)입니다.

기존 안드로이드 APP의 TitleBar가 단순한 제목의 표시나 간단한 정보만을 표시했다면 액션바는 아래와 같은 다양한 기능을 제공하며 4.0부터는 메뉴버튼까지 대체합니다.

 

 액션바의 주요기능

1) 타이틀바의 기능인 제목 표시 및 간단한 정보 표현

2) 사용자의 현재 위치 식별

3) 각종메뉴-탐색메뉴,액션항목을 통한 단축메뉴-제공

4) Navigation 지원

 

 액션바의 주요목적

1) APP의 이름,정보 와 사용자 위치 식별하기 위한 전용공간 제공

2) 다른 APP사의 일관된 네비게이션과 뷰의 세분화 제공

3) 검색,생성 공유와 같은 주요 액션에 대한 용이한 젭근 및 예측 가능 접근

 

 액션바의 주요UI

 

1. 앱 아이콘

: 애플리케이션 아이콘은 애플리케이션에 아이덴티티를 부여합니다. 원할 경우 다른 로고나 브랜드로 교체할 수도 있습니다. 중요: 만약 애플리케이션이 현재 최상위 화면을 표시하고 있지 않다면, 사용자가 상위 계층으로 이동할 수 있도록 애플리케이션 왼쪽에 상위 기호(Up caret)를 표시해야 합니다.

2. view control

: 만약 애플리케이션이 여러가지 뷰에서 데이터를 표시하고 있다면, 액션 바의 이 부분은 사용자가 뷰 간을 전활할 수 있도록 해줍니다. 뷰 전환 컨트롤에는 드롭다운 메뉴나 탭 컨트롤 등이 있습니다. 만약 애플리케이션이 여러가지 뷰를 지원하지 않는다면, 이 영역을 애플리케이션 타이틀이나 더 긴 브랜드 정보 등을 표시하는데 사용할 수도 있습니다.

 3. Action button

: 액션 섹션에서 애플리케이션의 중요한 액션들을 표시합니다. 액션 바에 들어갈 공간이 없는 액션들은 자동으로 액션 오버플로우 속으로 이동합니다.

4. Action overflow

: 덜 자주 사용하는 액션은 액션 오버플로우에 배치합니다.

 

 액션바의 사용시 주의사항

ActionBar, 액션바를 사용하기 위해서는 targetSdkVersion에 11 이상을 지정하셔야 합니다.

1
<uses-sdk android:targetSdkVersion="11" />



minSdkVersion은 하위 버전에 대한 호환을 고려하지 않을경우, 11이상으로 해두시면 됩니다.

이렇게 targetSdkVersion을 API 11이상으로 설정하시면, 자동적으로 액션바가 사용이 됩니다.

 

 Split action bar

안드로이드 4.0부터 split action bar라고 불리는 추가적인 모드를 사용할 수 있다. 분할 액션바를 사용하면 화면 하단에 별도의 bar가 보이며, 좁은 화면에서 액티비티가 실행되더라도 모든 액션 항목을 보여준다. 액션바를분할하여 액션 항목을 분리하는 것은 상단에 내비게이션과 타이틀 요소를 남겨둔 채로 좁은 화면에서 합리적인 공간을 사용하여 모든 액션 항목을 표시한다. 그리고 다양한 기기의 화면 크기와 화면 회전에 고려한 APP을 만들 때 중요한 UI입니다.

분할 액션바를 활성화하려면 uiOptions="splitActionBarWhenNarrow"를 매니페스트 파일의 <activity> 혹은 <application> 엘리먼트에 추가하면 됩니다.

 

1. 메인 액션바

2. 상단바

3. 하단바

사용자가 빠르게 애플리케이션에서 제공하는 뷰 간을 전환할 수 있도록, 상단 바에 탭이나 스피너를 배치하고 액션과 액션 오버 플로우를 표시하려면 하단 바를 배치하는 방법이 유용한 UI가 될 수 있습니다.

 

아직 split action bar는 사용을 해보지 못해서 이론적인 부분만 추가해 두었습니다.

서울시는 다양한 서비스와 데이터를 좀더 쉽게 이용하도록 하기 위해서 개인 개발자에게 다양한 API를 제공해주고 있습니다. 저도 이를 이용해서 [서울시공영주차장주차가능대수조회]와 [분실물 찾기]앱을 만들었습니다.

 

이에 관심 분을 위해서 사용방법을 적어 볼까 합니다.

이 정보는 물론 서울 열린 데이터 광장 홈페이지(http://data.seoul.go.kr/)에 다 있는 내용을 일단 제가 필요하다고 생각하는 부분을 정리해 보았습니다.

처음으로는 서울 열린 데이터 광장 홈페이지(http://data.seoul.go.kr/)에 접속하여 서울시 회원가입을 하고 로그인을 하는게 첫번째 순서입니다.

 

서울시가 아니고 전국적인 데이타는 공공데이터포털 (http://www.data.go.kr)도 있습니다.

공공API의 시스템 아키텍쳐는 아래와 같다고 합니다. 물론 개발하는데는 전혀 무관하지만 제가 워낙에 서버쪽 프로그램만 하다보니 이런 그림보면 일단 남 일 같지는 않아서요...(제가 10년넘게 이런 그림 참 많이 그렸기 때문에 ㅎㅎ)

 

공공API를 사용하는 절차는 아래와 같습니다.

 

서울시공공API사용방법

1. 참여소통 > 개발자공간 > 인증키신청 및 확인

2. 이용약관 동의 및 인증키 발급요청

3. 본인의 인증키 확인(로그인 필요)

4. OpenAPI 리스트에서 원하는 API 선택

5. 요청주소 및 요청인자 확인

6. 요청주소로 데이터 요청 후 사용

7. 획득한 데이터를 활용

 

나의 첫 서울시 공공앱을 이제 마켓으로 올릴 수 있을 정도로

완성된 듯 하다.

 

 

1. 사용에 관련된 주요기능

1) GPS가 켜있는 상태에서는 현재 위치 기준으로 가까운 주차장부터 찾아서 정렬

2) 서울시 공영주차장 19개의 주차장의 현재 잔여주차대수를 일괄으로 볼 수 있음

3) 주차장을 클릭하면 위치를 지도로 위치 확인 가능

4) 자세한 내용은 전화번호로 전화하여 문의 가능

2. 아쉬운 점

1) 좀더 많은 데이타(주차요금 등..)이 없음이 좀 아쉽다.

2) 보다 많은 주차장의 정보를 볼 수 있으면 좋으련만 19개의 주차장만 데이타를 제공한다.

3. 사용된 기술적 요소

1) html  request

- 서울시 공공API사용시 와 다음 로컬 API사용시 사용

- 아래는 서울시 공공API사용시 예제

 			  try{
				  URL url = new URL(urls[0]);
				  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()));      
						  for(;;){
							  String line = br.readLine();
							  if(line == null) break;
							  html.append(line+"\n");     
						  }     
						  br.close();
					  }
					  else{
			     
					  }
					  conn.disconnect();
				  }   
			  }
			  catch(Exception ex)
			  {
				  Log.e(TAG,"DownloadHtml ex=["+ex+"]"); 
				  return null;
			  } 

2) AsynTask기능

- 안드로이드 3.0(허니콤)부터는 network을 사용시에는 asyntask를 사용하여 처리가 늦어질 때를 대비하여 화면반응속도를 따로 처리하게 하여 사용자에게 앱이 죽은 듯 보여짐을 방지하고자 한다.

3) xml parser

- 서울공공API와 다음로컬 API시 xml로 받아서 분석하는 로직

		//API 요청 및 반환
		URLConnection conn = url.openConnection();
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document doc = builder.parse(conn.getInputStream());
		
		//channel노드를 객체화 하기
		Node node = doc.getElementsByTagName("channel").item(0);
		for (int i=0 ;i< node.getChildNodes().getLength();i++) {
			Node channelNode = node.getChildNodes().item(i);
			String nodeName = channelNode.getNodeName();
			
			//item 노드들일 경우
			if ("item".equals(nodeName)) 
			{
				//item노드의 자식노드를 검색
				for (int j=0 ;j< channelNode.getChildNodes().getLength();j++) 
				{
					Node itemNode = channelNode.getChildNodes().item(j);
					//title노드 일 경우 출력
					if("lng".equals(itemNode.getNodeName())){
						xy[0]= itemNode.getTextContent();
						
					}else if("lat".equals(itemNode.getNodeName())){
						xy[1]= itemNode.getTextContent();
						
					}	
				}
			}

4) SQLite

- 각 주차장의 데이타를 DB에 저장하는 데 사용함

5) tableview

- scrolling하는 기능의 추가와 헤더와 contents부분의 column사이즈를 맞추는 문제와 click event부여하는 문제 어느 것 하나 쉽지만은 않았음

6) 서울시 공공API 사용

- 서울시는 여러 공공의 데이타(http://data.seoul.go.kr/)를 시민들이 사용하기 편리하도록 공개하고 있음 이 앱은 이 데이타를 기반으로 만들어진 앱임

 7) 다음 API사용

- 다음의 로컬 API와 지도 API사용

		mapView = new MapView(this);
		
		mapView.setDaumMapApiKey("bcc6844e5b1aea2eda0dd7b52860dc30e3b97950");
		mapView.setOpenAPIKeyAuthenticationResultListener(this);
		mapView.setMapViewEventListener(this);
		mapView.setCurrentLocationEventListener(this);
		mapView.setPOIItemEventListener(this);

		mapView.setMapType(MapView.MapType.Standard);
		Log.d(TAG,"[1]dlng:["+ dlng + "],["+dlat +"]");
		// Move and Zoom to
		
		mapView.setMapCenterPointAndZoomLevel(MapPoint.mapPointWithGeoCoord(dlat, dlng),1, true);
		
		MapPOIItem poiItem1 = new MapPOIItem();
		poiItem1.setItemName(sname);
		
		poiItem1.setMapPoint(MapPoint.mapPointWithGeoCoord(dlat, dlng));
		poiItem1.setMarkerType(MapPOIItem.MarkerType.BluePin);
		poiItem1.setShowAnimationType(MapPOIItem.ShowAnimationType.NoAnimation);
		poiItem1.setShowCalloutBalloonOnTouch(true);
		poiItem1.setTag(153);	
		mapView.addPOIItem(poiItem1);
		mapView.fitMapViewAreaToShowAllPOIItems();
		linearLayout.addView(mapView);	

8) Action Bar에  refresh 메뉴 추가

9) intent

- 상세조회시 페이지를 다른 class로 하여 intent를 사용하여 call했으며 전화를 calling시에도 사용

10) 현위치 도출

- GPS를 이용하여 최근저장 위치를 처음에는 불러주고

나중에 이동시마다 10분간격으로 현위치를 저장하고 이에 따른 거리를 계산하도록 함

 

SeoulParking_20130320 (2).zip

+ Recent posts