블로그 이미지
App 개발에 대한 내용을 다룰 예정입니다. AppleSherbet

카테고리

분류 전체보기 (39)
한국프로야구 어플 (18)
세미나 (2)
Java Development (13)
Android App소개 (3)
기타 (2)
당근마켓 (1)
Total296,597
Today27
Yesterday16

최근 폭풍같이 바빠져서 블로깅이 너무 뜸해진 것 같아서 프로야구 어플에 카카오 링크를 적용한 방법을 짧게 공유할까 한다.


카카오 링크를 사용하는 어플이 점점 많아 지고 있는것같고 유용한 기능이기에 (예: 우리은행 계좌 이체후 이체 소식 전달 등) 


프로야구 어플에 어떻게 넣어볼까~ 고민도중 어플을 카톡으로 추천하기 기능을 카카오 링크로 만들기로 했다.


원래 애초에 원하던 기능은 내 어플의 중계방에서 카카오톡 아이디를 쉽게 공유할 수 있게 하는 것이었는데, 해당 API 를 요청해 보았으나 니즈가 없나보다 ㅠㅠ


아무튼 카카오 링크를 적용하는 방법은 간단하다~ ^^ (간단하지 않은게 있으랴)


현재 프로야구의 경우 Utility 메소드를 따로 모아 유틸 클래스를 만들어 놓았다. 우선 그곳에 카카오 링크를 위한 getKakaoLink() 란 메소드를 만들었다.


	private final static String strMessage = "대한민국 베스트 프로야구 어플리케이션 -한국 프로야구-"; 
	private final static String strURL = "http://iam1492.tistory.com";
	private final static String strAppId = "com.pantech.kbov2";
	private final static String strAppName = "프로야구";
	private final static String strInstallUrl = "market://details?id=com.pantech.kbov2"; 
        ...
	public static KakaoLink getKakaoLink(Context context,String version){
		KakaoLink link = null;
		try { 
			ArrayList< Map < String, String > > arrMetaInfo = new ArrayList< Map< String, String > >();

			Map < String, String > metaInfoAndroid = new Hashtable < String, String >(1);
			metaInfoAndroid.put("os", "android");
			metaInfoAndroid.put("devicetype", "phone");
			metaInfoAndroid.put("installurl", strInstallUrl);
			metaInfoAndroid.put("executeurl", "kbo://wooram.com");
			arrMetaInfo.add(metaInfoAndroid);
			link = new KakaoLink(context, strURL, strAppId, version, 
					strMessage, strAppName, arrMetaInfo, "UTF-8");
		}catch (Exception e){
			e.printStackTrace();
		}
		return link;
	}


위와 같이 필요한 정보를 입력하고 카카오에서 제공하는 라이브러리를 사용하여 KakaoLink 객체를 만들어 리턴하는 간단한 메소드를 만들었다. 


OS, devicetype, 마켓주소 등을 사용하는 어플에 맞게 정해준다. 


여기서 중요한 항목중 하나가 metaInfoAndroid.put("executeurl", "kbo://wooram.com"); 이부분이다. 


나중에 intent filter에서 해당 데이터를 받게 될 것이므로 format에 잘 맞춰줘야 한다.


카카오 링크를 이용해 링크를 보냈는데 만약 링크를 받은사람이 프로야구를 가지고 있지 않다면 마켓으로 연결이 된다. 하지만 프로야구가 이미 있다면?? 그때 사용되는 항목이 executeurl 이다

각자 어플에서 구현하기 나름이지만 나같은 경우 그냥 Main activity를 띄워주기로 했다. 즉, 이미 깔려있으면 그냥 어플을 정상적으로 실행 시켜주게된다. 그부분은 곧! 설명하겠다.


그 전에 위에 만들어 놓은 getKakaoLink 메소드를 이용하는 부분부터 보자.




              Button btn_katok = (Button)findViewById(R.id.btn_katok);
		btn_katok.setOnClickListener(new View.OnClickListener() {			
			@Override
			public void onClick(View arg0) {
				KakaoLink link = GeneratorUtil.getKakaoLink(AboutAct.this, versionName);
				if (link.isAvailable()){
					startActivity(link.getIntent());
				}
			}
		});

프로야구 어플의 경우 About 페이지에 카톡으로 어플 추천하기 라는 버튼을 만들었다. 누르면 단순히 getKakaoLink()메소드를 이용하여 전달받은 KakaoLink 객체에서 getIntent()를 호출하여 startActivity()에 넘겨주면 된다. 


여기까지 해주면 누르는 동시에 카카오톡의 친구 선택 화면으로 넘어간다. 친구를 선택하면 다음과 같은 동작을 하게 된다.


만약 ( 내 친구가 프로야구를 가지고 있지 않다면 ) { 

    안드로이드 마켓의 프로야구 어플 다운로드 페이지로 연결;

} 그게 아니고 만약 (프로야구를 가지고 있다면) {

     그냥 프로야구 실행 (아직 덜 구현됨.....아래에서 구현해보자);

}


자, 마지막으로 프로야구가 이미 깔려있을때 보내는 Intent 의 action을 받기 위해 Menifest 파일에 아래처럼 intent filter를 추가하자.


        
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="wooram.com"
                    android:scheme="kbo" />
            </intent-filter>

원하는 액티비티에 필터를 위처럼 추가를 해주면 metaInfoAndroid.put("executeurl", "kbo://wooram.com"); 에서 명시해 줬던 executeurl 값때문에 프로야구 어플이 깔려있는 경우 알아서 어플을 실행해준다. 나의 경우 당연히 MenuAct 가 가장 처음 실행되는 클래스이므로 로고 화면이 나오며 프로야구가 실행된다. (구현하기 나름)

여기 까지 해주면 이제 "카톡으로 어플 추천하기" 버튼을 누르면 내 어플을 내 카톡 친구에게 추천할 수 있게 된다. ^^ 카카오링크~ 어렵지 않아요~~





저작자 표시
신고
Posted by AppleSherbet


  안드로이드 리스트뷰는 가장 많이 쓰이는 인기 View 중 하나이다. 실제 프로젝트에 적용할때는 기본 리스트뷰를 사용하는 경우는 거의 없고 대부분의 경우 Custom View를 만들어서 사용하게 된다. 이때 이미지나 텍스트 등 한개의 리스트 아이템에 레이아웃이 복잡한 경우 리스트뷰를 스크롤할때 뚝뚝 끊기는 것을 볼 수 있다. 
  이것은 리스트 뷰가 메모리를 효율적으로 관리하기 위해 화면상에 보이지 않는 리스트 아이템을 재활용 하기 때문인데 자세한 내용은 생략하고(궁금하면 developer site에서 한번 읽어보자) 이렇게 뷰를 재활용하면서 UI를 갱신하면서 성능 저하가 발생 되는 것이다.
  대체적으로 이것을 해결하는 일반적인 방법은 xml 리소스에 접근하는 횟수를 최소화 하는 것이다. 그래서 대부분의 경우 ViewHolder 패턴을 적용한다. 패턴이라 하기엔 상당히 간단한 방법인데 예제를 보면서 확인해보자.

[View Holder 적용 전]
@Override
public View getView(int position, View convertView, ViewGroup parent) {

    View v = convertView;
    if(v == null) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        v = inflater.inflate(R.layout.layout_list_item, null);
    }
    TextView txtName = (TextView)v.findViewById(R.id.txtName);
    TextView txtMail = (TextView)v.findViewById(R.id.txtMail);

    Contact entry = mList.get(position);
    txtName.setText(entry.getName());
    txtMail.setText(entry.getMail());

    return v;
}

ViewHolder 를 적용하기 전에는 항상 xml 리소스에 접근하게 되며 성능 저하의 원인이 된다.


[View Holder 적용 후] 

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if(v == null) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        v = inflater.inflate(R.layout.layout_list_item, null);
        ViewHolder holder = new ViewHolder();
        holder.txtName = (TextView)v.findViewById(R.id.txtName);
        holder.txtMail = (TextView)v.findViewById(R.id.txtMail);
        v.setTag(holder);
    }
    Contact entry = mList.get(position);
    if(entry != null) {
        ViewHolder holder = (ViewHolder)v.getTag();
        holder.txtName.setText(entry.getName());
        holder.txtMail.setText(entry.getMail());
    }
    return v;
}

static class ViewHolder {
    TextView txtName;
    TextView txtMail;
}


이런식으로 ViewHolder 클래스를 만들고 처음 한번만 xml 리소스의 접근할 경우 getView 가 불릴때 마다 findViewByID 를 할 필요가 없어져 성능이 좋아진다.

 (위 예제의 경우 성능 차이를 느낄 수 없겠지만 실제로 복잡한 레이아웃의 경우 체감 성능에 많은 개선이 있다.)


그런데 얼마전 회사 동기가 View Holer 는 죽었다며 한 블로그 포스팅을 공유해줬다. 포스팅 제목은: View Holder is dead. Long live setTag 였다. 


무슨 내용인가 유심히 읽어 보니 안드로이드에서 1.6이후에 setTag(int key, Object tag) 라는 메소드를 지원해준다는 것이다.즉, 따로 ViewHolder 클래스를 만들 필요가 없다는 것이다. 바로 적용해 보았다. 


[ setTag(int key, Object tag) 메소드 적용 후]

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if(v == null) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        v = inflater.inflate(R.layout.layout_list_item, null);
        //View Holder 에 넣었던 부분을 setTag(key,tag) 메소드로 대체한다.
        v.setTag(R.id.txtName, v.findViewById(R.id.txtName));
        v.setTag(R.id.txtMail, v.findViewById(R.id.txtMail));
    }
    Contact entry = mList.get(position);
    if(entry != null) {
        TextView txtName= (TextView)v.getTag(R.id.txtNam);
        TextView txtMail= (TextView)v.getTag(R.id.txtMail);
        txtName.setText(entry.getName());
        txtMail.setText(entry.getMail());
    }
    return v;
}

성능 차이는 크게 나지 않는다~ 

결과적으로 View Holder is dead란 제목은 좀 적절해 보이지는 않으나, 이런것도 있구나 하는 걸 새로 알게됐다.(왜 그동안 이 메소드가 안보였는지...)


혹시 아직도 getView 혹은 bindView시에 항상 xml 리소스에 접근해 리소스 객체를 가져오고 있다면, ViewHolder 나 setTag(int key, Object tag) 둘중 하나의 방법으로 소스를 고쳐보자. 훨씬 부드럽게 스크롤되는 알흠다운 모습을 볼 수 있을 것이다.



저작자 표시
신고
Posted by AppleSherbet
안드로이드 개발을 하다보면 다양한 폰들의 해상도에 맞게 UI를 구성하는데 많은 어려움을 느낀다.
결과적으론 최대한 리소스들을 xml 로 정의하고 해상도에 맞게 폴더를 나누어 주어야하는데
가끔 그게 어려울때가 있다. 즉, 동적으로 리소스를 불러 사용하는 경우가 그런 경우이다.
그 중  text size에 관련해서 dimens.xml 파일에서 리소스를 가져와 setTextSize를 해주는데 Text가 의도한거보다 훨씬 크게 나오는 문제가 있어서 
오늘도 여느때처럼 폭풍 구글링을 하였다. ㅋㅋ TextSize의 경우 다음처럼 처리해주면 된다.

value-xhdpi/dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--  Record table row size -->
    <dimen name="record_row_height">24dp</dimen>
    <dimen name="record_default_text">15dip</dimen>
</resources>

이렇게 고해상도용 리소스를 정의한다. 물론 value-hdpi 폴더에도 같은 dimens.xml을 정의해줘야한다.
이렇게 하고 소스상에서 해당리소스를 사용할땐
tv_w1.setTextSize(TypedValue.COMPLEX_UNIT_PX, res.getDimension(R.dimen.record_default_text));
이렇게 현재 세팅하는 리소스가 pixel이라고 명시해줘야한다. 왜그럴까? 
이것때문에 계속 해깔려서 글씨 크기가 크게 나오는 문제가 있었다. 문제는  getDimension() 메소듣가 pixel 형태로 리턴하기 때문이다.
우리가 dimens.xml에 dip라고 명시해 줬기 때문에 getDimension()은 알아서 pixel로 변환해서 리턴해준다.
그런데 setTextSize 메소드는 기본 단위가 pixel이 아닌 sp 라서 문제가 되는것이다.
아무튼 이렇게 하니 정상적으로 글자크기가 나왔다.. 별거 아닌거였는데...ㅋㅋ

 
저작자 표시
신고
Posted by AppleSherbet
TableLayout 을 다루다 보니

뜬금없이  arithmeticException: divide by zero 라는 익셉션이 뜨면서 어플이 죽는 문제가 생겼다.

원인을 찾아보니 android:stretchColumns="*" 라는 코드를 사용하는데 해당 속성을 사용할때

내부적으로 칼럼수로 나누는 부분이 있나보다~ 아무튼 여기서 0으로 나누는 연산을 하면서 exception이 나온다.

원인은 알았는데 또 해결책을 무한 검색해보니 동적으로 TableLayout을 생성하면서 문제가 됐다.

결과적으로 아래와 같이


TableRow tableRow = new TableRow(this);
        	TableLayout.LayoutParams trParams = new TableLayout.LayoutParams(TableLayout.LayoutParams.FILL_PARENT, TableLayout.LayoutParams.WRAP_CONTENT);
        	tableRow.setBaselineAligned(false);
        	trParams.setMargins(0, 10, 0, 0);
        	tableRow.setLayoutParams(trParams);
TableLayout.LayoutParams 을 사용하던 코드를 TableRow.LayoutParams 으로 변경하니 깔끔하게 해결됐다.
TableRow tableRow = new TableRow(this);
        	TableRow.LayoutParams trParams = new TableRow.LayoutParams(TableRow.LayoutParams.FILL_PARENT, TableRow.LayoutParams.WRAP_CONTENT);
        	tableRow.setBaselineAligned(false);
        	trParams.setMargins(0, 10, 0, 0);
        	tableRow.setLayoutParams(trParams);
이유는???????
저작자 표시
신고
Posted by AppleSherbet
안드로이드 어플을 해보면 대부분의 어플이 3G혹은 WIFI망을 통해 데이터 통신을 하게된다. 현재 개발중인 앱또한 마찬가지여서 네트워크를 쓰는 엑티비티가 생성되기 전에 망 사용 가능 여부를 체크하는 코드를 삽입하였다.
다른 사이트에서 참고를 했었는데 어디였는지 기억이 안난다..;;

기본적으로 플랫폼에서 모든 API를 편하게 제공하기때문에 쉽게 구현할 수 있다.

1.ConnectivityManager m_NetConnectMgr= (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
- 위의 코드는 시스템으로 부터 서비스를 가져온다.

2. NetworkInfo info = m_NetConnectMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- 가져온 서비스를 이용해 WIFI 관련 정보를 가져온다.

3. info.isAvailable() / info.isConnected()
- 위와 같은 메소드를 이용해 현재 망 상태를 점검한다.

이상의 3가지 정도가 기본이 된다.

소스코드


다음은 Network 정보를 체크하는 코드로 static으로 만들었다.
public class KBONetworkInfo {

	public static boolean IsWifiAvailable(Context context)
	{
		ConnectivityManager m_NetConnectMgr= (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
		boolean bConnect = false;
		try
		{
			if( m_NetConnectMgr == null ) return false;

			NetworkInfo info = m_NetConnectMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
			bConnect = (info.isAvailable() && info.isConnected());

		}
		catch(Exception e)
		{
			return false;
		}

		return bConnect;
	}

	public static boolean Is3GAvailable(Context context)
	{  
		ConnectivityManager m_NetConnectMgr= (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
		boolean bConnect = false;
		try
		{
			if( m_NetConnectMgr == null ) return false;
			NetworkInfo info = m_NetConnectMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
			bConnect = (info.isAvailable() && info.isConnected());
		}
		catch(Exception e)
		{
			return false;
		}

		return bConnect;
	}
}
엑티비티의 onCreate 안에서 간단히 체크해준다.
  
if (!KBONetworkInfo.IsWifiAvailable(this) && !KBONetworkInfo.Is3GAvailable(this))
{
	Toast.makeText(this, "네크워크에 연결할 수 없습니다.", Toast.LENGTH_LONG).show();
	return;
}

마지막으로 네트워크 상태를 접근하기 위한 권한을 추가해 주어야 한다.
  
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
신고
Posted by AppleSherbet
안드로이드 개발을 하다보니 네트워크를 이용하여 데이터를 받아오는 코드가 많아졌다.
Wifi가 연결되어 있을땐 속도가 빨라서 괜찮은데 3G망...
특히 지하철 같은곳에서 테스트를 해보니 상당히 느려서 유저는 어플이 죽은거라고
생각 할 수도 있다.

그래서 ProgressBar를 구현하게 되었다.

ProgressBar구현은 간단하다..
추가적인 쓰레드를 만들어서 ProgressBar를 돌리는 동시에 네트워크로 부터 데이터를 받아오면 된다.
데이터를 모두 받아오면 ProgressBar를 종료시키고 받아온 데이터를 이용해 View를 갱신한다.

     //프로그래스 바 시작
        loagindDialog = ProgressDialog.show(this, "로딩중",
                "Loding...please wait", true, false);
        //쓰레드 시작: 네트워크로 부터 데이터 받아옴
        thread = new Thread(new Runnable() {
            public void run(){
                try {
                    source = new Source(new URL("http://sports.media.daum.net/baseball/kbo/record/main.daum"));
                } catch (MalformedURLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                source.fullSequentialParse();
                avgList = getRecordArr(source, REC_TYPE_AVG);
                hitList = getRecordArr(source, REC_TYPE_HIT);
                hrList = getRecordArr(source, REC_TYPE_HR);
                rbiList = getRecordArr(source, REC_TYPE_RBI);
                eraList = getRecordArr(source, REC_TYPE_ERA);
                soList = getRecordArr(source, REC_TYPE_SO);
                svList = getRecordArr(source, REC_TYPE_SV);
                wList = getRecordArr(source, REC_TYPE_W);
    //핸들러에게 종료 메세지를 보내줌
                handler.sendEmptyMessage(0);
            }
        });
        thread.start();
 
     // 핸들러는 프로그래스 바를 종료하고 데이터를 View에 채운다.
     private Handler handler = new Handler() {
            public void handleMessage(Message msg) {
                loagindDialog.dismiss();
                //받아온 데이터를 이용해 뷰 갱신.
                fillRecordData("타자(타율)", avgList);
                fillRecordData("타자(타점)", hitList);
                fillRecordData("타자(홈런)", hrList);
                fillRecordData("타자(안타)", rbiList);
               
                TextView tv = new TextView(RecordAct.this);
                tv.setText("투수 부분");
                tv.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 35));
                tv.setBackgroundDrawable(RecordAct.this.getResources().getDrawable(R.drawable.title_shape));
                tv.setTextSize(15.0f);
                tv.setTextColor(Color.BLACK);
                tv.setGravity(Gravity.CENTER);
               
                TableLayout tl = (TableLayout)findViewById(R.id.record_table);
                tl.addView(tv);
               
                fillRecordData("투수(방어율)", eraList);
                fillRecordData("투수(탈삼진)", soList);
                fillRecordData("투수(세이브)", svList);
                fillRecordData("투수(다승)", wList);
            }
        };
신고
Posted by AppleSherbet
-- 발췌 (http://jkproject.net/5)

지난번에 만들었던 갤러리 앱의 경우 res/drawable 디렉토리에 사용하는 모든 이미지를 넣어두었는데 이러다보니 앱 자체의 사이즈가 너무 커지는 단점이 있다. 그래서 외부 웹에서 이미지를 가져오도록 수정하였다.

우선 앱에서 인터넷에 접속할 수 있는 권한을 AndroidManifest.xml 에 주어야 한다. 아래와 같이 uses-permission을 사용하여 INTERNET 권한을 주면 된다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"     package="com.example.hellogallery"
 android:versionCode="1"
 android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="@drawable/icon" android:label="@string/app_name">

다음으로는 ImageAdapter class에서 사용했던 mImageIds 대신에 mRemoteImages를 다음과 같이 만든다. 여기엔 web상에 있는 이미지의 주소를 나열해주면 된다
private String[] mRemoteImages = {   
"http://www.examples.com/a.jpg", "http://www.examples.com/b.jpg", "http://www.examples.com/c.jpg", "http://www.examples.com/d.jpg", "http://www.examples.com/e.jpg" };

그 후엔 mImageIds를 사용하던 부분을 mRemoteImages를 사용해 구현하면 된다.
i.setImageResource(mImageIds[position]);
위의 것을 다음과 같이 수정한다.
try {
  URL aURL = new URL(mRemoteImages[position]);
  URLConnection conn = aURL.openConnection();
  conn.connect();
  InputStream is = conn.getInputStream();
  BufferedInputStream bis = new BufferedInputStream(is);
  Bitmap bm = BitmapFactory.decodeStream(bis);
  bis.close();
  is.close();
  i.setImageBitmap(bm);
} catch (IOException e) {
  i.setImageResource(R.drawable.file_not_found);
  Log.e("DEBUGTAG", "Remote Image Exception", e);
}

R.drawable.file_not_found 는 혹시 웹에서 이미지를 불러오지 못할 경우 화면에 띄워줄 수 있는 이미지를 res/drawable에 넣어주면 되겠다.

여기서 더 추가해볼 만한 것은 웹상의 xml파일을 읽어들여 어플에서 사용할 이미지에 대한 정보 및 이미지를 가져오는 것이다. 이렇게 되면 어플을 수정할 필요없이 웹상의 xml파일을 수정하는 것만으로 앱 내의 이미지 업데이트가 가능해지므로 좀더 유연한 어플이 될 수 있다.

참고한 사이트는 다음과 같다.
http://developer.android.com/guide/topics/security/security.html
http://www.anddev.org/gallery_with_remote_images-t769.htm
신고
Posted by AppleSherbet

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

티스토리 툴바