블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 


안드로이드 채팅 UI 만들기 #2 


나인패치 이미지를 이용한 채팅 버블


조대협 (http://bcho.tistory.com)


지난 글에서는 ListView를 이용하여 스크롤이 가능한 텍스트 기반의 간단한 채팅창을 만들어보았다.

이번글에는 채팅 메세지에 이미지로 채팅 버블을 입히는 방법을 알아보도록 한다.


채팅 버블 이미지를 입히는 방법이나 원리는 간단한데, 채팅 메세지를 출력하는 TextView에 백그라운드이미지를 입혀서 출력하면 된다. 그런데 여기서 문제가 생기는데, 채팅 메세지 버블의 크기는 메세지의 글자수에 따라 가변적으로 변경되기 때문에, 일반적인 이미지를 백그라운드로 넣어서 가로로 늘이거나 줄이게 되면 채팅창이 이상하게 가로로 늘어날 수 가 있다.. (아래 그림에서 가로로 늘렸을때 말꼬리 부분 삼각형이 원본과 다르게 늘어난것을 확인할 수 있다) 



< 원본 이미지 > 



<가로로 늘린 이미지 >



그래서 필요한 것이, 특정 부분만 늘어나게 하는 것이 필요한데. 이렇게 크기가 변경되어도 특정 구역만 늘어나게 하는 이미지를 나인패치 이미지 (9-patch image라고 한다.). 나인패치 이미지를 이용하여 말풍선을 느리게 되면, 말꼬리 부분은 늘어나지 않고 텍스트가 들어가는 영역만 늘어난다. 




< 나인패치 이미지를 가로로 늘린 경우> 


나인패치 이미지 만들기


나인 패치 이미지는 안드로이드 SDK에 내장된 draw9patch라는 도구를 이용해서 만들 수 있다.

보통 안드로이드 SDK 가 설치된 디렉토리인 ~/sdk/tools 아래 draw9patch라는 이름으로 저장되어 있다.

실행하면 아래와 같은 화면이 뜨는데, 

좌측은 일반 이미지를 나인패치 이미지로 만들기 위해서 늘어나는 영역을 지정하는 부분이고 (작업영역), 우측은 가로, 세로등으로 늘렸을때의 예상 화면 (프리뷰)을 보여주는 화면이다.




그러면 9 patch  이미지는 어떻게 정의가 될까? draw9patch에서 가이드 선을 드래그해서 상하좌우 4면에, 가이드 선을 지정할 수 있다.



좌측은 세로로 늘어나는 영역, 상단을 가로로 늘어나는 영역을 정의하고, 우측은 세로로 늘어났을때 늘어나는 부분에 채워지는 이미지를, 하단은 가로로 늘어났을때 채워지는 이미지 영역을 지정한다. 

이렇게 정의된 나인 패치 이미지는 어떻게 사용하는가? 일반 이미지 처럼 사용하면 되고, 크기를 조정하면 앞서 정의한데로, 늘어나는 부분만 늘어나게 된다.


나인패치 이미지를 채팅 메세지에 적용하기 


그러면 이 나인패치이미지를 앞에서 만든 채팅 리스트 UI에 적용하여 말 풍선을 만들어보도록 하자.

여기서는 채팅 버블을 좌측 우측용 양쪽으로 만들도록 하고, 서버에 연결된 테스트용이기 때문에, 메세지를 입력하면 무조건 좌/우 버블로 번갈아 가면서 출력하도록 한다.


앞의 코드 (http://bcho.tistory.com/1058) 에서 별도로 바위는 부분은 ChatMessageAdapter의 getView 메서드만 아래와 같이 수정하고 채팅 버블로 사용할 이미지를 ~/res/drawable/ 디렉토리 아래 저장해 놓으면 된다. 




그러면 수정된 getView 메서드를 살펴보도록 하자.


   @Override

    public View getView(int position, View convertView, ViewGroup parent) {

        View row = convertView;

        if (row == null) {

            // inflator를 생성하여, chatting_message.xml을 읽어서 View객체로 생성한다.

            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            row = inflater.inflate(R.layout.chatting_message, parent, false);

        }


        // Array List에 들어 있는 채팅 문자열을 읽어

        ChatMessage msg = (ChatMessage) msgs.get(position);


        // Inflater를 이용해서 생성한 View에, ChatMessage를 삽입한다.

        TextView msgText = (TextView) row.findViewById(R.id.chatmessage);

        msgText.setText(msg.getMessage());

        msgText.setTextColor(Color.parseColor("#000000"));


        // 9 패치 이미지로 채팅 버블을 출력

        msgText.setBackground(this.getContext().getResources().getDrawable( (message_left ? R.drawable.bubble_b : R.drawable.bubble_a )));


        // 메세지를 번갈아 가면서 좌측,우측으로 출력

        LinearLayout chatMessageContainer = (LinearLayout)row.findViewById(R.id.chatmessage_container);

        int align;

        if(message_left) {

            align = Gravity.LEFT;

            message_left = false;

        }else{

            align = Gravity.RIGHT;

            message_left=true;

        }

        chatMessageContainer.setGravity(align);

        return row;


    }


수정 내용은 간단한데, 좌측/우측용 채팅 버블을 출력하는 부분과, 좌측 버블은 좌측 정렬을, 우측 버블은 우측 정렬을 하는 내용이다.


채팅 버블을 적용하는 방법은 TextView에서 간단하게 setBackground 메서드를 이용하여 백그라운드 이미지를 나인패치 이미지를 적용하면 된다.


msgText.setBackground(this.getContext().getResources().getDrawable( (message_left ? R.drawable.bubble_b : R.drawable.bubble_a )));


나인패치이미지가  resource아래 drawable 아래 저장되어 있기 때문에, getResource().getDrawable() 메서드를  이용하여 로딩 한다.


        LinearLayout chatMessageContainer = (LinearLayout)row.findViewById(R.id.chatmessage_container);

        int align;

        if(message_left) {

            align = Gravity.LEFT;

            message_left = false;

        }else{

            align = Gravity.RIGHT;

            message_left=true;

        }

        chatMessageContainer.setGravity(align);


다음으로는 채팅 버블을 번갈아 가면서 좌/우측에 위치 시켜야 하는데, 채팅 버블의 위치는 채팅 메세지를 담고 있는 LinearLayout을 가지고 온후에, LinearLayout의 Gravity를 좌우로 설정하면 된다.


나인패치 이미지를 이용한 완성된 채팅 버블 UI는 다음과 같다. 






저작자 표시 비영리
신고

안드로이드에서 ListView를 이용한 채팅 UI 만들기


조대협 (http://bcho.tistory.com)


안드로이드 프로그래밍 기본 개념이 어느정도 잡혀가기 시작하니, 몬가 만들어봐야겠다는 생각이 들어서 생각하던중에 결론 낸것이, 간단한 채팅 서비스, 기존에 node.js 하면서 웹용 채팅을 만들어보기도 했고, 찾아보니, 안드로이드용 SocketIO 라이브러리도 잘되어 있어서 서버 연계도 어려울것이 없을것 같고, 또한 메세지가 왔을때 푸쉬 알림을 써야 하는 등 이것저것 실습이 될것 같아서, 결국은 채팅으로 정했다.


서버나 연계 코드 구현보다, 가장 어려운게 역시나 UI 디자인과 프로그래밍인데, 가장 쉬운 방법으로는 ListView를 사용하는 방법이 무난하다. (결국 코딩을 하고 나니 여러가지 한계를 느껴서 다른  UI를 찾으려고 하고는 있지만)


궁극적으로 만들고자 하는 UI는 카카오톡 처럼 말풍선이 나오는 UI이다. 





말풍선은 Ninepatch (나인패치) 이미지 라는 것으로 만들 수 가 있는데, 나인 패치 이미지에 대해서는 나중에 알아보도록 하고, 여기서는 말풍선 없이, 화면에 스크롤되서 내려가는 텍스트 기반의 대화창을 만드는 것을 먼저 진행하도록 한다.  스크롤이 되는 채팅창은 ListView 컴포넌트를 사용해서 구현하는데, 아래 그림과 같이 지난 메세지는 화면에 나오지 않고 현재 대화되는 상황만 보이도록 한다. 





리스트뷰(ListView) 에 대해서


그렇다면 리스트뷰는 무엇인가? 리스트뷰는 안드로이드 뷰 그룹 (View Group)의 일종으로, 스크롤이 가능한 아이템들의 리스트를 출력해주는 그룹이다.



아답터(Adaptor) 의 개념


채팅용 리스트뷰를 만들기 위해서는 아답터(Adaptor)의 개념을 이해해야 하는데, 아답터는 크게 두가지 기능을 한다. 리스트뷰에서 보여질 아이템들을 저장하는 데이타 저장소의 역할과, 리스트뷰안에 아이템이 그려질때 이를 렌더링하는 역할을 한다.


add 메서드를 이용하여, 아이템을 추가하고

getItem(int index)를 이용하여, index  번째의 아이템을 리턴하며 (자바의 일반적인 List형과 유사하다)

View getView(int position, xxx )이 중요한데, position 번째의 아이템을 화면에 출력할때 렌더링하는 역할을 한다.


그러면 실제로 작동하는 코드를 만들어보자. 이 예제에서는 텍스트를 입력하면 리스트 뷰에 추가되고, 텍스트가 입력됨에 따라 쭈욱 아래로 리스트뷰가 자동으로 스크롤되는 예제이다. 





아답터 클래스 구현


제일 먼저 채팅 메세지 리스트를 저장할 아답터 클래스를 구현해보자


package com.example.terry.simplelistview;


import android.content.Context;

import android.graphics.Color;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.ArrayAdapter;

import android.widget.TextView;


import java.util.ArrayList;

import java.util.List;


/**

 * Created by terry on 2015. 10. 7..

 */

public class ChatMessageAdapter extends ArrayAdapter {


    List msgs = new ArrayList();


    public ChatMessageAdapter(Context context, int textViewResourceId) {

        super(context, textViewResourceId);

    }


    //@Override

    public void add(ChatMessage object){

        msgs.add(object);

        super.add(object);

    }


    @Override

    public int getCount() {

        return msgs.size();

    }


    @Override

    public ChatMessage getItem(int index) {

        return (ChatMessage) msgs.get(index);

    }


    @Override

    public View getView(int position, View convertView, ViewGroup parent) {

        View row = convertView;

        if (row == null) {

            // inflator를 생성하여, chatting_message.xml을 읽어서 View객체로 생성한다.

            LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            row = inflater.inflate(R.layout.chatting_message, parent, false);

        }


        // Array List에 들어 있는 채팅 문자열을 읽어

        ChatMessage msg = (ChatMessage) msgs.get(position);


        // Inflater를 이용해서 생성한 View에, ChatMessage를 삽입한다.

        TextView msgText = (TextView) row.findViewById(R.id.chatmessage);

        msgText.setText(msg.getMessage());

        msgText.setTextColor(Color.parseColor("#000000"));


        return row;


    }

}


add나 getItem,getCount등은 메세지를 Java List에 저장하고, n 번째 메세지를 리턴하거나 전체 크기를 저장하는 방식으로 구현한다.


가장 중요한 부분은 getView 메서드인데,리스트의 n 번째 아이템에 대한 내용을 화면에 출력하는 역할을 하며 여기서는 두 단계를 거쳐서 렌더링을 진행한다.


첫번째로는 인플레이터 (inflator)를 사용하여, 아이템을 렌더링할 View 컴포넌트를 ~/layout/XX.XML 에서 읽어오는 역할을 한다.

인플레이터에 대해서 간략하게 짚고 넘어가면, View등 화면등을 디자인할 때 안드로이드에서는 XML 을 사용하여 쉽게 View를 정의할 수 있다. 이렇게 정의된 XML을 실제 View 자바 Object로 생성을 해주는 것이 인플레이터(inflator)이다. 


    LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 


를 통해서 인플레이터를 생성하고, 이 인플레이터를 통해서 각 아이템을 출력해줄 View를 생성하여 row라는 변수에 저장한다.


 row = inflater.inflate(R.layout.chatting_message, parent, false);


이때, 레이아웃을 정의한 XML 파일은 chatting_message.xml 로 안드로이드 프로젝트의 ~/layout 디렉토리 아래에 있으며, 인플레이트 할때는 R.layout.chatting_message라는 이름으로 지칭한다.


chatting_message.xml 의 내용은 다음과 같다.


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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical" android:layout_width="match_parent"

    android:layout_height="match_parent">


    <TextView

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:textAppearance="?android:attr/textAppearanceMedium"

        android:text="Chat message"

        android:id="@+id/chatmessage"

        android:gravity="left|center_vertical|center_horizontal"

        android:layout_marginLeft="20dp" />

</LinearLayout>



이렇게 아이템을 표시할 View가 생성되었으면, 그 안에 알맹이를 채워넣어야 하는데, 채팅 메세지를 저장하는 List 객체에서, position 번째의 채팅 메세지를 읽어온 후에, row 뷰 안에 있는 TextView에 그 채팅 메세지를 채워 넣는다. 


getView 코드에서 주의해서 봐야할 부분이


  View row = convertView;

        if (row == null) { …


인데, 가만히 보면 row가 null 일 경우에만 인플레이터를 이용해서 row를 생성하는 것을 볼 수 있다.


ListView의 특징중 하나는, 아이템을 랜더링 하는 View 객체가 매번 생성되는 것이 아니라, 해당 아이템에 대해서 이미 생성되어 있는 View가 있다면, getView( .. ,View convertView, ..) 를 통해서 인자로 전달된다.


그래서, convertView가 null 인지를 체크하고, 만약에 null이 아닌 경우에만 View를 생성한다. 


여기까지가 채팅 메세지를 저장하고, 각 메세지를 렌더링 해주는 ListView 용 아답터의 구현이었다. 

그러면 아답터를 이용한 ListView를 사용하여 채팅 메세지를 출력하는 부분을 구현해보자


아래는 ListView를 안고 있는 MainActivity이다.


package com.example.terry.simplelistview;


import android.database.DataSetObserver;

import android.support.v7.app.ActionBarActivity;

import android.os.Bundle;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.widget.AbsListView;

import android.widget.EditText;

import android.widget.ListView;



public class MainActivity extends ActionBarActivity {

    ChatMessageAdapter chatMessageAdapter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }


    @Override

    public boolean onCreateOptionsMenu(Menu menu) {


        // Inflate the menu; this adds items to the action bar if it is present.

        getMenuInflater().inflate(R.menu.menu_main, menu);


        chatMessageAdapter = new ChatMessageAdapter(this.getApplicationContext(),R.layout.chatting_message);

        final ListView listView = (ListView)findViewById(R.id.listView);

        listView.setAdapter(chatMessageAdapter);

        listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); // 이게 필수


        // When message is added, it makes listview to scroll last message

        chatMessageAdapter.registerDataSetObserver(new DataSetObserver() {

            @Override

            public void onChanged() {

                super.onChanged();

                listView.setSelection(chatMessageAdapter.getCount()-1);

            }

        });

        return true;

    }


    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        // Handle action bar item clicks here. The action bar will

        // automatically handle clicks on the Home/Up button, so long

        // as you specify a parent activity in AndroidManifest.xml.

        int id = item.getItemId();


        //noinspection SimplifiableIfStatement

        if (id == R.id.action_settings) {

            return true;

        }


        return super.onOptionsItemSelected(item);

    }


    public void send(View view){

        EditText etMsg = (EditText)findViewById(R.id.etMessage);

        String strMsg = (String)etMsg.getText().toString();

        chatMessageAdapter.add(new ChatMessage(strMsg));

    }

}


먼저 send는 “SEND” 버튼을 눌렀을때, 화면상에서 채팅 메세지를 읽어드려서 Adapter에 저장하는 역할을 한다.

가장 중요한 메서드는  onCreateOptionsMenu인데, 이 메서드의 주요 내용은, Adapter를 생성하여, Adapter를 listView에 바인딩한다.


  chatMessageAdapter = new ChatMessageAdapter(this.getApplicationContext(),R.layout.chatting_message);

        final ListView listView = (ListView)findViewById(R.id.listView);

        listView.setAdapter(chatMessageAdapter);


다음 기능으로는, 새로운 아이템이 listView에 추가되었을때, 맨 아래로 화면을 스크롤 하는 기능인데, 이는 Adapter에 DataObserver를 바인딩해서, 데이타가 바뀌었을때 즉 채팅 메세지가 추가되었을때, listView에서 리스트업 되는 아이템 목록을 맨 아래로 이동 시킨다.


        // When message is added, it makes listview to scroll last message

        chatMessageAdapter.registerDataSetObserver(new DataSetObserver() {

            @Override

            public void onChanged() {

                super.onChanged();

                listView.setSelection(chatMessageAdapter.getCount()-1);

            }

        });


이렇게 DataObserver를 추가하더라도, 아래로 스크롤이 안되는데, 새로운 아이템이 리스트뷰에 추가되었을 때 스크롤을 하게 해줄려면 TranscriptMode에 새로운 아이템이 추가되었을때 스크롤이 되도록 설정을 해줘야 한다. (디폴트는 새로운 아이템이 추가되더라도 스크롤이 되지 않는 옵션으로 설정되어 있다.)


listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); // 이게 필수



간단하게, ListView를 이용한 채팅 UI를 만들어봤다. 다음에는 나인패치 이미지를 이용하여, 말풍선을 넣는 기능을 추가해보도록 한다.


참고 : http://javapapers.com/android/android-chat-bubble/



저작자 표시 비영리
신고

안드로이드 Fragement 이해하기


조대협 (http://bcho.tistory.com)


안드로이드에서 하나의 화면은 하나의 Activity로 대변된다.

이러한 Activity를 View등을 이용해서 여러개로 쪼갤 수 있지만, 타블렛이나 팬블릿과 같이 스마트 폰과 다른 해상도를 사용할때 일일이 View를 다시 디자인 해야 하며, 또한 해당 화면 레이아웃이나 배치등을 다른 Activity에서 사용하고자 하면 매번 다시 만들어야 한다.





<출처 : http://developer.android.com/guide/components/fragments.html >


왼쪽은 타블렛 화면 레이아웃으로, 좌측에 메뉴와 오른쪽에 다른 형태의 컨텐츠가 나타난다.

이를 스마트 폰에서 볼때는 화면이 작기 때문에, 한 화면에서는 메뉴를 출력하고, 메뉴를 클릭하면 컨텐츠 화면으로 이동하도록 하는 형태인데, 이를 그냥 각각 구현하려면, 각 Activity에서 중복된 코드를 일일이 개발해야 한다. 

만약에 이러한 메뉴와 컨텐츠 화면이 포터블한 컴포넌트 형태로 묶이게 된다면 이를 재 사용해서 쉽게 만들 수 있다. 즉 타블릿 화면에는 메뉴와 컨텐츠 컴포넌트를 출력하고, 스마트 폰에는 메뉴 화면에는 메뉴 컴포넌트를, 컨텐츠 화면에는 컨텐츠 컴포넌트를 출력하면 쉽게 개발이 가능하다.


그래서 Activity안에 다른 Activity를 구현하는 것과 같은 형태의 컴포넌트를 제공하는 데 이를  Fragment라고 한다.


Fragment를 사용하는 방법은 단순하다. Activity의 Layout XML 안에, <fragment> 라는 태그를 이용하여, <fragment>를 추가해주면 된다. ( 또는 자바 코드를 이용하여 fragment를 추가할 수 있다.)



다음은 간단한 TextView를 포함한 Fragement를 정의한 fragement_main.xml 이다.


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivityFragment">


    <TextView android:text="Hello!! I&apos;m fragment" android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:id="@+id/tvHello" />


</RelativeLayout>


Activity의 Layout을 정의하는 방법과 유사하게, fragment의  layout도 정의한다.

다음으로 Fragment class를 상속받아서 MainActivityFragement를 정의한다.


public class MainActivityFragment extends Fragment {


    public MainActivityFragment() {

    }


    @Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container,

                             Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment_main, container, false);

    }

}


여기까지 진행했으면 Fragment를 만든것이다. 다음으로, 이 Fragement를 Activity 위에 배치해보자.

아래는 MainActivity에서 사용하는 Layout을 정의한 activity_main.xml 이다.


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivityFragment">


    <fragment xmlns:android="http://schemas.android.com/apk/res/android"

        xmlns:tools="http://schemas.android.com/tools"

        android:id="@+id/fragment1"

        android:name="com.example.terry.simplefragment1.MainActivityFragment"

        tools:layout="@layout/fragment_main"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        />


    <fragment xmlns:android="http://schemas.android.com/apk/res/android"

        xmlns:tools="http://schemas.android.com/tools"

        android:id="@+id/fragment2"

        android:name="com.example.terry.simplefragment1.MainActivityFragment"

        tools:layout="@layout/fragment_main"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_below="@+id/fragment1"

        android:layout_alignParentEnd="true" />


    <fragment xmlns:android="http://schemas.android.com/apk/res/android"

        xmlns:tools="http://schemas.android.com/tools"

        android:id="@+id/fragment3"

        android:name="com.example.terry.simplefragment1.MainActivityFragment"

        tools:layout="@layout/fragment_main"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_below="@+id/fragment2"

        android:layout_alignParentEnd="true" />



</RelativeLayout>


안에 <fragment ..> 라는 이름으로 앞서 정의한 MainActivityFragment를 3개 순차적으로 배치했음을 볼 수 있다.





저작자 표시 비영리
신고

안드로이드에서 비디오 플레이 하기


조대협 (http://bcho.tistory.com)


동영상 서비스를 하다보니, 실제로 비디오 플레이어를 어떻게 구현하는 지가 궁금해서 찾아봤는데, 생각보다 안드로이드의 비디오 플레이어 구현은 간단해 보인다. 

내부적으로 MediaPlayer라는 컴포넌트를 사용하는데, 이 MediaPlayer는 조금 더 미세한 컨트롤이 가능하고 이를 한단계 더 쌓아 놓은 VideoView라는 것이 사용방법이 쉽다. 또한 구버전의 안드로이드에서는 MediaPlayer 기능이 잘 작동하지 않는 부분이 있기 때문에, VideoView를 사용하는 것이 조금 더 안정적일 수 있다.


내장된 플레이어로 지원되는 프로토콜은 다음과 같다.

RTSP

HTTP/HTTPS Progressive Streaming

HTTP/HTTPS Live Streaming (HLS)

MPEG-2 TS 

Protocol version 3 (Android 4.0 & above)

Protocol version 2 (Android 3.X)

3.0 이하 버전은 지원 안됨


 


MediaPlayer는 실시간 방송을 위한 RTMP나 HTTP 기반의  DASH등 다양한 프로토콜을 지원하지 않고, 기능상에 제약이 있는데, 이러한 기능상의 제약을 없애고 플레이어를 확장하려면, MediaCodec과  MediaExtractor 클래스를 이용하여 직접 플레이어를 만들어야 하는데, 번거롭고 다른 대안으로는 조금 더 다양한 기능을 제공하는 오픈 소스 플레이어를 활용하는 방법이 있다.

https://developer.android.com/guide/topics/media/exoplayer.html

를 보면 ExoPlayer를 소개하고 있는데, 안드로이드 공식 개발자 페이지에서 소개하는 것을 보면 신뢰성이 꽤 높은 듯 하다.


배경 설명은 이정도로 하고, 여기서는 간단하게 VieoView를 이용한 간단한 플레이어를 만들어 보도록 한다.


다음과 같은 기능을 구현한다.


URL 입력 (Progressive download 또는 HLS)

재생

일시 멈춤

네비게이션 바 출력

전체 시간 출력


완성된 앱의 UI는 다음과 같다. 




가장 기본적인 기능인데, 앞으로 가기나, 뒤로 가기,음소거,음켜기 등도 구현이 어렵지 않으니 쉽게 추 가 할 수 있다.


참고 

미디어 플레이어 예제  http://www.tutorialspoint.com/android/android_mediaplayer.htm

전체화면 보기 예제

http://javaexpert.tistory.com/365


레이아웃 코드는 다음과 같다. 

activity_main.xml



다음은, MainActivity.java  코드이다.


MainActivity.java



코드를 하나하나 뜯어 보면, 먼저 OnCreate에서 객체가 생성될때, VideoView 객체와 비디오 플레이 진행상황을 표시할 SeekBar에 대한 레퍼런스를 저장해놓는다.


    final static String SAMPLE_VIDEO_URL = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";

    VideoView videoView;

    SeekBar seekBar;

    Handler updateHandler = new Handler();


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        EditText tvURL = (EditText)findViewById(R.id.etVieoURL);

        tvURL.setText(SAMPLE_VIDEO_URL);


        videoView = (VideoView)findViewById(R.id.videoView);

        // MediaController mc = new MediaController(this);

        // videoView.setMediaController(mc);


        seekBar = (SeekBar)findViewById(R.id.seekBar);

    }


다음 “LOAD” 버튼을 누르면 지정된 URL에서 MP4 파일을 읽어와서 플레이를 시작하도록 한다.


    public void loadVideo(View view) {

        //Sample video URL : http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_2mb.mp4

        EditText tvURL = (EditText) findViewById(R.id.etVieoURL);

        String url = tvURL.getText().toString();


        Toast.makeText(getApplicationContext(), "Loading Video. Plz wait", Toast.LENGTH_LONG).show();

        videoView.setVideoURI(Uri.parse(url));

        videoView.requestFocus();


        // 토스트 다이얼로그를 이용하여 버퍼링중임을 알린다.

        videoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {


            @Override

            public boolean onInfo(MediaPlayer mp, int what, int extra) {

                switch(what){

                    case MediaPlayer.MEDIA_INFO_BUFFERING_START:

                        // Progress Diaglog 출력

                        Toast.makeText(getApplicationContext(), "Buffering", Toast.LENGTH_LONG).show();

                        break;

                    case MediaPlayer.MEDIA_INFO_BUFFERING_END:

                        // Progress Dialog 삭제

                        Toast.makeText(getApplicationContext(), "Buffering finished.\nResume playing", Toast.LENGTH_LONG).show();

                        videoView.start();

                        break;

                }

                return false;

            }

        }


        );


        // 플레이 준비가 되면, seekBar와 PlayTime을 세팅하고 플레이를 한다.

        videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

            @Override

            public void onPrepared(MediaPlayer mp) {

                videoView.start();

                long finalTime = videoView.getDuration();

                TextView tvTotalTime = (TextView) findViewById(R.id.tvTotalTime);

                tvTotalTime.setText(String.format("%d:%d",

                                TimeUnit.MILLISECONDS.toMinutes((long) finalTime),

                                TimeUnit.MILLISECONDS.toSeconds((long) finalTime) -

                                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) finalTime)))

                );

                seekBar.setMax((int) finalTime);

                seekBar.setProgress(0);

                updateHandler.postDelayed(updateVideoTime, 100);

                //Toast Box

                Toast.makeText(getApplicationContext(), "Playing Video", Toast.LENGTH_SHORT).show();

            }

        });

    }


하나씩 뜯어보면, 비디오 플레이는  viewView.SetVideoURI(URI)를 지정하면 비디오가 플레이가 시작되고, videoView.requestFocus()를 하면, 해당 view에 포커스를 한다.

다음으로 videoView.start()를 하면 플레이가 되는데, 중간에 몇가지 추가 코드가 들어가 있다.


먼저 이 예제는 로컬에 있는 파일을 플레이 하는 것이 아니라 Remote에 있는 MP4파일을 http로 다운 받으면서 플레이하는 Progressive download 형태의 플레이 방식이기 때문에 네트워크 상태에 따라서 로딩 시간이 걸릴 수 있다. 다음 videoView.setOnInfoListner는 VideoView의 상태가 변경되었을때 호출되는 콜백함수로, 버퍼링이 시작될때와 버퍼링이 끝날때의 이벤트를 잡아서 토스트 다이얼로그를 띄워주는 부분이다. 아래 구현에서는 토스트 다이얼로그를 사용했지만 Progress Dialog등을 이용하여 버퍼링 중임을 알려줄 수 도 있다.

버퍼링이 시작될때는 MediaPlayer.MEDIA_INFO_BUFFERING_START라는 이벤트를 보내고, 버퍼링이 끝날때는 MediaPlayer.MEDIA_INFO_BUFFERING_END라는 이벤트를 보내기 때문에, 이 시점에 맞춰서 버퍼링중임을 알리는 메세지를 보이거나 감추면 된다.


        // 토스트 다이얼로그를 이용하여 버퍼링중임을 알린다.

        videoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {


            @Override

            public boolean onInfo(MediaPlayer mp, int what, int extra) {

                switch(what){

                    case MediaPlayer.MEDIA_INFO_BUFFERING_START:

                        // Progress Diaglog 출력

                        Toast.makeText(getApplicationContext(), "Buffering", Toast.LENGTH_LONG).show();

                        break;

                    case MediaPlayer.MEDIA_INFO_BUFFERING_END:

                        // Progress Dialog 삭제

                        Toast.makeText(getApplicationContext(), "Buffering finished.\nResume playing", Toast.LENGTH_LONG).show();

                        videoView.start();

                        break;

                }

                return false;

            }

        }


        );


다음 코드 부분은 버퍼링이 다 끝나고 처음 플레이 준비가 되었을 때 전체 플레이 시간을 세팅하고 SeekBar의 전체 길이등을 세팅하는 초기 작업을 하기 위한 콜백 함수이다. setOnPreparedListner에서, MediaPlayer.OnPreparedListner를 콜백함수로 다음과 같이 정의한다.


        // 플레이 준비가 되면, seekBar와 PlayTime을 세팅하고 플레이를 한다.

        videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

            @Override

            public void onPrepared(MediaPlayer mp) {

                videoView.start();

                long finalTime = videoView.getDuration();

                TextView tvTotalTime = (TextView) findViewById(R.id.tvTotalTime);

                tvTotalTime.setText(String.format("%d:%d",

                                TimeUnit.MILLISECONDS.toMinutes((long) finalTime),

                                TimeUnit.MILLISECONDS.toSeconds((long) finalTime) -

                                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) finalTime)))

                );

                seekBar.setMax((int) finalTime);

                seekBar.setProgress(0);

                updateHandler.postDelayed(updateVideoTime, 100);

                //Toast Box

                Toast.makeText(getApplicationContext(), "Playing Video", Toast.LENGTH_SHORT).show();

            }

        });


먼저 플레이 준비가 되면 videoView.start() 메서드로 동영상 플레이를 시작한다.

다음으로 vieoView.getDuration() 메서드를 이용하여 전체 동영상 플레이 시간을 받아서 분/초로 변환한후 출력하고, SeekBar의 Max값을 총 플레이 시간으로 저장한다. 그리고 100ms마다 seekBar의 플레이 상태를 이동시키기 위해서 updateHandler에 updateVideoTime 함수를 100ms 이후에 실행하도록 지정한다.

updateVideoTime 함수는 다음과 같다.


    // seekBar를 이동시키기 위한 쓰레드 객체

    // 100ms 마다 viewView의 플레이 상태를 체크하여, seekBar를 업데이트 한다.

    private Runnable updateVideoTime = new Runnable(){

        public void run(){

            long currentPosition = videoView.getCurrentPosition();

            seekBar.setProgress((int) currentPosition);

            updateHandler.postDelayed(this, 100);


        }

    };


만약에 안드로이드 에뮬레이터에서 실행을 하게 되면 일부 MP4 동영상은 Decoder를 찾을 수 없다는 에러가 나올 수 있는데, 이는 MP4 확장명을 가지더라도, 내부 인코딩 방식이 각기 틀리기 때문에, 안드로이드 에뮬레이터에서는 일부 디코더만 지원하기 때문에, 제대로 플레이가 되지 않을 수 있다.  실제 기기를 연결하면, 실 기기에는 대부분의 MP4 디코더가 탑재되어 있기 때문에, 문제 없이 동영상이 플레이 된다.

그렇지만 실제로 동영상을 서비스하는 플레이어를 만들려면, 동영상을 플레이어에 최적화된 형태로 다시 인코딩을 해서 플레이를 하는 것이 좋다. 화면 해상도에 따른 품질 차이나, 압축 방식 그리고 초당 프레임수 (FPS)등에 따라서 영향을 받기 때문에 최적의 인코딩 포맷을 찾은 후에, 그 포맷에 따라서 인코딩을 하는 것이 좋으며, 이런 인코딩 전문 플랫폼으로는 예전 엠군에서 제공하는 위켄디오나 해외의 zencoder등과 같인 인코딩 플랫폼을 사용하면 손쉽게 연동이 가능하다. 



참고 자료

http://www.techotopia.com/index.php/An_Android_Studio_VideoView_and_MediaController_Tutorial#Introducing_the_Android_MediaController_Class



저작자 표시 비영리
신고

SharedPreference를 이용한 데이타 저장 

및 애플리케이션간 데이타 공유


조대협 (http://bcho.tistory.com)



안드로이드에서는 데이타를 저장할때 여러가지 방법을 사용할 수 있다.

대표적인 방법으로는 일반적으로 파일을 생성해서 저장하는 방법, 그리고 안드로이드에 내장된 데이타 베이스인 SQLite 데이타 베이스를 활용하는 방법이외에 SharedPreference라는 클래스를 사용하여 데이타를 저장하는 방법이 있다.


SharedPreference


SharedPreference는 간단하게 Key/Value 형태로 데이타를 저장할 수 있는 데이타 구조로 내부적으로는 XML 파일로 저장이 된다.


사용법이 매우 간단해서 일반적인 설정값이나 세션 정보, 사용자 정보와 같은 값들을 저장하거나 주로 복잡하지 않고 경량의 데이타를 저장하는데 쉽게 사용할 수 있다. 단 해당 정보는 안드로이드 파일 시스템 내에 XML 파일로 접근이 되기 때문에, 보안적으로 안전하지 않을 수 있기 때문에, 노출등을 대비해서 중요한 정보는 저장하지 않거나 또는 최소한의 암호화를 해서 저장하는 게 어떨까 하는 생각이다. 


실제로 사용하는 방법은 Activity나 Service  클래스에서 Context를 가지고 온후, 해당 Context를 통하여 SharedPreference를 생성하여, 데이타를 저장하면 된다.


다음은 SharedPreference를 write 하는 예제이다



    public void writeSharedPreference(View view){


        EditText txtValue = (EditText) findViewById(R.id.txtValue);

        String value = txtValue.getText().toString();



        // 1. get Shared Preference

        SharedPreferences sharedPreference

                = this.getSharedPreferences("MYPREFRENCE", Context.MODE_MULTI_PROCESS | Context.MODE_WORLD_READABLE); 


        // 2. get Editor

        SharedPreferences.Editor editor = sharedPreference.edit();


        // 3. set Key values

        editor.putString("MYKEY",value);

        editor.putString("KEY2", "VALUE2");


        // 4. commit the values

        editor.commit();

    }


먼저 위의 1단계에서 getSharedPreference를 이용하여, 애플리케이션 컨텍스트로부터 SharedPreference를 가지고 온다. 이때 Preference 의 이름을 지정할 수 있는데, 여기서는 “MYPREFRENCE”라는 이름으로 Preference를 생성하였다. 그리고 다음으로는 access mode를 설정하였는데, 이 예제에서 설정한 access mode는 여러 프로세스간 공유가 가능하고, 읽을 수 있는 모드로 설정하였다. (뒤에서 다른 애플리케이션에서 이 Preference를 열기 위해서 권한을 부여하였다)


잠깐 access_mode에 대해서 짚고 넘어가면

보통은 MODE_PRIVATE을 사용하는데, 이 설정은 다른 애플리케이션은 접근이 불가하고, 오직 이 SharedPreference를 만든 애플리케이션만 접근이 가능하게 한다. MODE_WORLD_READABLE은 다른 애플리케이션도 읽기를 허용하는 권한이고, MODE_WORLD_WRITABLE은 다른 애플리케이션도 쓰기를 허용하는 권한이다.

조금더 자세한 권한에 대한 설명은 http://www.tutorialspoint.com/android/android_shared_preferences.htm 를 참고하기 바란다.



애플리케이션을 실행하면 텍스트 문자열을 입력 받아서 SharedPreference를 저장하게된다.

이때 바로 저장하는 것이 아니라 SharedPreference로 부터 Editor라는 객체를 받아온 후, setXXX메서드를 이용하여 데이타를 셋한후, 저장을 할때는 반드시 editor.commit()을 하여 저장한다.


주) 일종의 트렌젝션과 같은 기능을 제공하는데, 추측하건데 동시에 여러 애플리케이션이 접근하거나 여러 쓰레드가 접근할때 데이타가 틀어지는 것을 막기 위해서 단일 트렌젝션의 기능을 제공하는 걸로 이해 된다.




<그림 1. > 문자열을 입력받아 SharedPreference에 저장하는 앱


실제로 저장이 되었는지 확인을 하려면 Android Device Monitor 라는 도구를 이용하여, 파일 시스템을 살펴보면 생성된 SharedPreference 파일을 찾을 수 있다.


파일은 /data/data/PACKAGE_NAME/shared_prefs 에 저장된다.



<그림 2> Android Device Monitor


실제로 파일을 열어보면 다음과 같이 입력한 값이 XML 형태로 저장됨을 확인할 수 있다.



<그림 3> MYPREFRENCE.xml


SharedPreference를 통하여 다른 애플리케이션과 데이타를 공유하기


SharedPreference는 생성할때 다른 애플리케이션의 접근을 허용하여 생성할 경우, 다른 애플리케이션이 그 데이타를 액세스 할 수 있다. 즉 SharedPreference 를 이용해서 애플리케이션간에 데이타를 공유하는 것이 가능하다.


다음은, 타 애플리케이션의 SharedPreference로 부터 데이타를 읽어와서 EditText에 출력하는 예제의 실행예와 코드이다.




    public void readPreference(View view) throws Exception {



        // get Context of other application

        Context otherAppContext = null;

        try{

            otherAppContext =

                    createPackageContext("com.example.terry.sharedpreferencewriter",0);

        }catch(PackageManager.NameNotFoundException e){

            // log

            e.printStackTrace();

            throw e;

        }


        // getting Shared preference from other application

        SharedPreferences pref

                = otherAppContext.getSharedPreferences("MYPREFRENCE", Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS);

        String value = pref.getString("MYKEY", "NOTFOUND");


        EditText txtValue = (EditText)findViewById(R.id.txtValue);

        txtValue.setText(value);


    }


먼저 createPcakgeContext라는 메서드를 이용하여 다른 애플리케이션인 “com.example.terry.sharedpreferencewriter” 에 대한 애플리케이션 Context를 읽어온다.

다음으로는 얻어온 Context로 부터 getSharedPreferences 메서드를 읽어온후에, 해당 SharedPreference로 부터 getString 메서드를 이용하여 “MYKEY” 라는 키로 저장된 값을 읽어와서 출력한다.





저작자 표시 비영리
신고

간단한 웹뷰 컴포넌트의 이해와 최적화

조대협(http://bcho.tistory.com)


들어가기에 앞서서


이글은 안드로이드 프로그래밍 개발을 공부하면서 공부한 내용을 정리한 글입니다. 제가 아직 안드로이드에는 전문성이 없는 초보자 수준이기 때문에, 수준이 높지 않으니, 내용을 참고만 하시기를 바라며, 더 나은 프로그래밍 방법등이 있음을 미리 알려드립니다.


Webview(웹뷰) 컴포넌트에 대해서


웹뷰는 안드로이드 프레임웍에 내장된 웹 브라우져 컴포넌트로 뷰(View)형태로 앱에 임베딩이 가능하다. 비교해서 생각하자면, 안드로이드 앱안에, HTML iframe을 넣어놓은 것이라고나 할까?

단순하게, 웹 페이지를 보기위해도 사용하지만, 안드로이드 앱 안에서 HTML을 호출하여 앱을 구현하는 하이브리드 형태의 앱을 개발하는데에도 많이 사용된다. 이러한 하이브리드 앱은 안드로이드 네이티브 앱 개발에 비해서 개발이 비교적 쉽고, 특히나 기기간의 호환성을 해결하기가 상대적으로 편하다는 장점을 가지고 있으나, HTML 기반인 만큼 상대적으로 반응성이 약하고, 애니메이션등의 다양한 UI 효과를 넣기 어렵다는 단점을 가지고 있다.

그럼에도 불구하고 SNS나 기타 서비스 앱에서는 타 웹사이트 링크로 가는 기능등을 지원하기 위해서 많이 사용되고 있다.

웹 뷰에 사용되는 브라우져 컴포넌트는 우리가 일반적으로 생각하는 웹브라우져 수준으로 생각하면 곤란하다. 일반적인 웹 브라우져를 안드로이드 OS에 맞게 일부 기능들을 제외하고 작게 만든 웹브라우져라고 생각하면 되고 그렇기 때문에 HTML5 호환성등 기능의 제약을 많이 가지고 있다.



기본적인 웹뷰 사용방법


이해를 돕기 위해서 웹뷰를 이용한 간단한 애플리케이션을 만들어보자. 이 애플리케이션은 URL을 입력 받고 Go 버튼을 누르면 웹뷰에 해당 URL을 접속하여 보여주고, 해당 URL이 로딩되는데 까지 걸린 시간을 millisecond 로 표시해주는 애플리케이션이다.




먼저 안드로이드 스튜디오에서, MainActivity하나만 있는 심플한 앱 프로젝트를 생성한 후에, MainActivity의 레이아웃을 다음과 같이 디자인하자




위의 레이아웃의 XML 코드를 보면 다음과 같다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity"
android:weightSum="1">


<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<EditText
android:layout_width="252dp"
android:layout_height="wrap_content"
android:id="@+id/txtURL"
android:text="http://www.naver.com" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Go"
android:id="@+id/btnGo"
android:onClick="goURL" />
</LinearLayout>

<WebView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/webView"
android:layout_weight="0.83" />

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.13">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Elapsed time :"
android:id="@+id/tvElapsedTime" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="sec"
android:id="@+id/tvSec" />
</LinearLayout>

</LinearLayout>

레이아웃이 디자인 되었으면, [GO]버튼을 눌렀을때, 웹뷰에 URL을 표시하도록 goURL(View)라는 메소드를 다음과 같이 구현하자


public void goURL(View view){
TextView tvURL = (TextView)findViewById(R.id.txtURL);
String url = tvURL.getText().toString();
Log.i("URL","Opening URL :"+url);

WebView webView = (WebView)findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient()); // 이걸 안해주면 새창이 뜸
webView.loadUrl(url);

}


사용법은 의외로 간단한데, Webview객체를 얻어와서 loadURL로 url을 로딩하면 된다. 이때 주의할점은 setWebViewClient()  메서드를 사용해야 하는데, 안드로이드에서는 디폴트로, 다른 링크로 이동하고자 할때는 안드로이드의 디폴트 외부 웹 브라우져를 통해서 이동하도록 하고 있다. 그래서 만약 setWebViewClient로 내부 웹뷰 클라이언트를 지정해주지 않으면 별도의 브라우져가 뜨는 것을 볼 수 있다.

그리고 이 애플리케이션은 외부 인터넷을 억세스하기 때문에, AndroidManifest.xml에서INTERNET 퍼미션을 추가해줘야 한다.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.terry.simplewebview" >
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true"> // 하드웨어 가속 사용
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


주목해서 봐야할점중 하나는 android:hardwareAccelerated  라는 필드인데, 웹 페이지를 렌더링 하는데 하드웨어 가속 기능을 사용하도록 하는 옵션으로, 이 옵션 사용 여부에 따라서 성능차이가 꽤 있기 때문에, 가급적이면 사용하도록 한다.


여기서 약간 개량을 해서, 해당 URL이 다 뜨고 나면 로딩하는데 걸리는 시간을 측정하는 로직을 추가해보자


public void goURL(View view){
TextView tvURL = (TextView)findViewById(R.id.txtURL);
String url = tvURL.getText().toString();
Log.i("URL", "Opening URL with WebView :" + url);

final long startTime = System.currentTimeMillis();
WebView webView = (WebView)findViewById(R.id.webView);

// 하드웨어 가속
// 캐쉬 끄기
//webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

webView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
long elapsedTime = System.currentTimeMillis()-startTime;
TextView tvSec = (TextView) findViewById(R.id.tvSec);
tvSec.setText(String.valueOf(elapsedTime));
}
});
webView.loadUrl(url);

}


setWebViewClient를 만들때, WebViewClient에 onPageFinished라는 메서드를 오버라이딩해서 종료 시간을 구할 수 있다. 이 메서드는 해당 URL이 모두 로딩되었을때 호출되는 메서드이다.


웹뷰의 개념 이해를 돕기 위해서 가장 간단한 예제를 통해서 설명하였는데, HTML 안에서 Javascript 를 사용하려면 별도의 옵션이 필요하고, 또한 javascript를 사용할때는 Javascript의 메서드를 실행하면, 안드로이드 애플리케이션내에 정의된 메서드를 수행할 수 있도록도 할 수 있다. 

기타 자세한 내용은 http://developer.android.com/guide/webapps/webview.html 를 참고하기 바란다.


웹뷰의 성능 향상과 확장


웹뷰에 대한 성능 관련 자료를 찾아보니, 성능이 안나오는 문제가 많아서 하이브리드 앱을 개발하는 쪽에는 다양한 솔루션이 제시되고 있다. 써드 파티 웹뷰 라이브러리를 이용하는 방법이나 또는 웹뷰를 커스터마이징 해서 WebSocket의 기능을 추가하거나 Canvas 성능을 개선하는 작업등이 있는데 (http://deview.kr/2013/detail.nhn?topicSeq=14) 성능 부분에 대해서는 조금 더 공부가 필요할듯 하고, 아래는 성능 관련해서 검색을 했을때 주로 많이 소개되는 내용을 정리하였다.


크롬뷰 사용


안드로이드 4.4 (API level 19)부터는 웹뷰에 대한 성능 개선의 일환으로 Chromium (http://www.chromium.org/Home

) 이라는 오픈소스( 브라우져가 사용되었는데, 별도의 설정없이도, 이 브라우져 기반의 웹뷰가 사용이 가능하다. Chromium 기반의 웹뷰는 성능 향상뿐만 아니라, HTML 호환성 개선도 같이 이루어졌다. 

https://www.google.co.kr/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=webview+performance+android&newwindow=1&start=10


일반적인 튜닝 옵션


일반적으로 웹뷰 성능 튜닝 관련 내용을 검색하면 단골로 나오는 3가지 튜닝 옵션들이 있는데 다음과 같다

  • 캐쉬 사용하지 않기 : webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);로 브라우져 캐쉬를 끄는 방법이 언급되는데, 사실 캐쉬를 쓰는데 왜 성능이 올라가는지는 잘 모르겠다. 이 부분은 조금 더 살펴봐야할것으로 보이고
  • 하드웨어 가속 기능 사용하기 :렌더링에 대해서 하드웨어 가속 기능을 사용할지 여부인데, 3.0 부터 지원이 되기 시작했고, 디폴트는 false이며, 4.0부터는 default가 true이다. 앞의 예제에서와 같이 AndroidManifest.xml에서, android:hardwareAccelrated를  true로 설정해주면 된다.
  • 렌더링 우선 순위 높이기 : 강제적으로 렌더링 기능에 대한 우선 순위를 높이는 방법이다. webView.getSettings().setRenderPriority(RenderPriority.HIGH)로 설정해주면 되는데, API Level 18부터는 deprecated 되었기 때문에 하위 버전에만 적용될 수 있도록 한다.

외부 라이브러리를 이용한 웹뷰의 성능 향상


다른 성능 향상 방법으로는 써드 파티 라이브러리를 찾아봤는데, 가장 많이 사용되는 라이브러리로 크로스워크(crosswalk)라는 오픈 소스 라이브러리가 있다. https://crosswalk-project.org/documentation/getting_started.html

성능 향상은 물론이거니와, HTML5에 대한 호환성 지원도 매우 높고, 안드로이드뿐만 아니라, iOS,Tizen등 다양한 OS를 지원하기 때문에 하이브리드 앱을 개발한다면 탑재를 고려해볼만하지만, 이러한 호환성이나 성능을 높이기 위해서 렌더링 관련 기능을 모두 자체 구현하였기 때문에 앱 사이즈가 대략 30~40MB정도 커지기 때문에, 앱 크기에 대한 부담과 dex 사이즈에 대한 부담 (안드로이드 실행 파일인 dex는 메서드수가 65535개로 제한이 되어 있다.)

http://monaca.mobi/en/blog/crosswalk-performance/ 는 HTML5에 대한 호환성을 Crosswalk와 기본 웹뷰 사이에 수치를 비교한것인데, 일반 웹뷰는 278, 크로스워크는 호환성 부분에서 493 포인트를 낸것을 확인할 수 있다.


게임이나 일반 애플리케이션의 경우는 네이티브 앱 개발이 아무래도 대세가 되겠지만, SNS나 미디어성 컨텐츠는 개발의 용이성과 호환성 그리고 다양한 컨텐츠 포맷을 지원하는 점에서 HTML5 기술이 지원되는 웹뷰는 아무래도 필수적인 기술이 되지 않을까 싶다.


저작자 표시 비영리
신고

 

REST API를 이용하여, 날씨를 조회하는 간단한 애플리케이션 만들기

 

조대협 (http://bcho.tistor


네트워크를 통한  REST API 호출 방법을 알아보기 위해서, 간단하게, 위도와 경도를 이용하여 온도를 조회해오는 간단한 애플리케이션을 만들어보자

이 애플리케이션은 경도와 위도를 EditText 뷰를 통해서 입력 받아서 GETWEATHER라는 버튼을 누르면 네트워크를 통하여 REST API를 호출 하여, 날씨 정보를 받아오고, 해당 위치의 온도(화씨) 출력해주는 애플리케이션이다.

 



 

날씨 API 는 http://www.openweathermap.org/ 에서 제공하는데, 사용법이 매우 간단하고, 별도의 API인증 절차가 필요하지 않기 때문에 쉽게 사용할 수 있다. 사용 방법은 다음과 같이 쿼리 스트링으로 위도와 경도를 넘겨주면 JSON  형태로 지정된 위치의 날씨 정보를 리턴해준다.

 

http://api.openweathermap.org/data/2.5/weather?lat=37&lon=127

 

아래는 리턴되는 JSON 샘플



이 리턴되는 값중에서 main.temp 에 해당하는 값을 얻어서 출력할 것이다.

 

날씨값을 저장하는 클래스 작성

 

package com.example.terry.simpleweather.client;

/**

 * Created by terry on 2015. 8. 27..

 */

public class Weather {
    int lat;
    int ion;
    int temprature;
    int cloudy;
    String city;

    public void setLat(int lat){ this.lat = lat;}
    public void setIon(int ion){ this.ion = ion;}
    public void setTemprature(int t){ this.temprature = t;}
    public void setCloudy(int cloudy){ this.cloudy = cloudy;}
    public void setCity(String city){ this.city = city;}

    public int getLat(){ return lat;}
    public int getIon() { return ion;}
    public int getTemprature() { return temprature;}
    public int getCloudy() { return cloudy; }
    public String getCity() { return city; }
}

 

다음으로 REST API를 호출하는 OpenWeatherAPIClient.java 코드를 다음과 같이 작성한다.

 

package com.example.terry.simpleweather.client;



import org.json.JSONException;

import org.json.JSONObject;


import java.io.BufferedInputStream;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;


/**

 * Created by terry on 2015. 8. 27..

 * 목표

 * 1. AsyncTask와 HTTPURLConnection을 이용한 간단한 HTTP 호출 만들기

 * 2. 리턴된 JSON을 파싱하는 방법을 통하여, JSON 객체 다루는 법 습득하기

 * 3. Phone Location (GPS) API 사용 방법 파악하기

 *

 * 참고 자료 : http://developer.android.com/training/basics/network-ops/connecting.html

 * */

public class OpenWeatherAPIClient {

    final static String openWeatherURL = "http://api.openweathermap.org/data/2.5/weather";

    public Weather getWeather(int lat,int lon){

        Weather w = new Weather();

        String urlString = openWeatherURL + "?lat="+lat+"&lon="+lon;


        try {

            // call API by using HTTPURLConnection

            URL url = new URL(urlString);

            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

//            urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);

//            urlConnection.setReadTimeout(DATARETRIEVAL_TIMEOUT);


            InputStream in = new BufferedInputStream(urlConnection.getInputStream());

            JSONObject json = new JSONObject(getStringFromInputStream(in));


            // parse JSON

            w = parseJSON(json);

            w.setIon(lon);

            w.setLat(lat);


        }catch(MalformedURLException e){

            System.err.println("Malformed URL");

            e.printStackTrace();

            return null;


        }catch(JSONException e) {

            System.err.println("JSON parsing error");

            e.printStackTrace();

            return null;

        }catch(IOException e){

            System.err.println("URL Connection failed");

            e.printStackTrace();

            return null;

        }


        // set Weather Object


        return w;

    }


    private Weather parseJSON(JSONObject json) throws JSONException {

        Weather w = new Weather();

        w.setTemprature(json.getJSONObject("main").getInt("temp"));

        w.setCity(json.getString("name"));

        //w.setCloudy();


        return w;

    }


    private static String getStringFromInputStream(InputStream is) {


        BufferedReader br = null;

        StringBuilder sb = new StringBuilder();


        String line;

        try {


            br = new BufferedReader(new InputStreamReader(is));

            while ((line = br.readLine()) != null) {

                sb.append(line);

            }


        } catch (IOException e) {

            e.printStackTrace();

        } finally {

            if (br != null) {

                try {

                    br.close();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }


        return sb.toString();


    }

}

 

 

코드를 하나씩 뜯어보면

안드로이드에서 HTTP 호출을 하기 위해서는 HttpURLConnection이라는 클래스를 사용한다. URL이라는 클래스에 API를 호출하고자 하는 url주소를 지정하여 생성한후, url.openConnection()을 이용해서, HTTP Connection을 열고 호출한다.

URL url = new URL(urlString);

HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

 

다음으로, 리턴되어 오는 문자열을 읽어서 JSON형태로 파싱을 해야 하는데,

InputStream in = new BufferedInputStream(urlConnection.getInputStream());

먼저 위와 같이 urlConnection으로 부터 getInputStream() 메서드를 통해서 InputStream을 리턴받고,

JSONObject json = new JSONObject(getStringFromInputStream(in));

getStringFromInput(InputStream xx)이라는 메서드를 이용하여 inputStream을 String으로 변환한후에 JSONObject로 변환한다. (여기서 getStringFromInput은 미리 정해진 메서드가 아니고 위의 소스코드 처럼 InputStream 을 String으로 변환하기 위해서 여기서 지정된 코드들이다.)

다음으로 해당 JSON을 파싱해서, main.temp 값을 읽은후 Weather class에 넣어서 리턴을 한다.

w = parseJSON(json);

w.setIon(lon);

w.setLat(lat);

에서 parseJSON이라는 메서드를 호출하는데, parseJSON은 다음과 같다.


private Weather parseJSON(JSONObject json) throws JSONException {

    Weather w = new Weather();

    w.setTemprature(json.getJSONObject("main").getInt("temp"));

    w.setCity(json.getString("name"));

    //w.setCloudy();


    return w;

}


위의 메서드에서는 json에서 먼저 getJSONObject(“main”)을 이용하여 “main” json 문서를 얻고, 그 다음 그 아래 있는 “temp”의 값을 읽어서 Weather 객체에 세팅한다.

 

여기까지 구현을 하면 REST API를 http를 이용하여 호출하고, 리턴으로 온 JSON 문자열을 파싱하여 Weather 객체에 세팅을해서 리턴하는 부분까지 완료가 되었다.

 

그다음 그러면 이 클래스의 메서드를 어떻게 호출하는가? 네트워크 통신은 IO작업으로 시간이 다소 걸리기 때문에, 안드로이드에서는 일반적으로 메서드를 호출하는 방식으로는 불가능하고, 반드시 비동기식으로 호출해야 하는데, 이를 위해서는 AsyncTask라는 클래스를 상속받아서 비동기 클래스를 구현한후, 그 안에서 호출한다.

다음은 OpenWeatherAPITask.java 클래스이다.

 

public class OpenWeatherAPITask extends AsyncTask<Integer, Void, Weather> {

    @Override

    public Weather doInBackground(Integer... params) {

        OpenWeatherAPIClient client = new OpenWeatherAPIClient();


        int lat = params[0];

        int lon = params[1];

        // API 호출

        Weather w = client.getWeather(lat,lon);


        //System.out.println("Weather : "+w.getTemprature());


        // 작업 후 리

        return w;

    }

}

 

위와 같이 AsyncTask를 상속받았으며, 호출은 doInBackground(..)메서드를 구현하여, 그 메서드 안에서 호출을 한다.

위의 코드에서 볼 수 있듯이, doInBackground에서 앞서 작성한 OpenWeatherAPIClient의 객체를 생성한후에, 인자로 넘어온 lat,lon값을 받아서, getWeahter를 통하여, 호출하였다.

 

이제 API를 호출할 준비가 모두 끝났다.

이제 UI를 만들어야 하는데, res/layout/activity_main.xml은 다음과 같다.

 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">




    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceLarge"

        android:text="Temperature"

        android:id="@+id/tem"

        android:layout_below="@+id/tvLongtitude"

        android:layout_alignParentStart="true"

        android:layout_marginTop="46dp" />


    <Button

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="getWeather"

        android:id="@+id/getWeatherBtn"

        android:onClick="getWeather"

        android:layout_alignBottom="@+id/tem"

        android:layout_alignParentEnd="true" />


    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceMedium"

        android:text="latitude"

        android:id="@+id/tvLatitude"

        android:layout_marginTop="27dp"

        android:layout_alignParentTop="true"

        android:layout_alignParentStart="true" />


    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:textAppearance="?android:attr/textAppearanceMedium"

        android:text="longtitude"

        android:id="@+id/tvLongtitude"

        android:layout_marginRight="62dp"

        android:layout_marginTop="30dp"

        android:layout_below="@+id/lat"

        android:layout_alignParentStart="true" />


    <EditText

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:id="@+id/lat"

        android:width="100dp"

        android:layout_marginRight="62dp"

        android:layout_alignBottom="@+id/tvLatitude"

        android:layout_toEndOf="@+id/tvLatitude"

        android:layout_marginStart="36dp" />


    <EditText

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:width="100dp"

        android:id="@+id/lon"

        android:layout_marginRight="62dp"

        android:layout_alignBottom="@+id/tvLongtitude"

        android:layout_alignStart="@+id/lat" />


</RelativeLayout>

 

화면 디자인이 끝났으면, 이제 MainActivity에서 버튼을 누르면 API를 호출하도록 구현해보자.

 

package com.example.terry.simpleweather;


import android.support.v7.app.ActionBarActivity;

import android.os.Bundle;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.widget.EditText;

import android.widget.TextView;


import com.example.terry.simpleweather.client.OpenWeatherAPITask;

import com.example.terry.simpleweather.client.Weather;


import java.util.concurrent.ExecutionException;



public class MainActivity extends ActionBarActivity {


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }


    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.

        getMenuInflater().inflate(R.menu.menu_main, menu);

        return true;

    }


    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        // Handle action bar item clicks here. The action bar will

        // automatically handle clicks on the Home/Up button, so long

        // as you specify a parent activity in AndroidManifest.xml.

        int id = item.getItemId();


        //noinspection SimplifiableIfStatement

        if (id == R.id.action_settings) {

            return true;

        }


        return super.onOptionsItemSelected(item);

    }


    // MapView 참고 http://seuny.tistory.com/14

    public void getWeather(View view)

    {

        EditText tvLon = (EditText) findViewById(R.id.lon);

        String strLon = tvLon.getText().toString();

        int lon = Integer.parseInt(strLon);


        EditText tvLat = (EditText) findViewById(R.id.lat);

        String strLat = tvLat.getText().toString();

        int lat = Integer.parseInt(strLat);



        // 날씨를 읽어오는 API 호출

        OpenWeatherAPITask t= new OpenWeatherAPITask();

        try {

            Weather w = t.execute(lon,lat).get();


            System.out.println("Temp :"+w.getTemprature());


            TextView tem = (TextView)findViewById(R.id.tem);

            String temperature = String.valueOf(w.getTemprature());


            tem.setText(temperature);

            //w.getTemprature());



        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}

 

 

위에서 getWeather(View )부분에서 호출을하게 되는데, 먼저 앞에서 AsyncTask를 이용해서 만들었던 OpenWeatherAPITask 객체 t를 생성한 후에, t.execute로 호출을하면 된다.

실행 결과는 t.execute()에 대해서 .get()을 호출함으로써 얻을 수 있다.

 

이때 주의할점이 안드로이드는 네트워크 호출과 같은 리소스에 대한 접근을 통제하고 있는데, 이러한 통제를 풀려면, AnrdoidManifest.xml에 다음과 같이 INTERNET 과 NETWORK 접근을 허용하는 내용을 추가해줘야 한다.

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

다음은 위의 내용을 추가한 AndroidManifest.xml이다.

 

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.example.terry.simpleweather" >

    <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


    <application

        android:allowBackup="true"

        android:icon="@mipmap/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

        <activity

            android:name=".MainActivity"

            android:label="@string/app_name" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>


    </application>


</manifest>

 

참고 : http://developer.android.com/training/basics/network-ops/connecting.html




9월 15일 추가 내용

  • t.execute().get 보다는  onPostExecute 를 통해서 리턴 값을 받는 것이 더 일반적인 패턴
  • AsyncTask를 사용하기 보다는 근래에는 Retrofit이라는 프레임웍을 사용하는게 더 효율적이며, 성능도 2배이상 빠름.
    관련 튜토리얼 : http://gun0912.tistory.com/30
    아래는 성능 비교 자료


 

저작자 표시 비영리
신고

안드로이드 주요 레이아웃

프로그래밍/안드로이드 | 2015.08.27 00:09 | Posted by 조대협

안드로이드 주요 레이아웃 (메모)



원본 : https://www.udacity.com/course/viewer#!/c-ud853/l-1395568821/m-1645248621


다른 종류의 레이아웃도 있으나 일반적으로 사용하는 레이아웃은 다음과 같다.

  • FrameLayout : 보려는 항목이 하나만 있을 경우. (ListView 하나만 넣는 경우와 같은 케이스)
  • LinearLayout : 수직 또는 수평으로 차례로 View를 출력할때
  • RelativeLayout : 다른 View에 비한 상대적 위치를 표현하는 반응형 UI에 적합


저작자 표시 비영리
신고

드로이드에 애플리케이션  컴파일,설치해서 실행하기 


(에뮬레이터 실행 포함)


조대협 (http://bcho.tistory.com)


다음은 개발된 애플리케이션을 단말이나 또는 에뮬레이터에서 실행하는 방법이다.

이 방법은 안드로이드 스튜디오 1.2.2 을 맥북에 설치한 기준으로 설명한다.

안드로이드 스튜디오 IDE가 좋아서, 필요는 없겠지만. 몰 하던간에 IDE 없이도 할 수 있는 방법이 있어야 하기에 테스트 해놓은 내용을 기록해놓는다.


1. 애플리케이션 컴파일 하기


디렉토리에서 ./gradlew assembleDebug

명령을 실행하면, 디버깅 모드로 컴파일이 되고 apk 파일이 생성된다.



위와 같은 에러가 발생할 수 있는데, 이 경우는 ANDROID_HOME이 설정되지 않은 경우이다.

~/bash_profile에

export ANDROID_HOME=~/Library/Android/sdk 으로 설정하면 된다.



2. 애플리케이션을 디바이스에 설치하기 


안드로이드 SDK가 설치되는 곳은 /User/{username}/Library/Android/sdk

안드로이드의 필수적인 adb는 /Users/{username}/Library/Android/sdk/platform-tools

에 있는데 Path가 걸려있지 않아서, 다른 디렉토리에서 실행이 되지 않는다.


~/.bash_profile 파일에서 PATH에 위의 platform-tools 디렉토리 경로를 추가해주면 된다.


adb를 사용할 수 있는 상태가 되면, apk 파일이 있는 위치에서 다음과 같이 adb install 명령으로 설치한다.


ChoByungwookui-MacBook-Pro:apk terry$ adb install app-debug.apk


8/17 추가. adb install 시 adb install -r 을 하면, 기존에 파일이 있으면 replace하게 된다.


3. 에뮬레이터 띄우기


  실제로 디바이스 없는 경우에는 소프트웨어 기반의 버추얼 디바이스를 사용하면된다. (에뮬레이터)

에뮬레이터는 

%/Users/{username}/Library/Android/sdk/android avd

로 실행하면 된다.



4. 애플리케이션 실행하기


에뮬레이터를 띄워서 adb를 이용해 애플리케이션을 설치했으면, 실행을 해야 하는데, 직접 에뮬레이터에 들어가서 실행하는 방법도 있지만 adb shell을 이용하면 조금 더 쉽게 커맨드라인에서 실행을 해볼 수 있다.


adb shell am start 패키지명/패키지명.액티버티클래스명

예) adb shell am start -n com.example.terry.basicintentsample/com.example.terry.basicintentsample.MyActivity



저작자 표시 비영리
신고

안드로이드 리소스 파일 개요


조대협 (http://bcho.tistory.com)


안드로이드 프로젝트에서 소스 코드 이외에 이미지나 디자인 레이아웃등의 기타 리소스등을 코드와 분리해서 취급한다.


이렇게 리소스를 코드와 분리해서 사용하는 이유는, 조금 더 유연하게 애플리케이션을 개발하게 하기 위함이다. string과 같은 문자열을 별도의 리소스 파일에서 관리함으로써, 애플리케이션 수정 없이 string 리소스 파일에만 문자열을 추가함으로 간단하게 다국어를 지원할 수 있다.


리소스는 크게 7가지 타입의 리소스가 있고, “/res” 디렉토리에 저장된다. 



Layout Resource (뷰 리소스)

애플리케이션 UI의 배치 에 대한 내용을 XML 형태로 정의하여 저장한다. res/layout 아래와 XML 형태로 저장되며, R.layout으로 접근이 가능하다.


Animation Resource (애니메이션 리소스)

안드로이드에서 애니메이션 기능을 구현하는데 사용되는 설정들이 저장된다. res/anim에 XML로 정의되며 R.anim 이라는 전역 변수를 통해서 접근이 가능하다.


Drawable Resource (그래픽 리소스)

res/drawable 디렉토리에 저장되는 gif,png와 같은 이미지나,  기타 화면에 그려지는 그래픽 관련 리소스들을 저장한다. 이미지 뿐만 아니라, 레이아웃 리스트등의 XML 등 여러 관련 리소스를 저장 하며 R.drawable 을 통해서 접근이 가능하다.


Color State List Resource (칼라 리소스)

res/color 디렉토리 안에 XML 형태로 저장되며, R.color 를 통해서 접근이 가능하다. 버튼이나 글자등의 색을 정의한다.


Menu Resource (메뉴 리소스)

res/menu 디렉토리 내에 XML 형태로 저장되며, Popup 이나 Option과 같은 메뉴를 정의하는 리소스들이 저장된다. R.menu를 이용하여 접근이 가능하다.


String Resource (문자열 리소스)

res/values에 XML 형태로 저장된다. 



위에서 간략하게 정리했지만, 각 리소스안에는 더 세부적인 설정들이 여러 파일로 저장된다. 조금 더 자세한 내용은 http://developer.android.com/guide/topics/resources/available-resources.html 를 참고하기 바란다.

저작자 표시 비영리
신고


안드로이드 인테트를 통해서 액티버티 생성하기


조대협(http://bcho.tistory.com)


요즘 일이 바쁘고 하는 역할도 바뀌어서 기술보다는 관리나 스크럼에 대한 글들을 읽고 공부하다보니, 반대적으로 기술에 대한 포스팅이 줄었다. 이래서는 안되겠다 싶어서, 어떤 부분을 봐야 할까 고민하다 보니 빅데이타와 앱 개발 기술쪽을 봐야겠다가 시작은 했는데, 막상 진도는 잘 안 나가고 있어서. 앱팀을 운영하고 있으니, 말이라도 통할려면 기본이라도 알아야 겠다 해서 안드로이드 앱쪽을 공부하고 있는데, 일단 해놓은 내용이라고 까먹지 않으려면 정리해놔야겠다는 생각에 간단하게 포스팅한다.


본 문서의 내용은 https://developer.android.com/training/index.html 를 기반으로 작성하였다. (문서에 있는데로 하면 생각보다 안되느 부분이 많다. 문서를 업데이트 하지 않아서 그런것 같은데). 

해당 프로그램은 화면에서 문자열을 입력 받아서, SEND라는 버튼을 누르면 다른 화면에서 입력 받은 문자열을 다른 화면에서 보여주는 예제이다 



MyActivity라는 액티비트에서 텍스트 박스를 통해서 문자열을 입력받고, SEND 버튼을 누르면, 텍스트 박스내의 문자열을 읽어서 Intent로 만들어서 DisplayActivity라는 액티비티로 전달하면 해당 문자열을 출력하는 내용이다.



먼저 인텐트의 개념에 대해서 이해할 필요가 있는데, 인텐트는 서로 다른 컴포넌트 간에 이벤트를 전달할 수 있는 개념이다. 위의 그림에서와 같이 MyActivity에서, DisplayActicity로 액티비티간에 출력할 문자열을 보내는것과 같은 이벤트이다. 유닉스의 IPC(Inter process call)과 같은 개념으로 생각을 하면된다. 

인텐트에 대한 자세한 내용은 https://developer.android.com/guide/components/intents-filters.html 를 참고하기 바라며, 나중에 다시 다루도록한다.


먼저, 문자열을 입력받을 화면을 작성한다. 안드로이드 스튜디오에서, 다음과 같은 레이아웃으로 작성을 한다. 



다음으로 버튼을 눌렀을때, 인텐트를 만들어서 보내기 위해서 버튼을 눌렀을때 실행되는 메서드를 정의하여 버튼 이벤트와 연결한다.

    public void sendMessage(View view){


        EditText editText = (EditText)findViewById(R.id.editText);

        String message = editText.getText().toString();


        Intent intent = new Intent(this,DisplayMessageActivity.class);

        intent.putExtra("com.example.terry.basicintentsample.MESSAGE",message);

        startActivity(intent);

    }


이 메서드는 새로운 인텐트를 생성하여 DisplayActivity라는 액티비티 클래스로 보내지는 인텐트가 된다.

인텐트를 통해서 보내지는 데이타는 “com.example.terry.basicintentsample.MESSAGE” 문자열을 키로 해서, EditText 텍스트 문자열 박스에서 읽어도 문자열을 값으로 해서 putExtra메서드를 이용해서 인텐트에 바인딩 된다. 

이 인텐트는 startActivity를 통해서 DisplayActivity 액티비티를 새롭게 시작하면서 같이 전달이 된다. 

위와 같이 코드를 작성하여,액티버티 뷰를 정의한 activity_my.xml에서 다음과 같이 버튼이 눌렀을때 작성한 “sendMessage”메서드가 호출되도록 정의 한다.

        <Button

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="SEND"

            android:id="@+id/button"

            android:layout_weight="1"

            android:onClick="sendMessage" /


인텐트를 만들어서 보내는 부분이 끝났으면, 이제 인텐트를 받아서 출력하는 부분을 구현해보도록 하자.

안드로이드 스튜디오에서,  새로운 액티비티를 생성한다.




생성된 액티비티에는 간단하게, “myMessage”라는 아이디로 TextView 하나만 배치 시킨다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    android:paddingBottom="@dimen/activity_vertical_margin"

    tools:context="com.example.terry.basicintentsample.DisplayActivity">


    <TextView android:text="@string/hello_world" android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:id="@+id/myMessage" />


</RelativeLayout>


다음으로 OnCreate 메세지에 다음과 같은 코드를 추가한다.

    @Override 

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);


        // Render View first

        setContentView(R.layout.activity_display);


        // Get the message from the intent

        Intent intent = getIntent();

        String message = intent.getStringExtra("com.example.terry.basicintentsample.MESSAGE");

        TextView textView = (TextView)findViewById(R.id.myMessage);

        textView.setText(message);


    }

위의 코드는 인텐트에서 문자열을 받아서 “myMessage”라는 id의 텍스트 뷰에 그 문자열을 출력하는 코드이다.

출력전에, setContentView를 먼저 호출해야 하는데, View를 렌더링한 후에 myMessage TextView가 생성된다. 

다음으로 getIntent() 메서드를 이용하면, 인텐트를 받을 수 있고, getStringExtra메서드를 이용해서 “com.example.terry.basicintentsample.MESSAGE” 키로 문자열을 받아서 TextView에 setText로 세팅한다.




저작자 표시 비영리
신고


Android View와 ViewGroup의 개념



View와 ViewGroup 객체를 이용해서 만듬.

View는 버튼이나 텍스트필드와 같은  UI Widget 이다. 

ViewGroup은 여러개의 View 또는 ViewGroup을 포함할 수 있는 컨테이너의 개념이며, ViewGroup을 상속하여 화면 배치 속성을 갖는 Layout을 가지고 있는 ViewGroup이 있다. ex)LinearLayout 등






<그림. ViewGroup과  View의 상하 관계>

출처 : https://developer.android.com/training/basics/firstapp/building-ui.html


이 ViewGroup은 res/layout/*.xml 에 XML 형태로 정의할 수 있다.

다음은 LearLayout 을 선언한 예이다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="horizontal" >

</LinearLayout>

View의 경우에는 보통 다음과 같은 형태로 표현되는데, 

<TextView

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:textAppearance="?android:attr/textAppearanceMedium"

    android:text="Medium Text"

    android:id="@+id/textView2" />

해당 뷰 컴포넌트를 식별하기 위한 id, 그리고 사이드를 정의하기 위한 layout_width,layout_height으로 정의한다. 위에서는 wrap_content로 정의했는데, 절대 크기가 아니라 화면에 나타난 뷰 크기만큼 맞추겠다는 정의이다. (안드로이드 디바이스는 해상도가 다양하기 때문에 절대 크기 보다는 위와 같은 상태 크기를 사용할 수 있도록 지원한다.)





저작자 표시 비영리
신고

안드로이드 Activity Lifecycle (액티버티 생명주기)와

, Back Stack & Task


조대협 (http://bcho.tistory.com)


Back Stack

Activity의 라이프 사이클 개념을 이해하기 전에, 먼저 BackStack이라는 개념을 이해해야 한다.
안드로이드 애플리케이션은 모바일 애플리케이션의 특성상 하나의 화면, 즉 Activity만 화면에 활성화가 되게 된다. 활성화 된 화면에서 작업하다가, Back 버튼등을 눌러서 이전화면으로 돌아갈 수 있는데, 이런 돌아가는 구조를 지원하기 위해서, 내부적으로 화면이 전환이 되더라도 기존의 화면을 없애는것이 아니라 메모리에 저장해놨다가, 돌아갈때 저장된 화면을 띄워 주는 구조가 된다
이런 구조를 구현하기 위해서 내부적으로 BackStack이라는 구조를 사용하는데, 각각의 화면이 Activity이기 때문에, 화면이 전환될때 마다, 그 Activity를  BackStack안에 저장한다. 그리고, Back 버튼등으로 돌아기기를 할 경우에는 Stack에서 하나씩 꺼내서 이전 화면으로 전환을 한다

아래 그림을 보자, 
  • 처음에 Activity 1 화면이 활성화 되었다.
  • 다음에 Activity 2가 실행되면, Activity 1이 BackStack에 저장된다.
  • 그 다음에 Activity 3이 실행되면, Activity 2가 BackStack에 추가적으로 저장되는 것을 볼 수 있다.
  • 이때 Activity 3가 Back 버튼등에 의해서 종료되면, Activity 3는 Destroy 되고, Back Stack에 있던 Activity2 가 활성화 된다.





Task

A task is a collection of activities that users interact with when performing a certain job. The activities are arranged in a stack (the back stack), in the order in which each activity is opened.

BackStack의 개념을 대략적으로 이해한후에, Task라는 개념을 이해하는게 좀 어려웠는데, Task하는 하나의 BackStack이라고 생각하면 된다. 그런데 왜 굳이 이 Task라는 개념을 사용하느냐? 아직은 잘 모르겠지만, 글들을 읽어보면 일단 키 포인트는 안드로이드 애플리케이션의 Activity는 서로 다른 application간에 공유가 가능하다.
즉, 내가 A라는 애플리케이션을 수행하다가, SNS 공유와 같은 기능을 사용할때, SNS 애플리케이션 B의 Activity들을 불러올 수 있다.  또는 이메일 클라이언트에서, 사진앱의 activity들을 불러서, 첨부 사진을 선택할 수 있다.

그렇다보니, 하나의 Activity는 동시에 여러개의 애플리케이션에 의해서 사용될 수 있기 때문에, 다른 개념을 사용하는데, 이를 Task 라고 한다. 현재 실행중인 애플리케이션의 Activity Stack은 이 Task라는 곳에 저장이 되게 된다.
(일종의 instance 개념과 유사한듯)


Activity의 라이프 사이클

Activity는 사용자와 Interaction이 있는 UI를 처리하는 컴포넌트로 다음과 같은 생명 주기를 갖는다. 각 주기마다 이벤트에 의해서 호출되는 함수들을 정리하면 다음과 같다.





  • OnCreate : 맨 처음 랩이 처음 실행되었을때, 실행되는 메서드로 UI 컴포넌트등을 로딩하고, Listner를 바인딩하고, 초기 데이타를 로딩하는 등의 초기화 작업을 수행한다.
  • OnStart : UI가 화면에 보이기 전에 바로 호출된다. UI가 로딩 된다고 해도, 사용자로 부터 Input을 받을 수 는 없다
  • OnResume : UI 로딩이 끝난후, 사용자 Input (Interaction)이 시작되기 전에 호출된다. 이 함수들이 다 호출되고 나면, 애플리케이션은 실행 가능 상태인 “Activity Running” 상태가 되며, UI도 모두 로딩되고, 사용자로 부터 입력을 받을 준비도 끝난다.
  • OnPause : 이 상태에서 만약에 다른 Activity 화면이 올 경우, OnPause가 호출된다.
    정확한 상태 정의는 “보이기는 하지만 사용자가 Interaction을 할 수 없는 상태” 정도로 정의할 수 있다. 이런 상태가 어떤 상태인가 하면, 다이얼로그등과 같은 다른 액티버티가 앞에 떠서 사용자 Interaction을 수행하는 상태이다 그러나 기존의 Activity는 그대로 뒤에 떠 있지만, 뒤에 떠 있는 activity 는 사용자 Interaction을 받지 못하는 상태이다.
 




이 때 사용중인 쓰레드 정리, 데이타 저장등을 수행하거나 또는 포커스를 잃은 화면이기 때문에 애니메이션등을 정지해야 한다.
  - 이때 다시 해당 Activity로 돌아가게 되면 OnResume으로 다시 돌아가서, 화면을 다시 호출하게 된다.
  - 화면이 보이지 않는 상태에서 만약에 메모리가 부족하게 되면 안드로이드 시스템에 의해서 이 단계에서 자동으로 정지(Killed) 될 수 있다. Killed 된 상태에서 다시 그 화면으로 돌아가게 되면 다시 onCreate로 돌아가서 앱을 처음부터 다시 시작하게 된다.

  • OnStop : 액티버티가 더이상 사용자에게 보여지지 않을때 호출된다. 
  • OnDestroy : 액티버티가 소멸될때 호출된다. 

Activities in the system are managed as an activity stack. When a new activity is started, it is placed on the top of the stack and becomes the running activity -- the previous activity always remains below it in the stack, and will not come to the foreground again until the new activity exits.

An activity has essentially four states:

  • If an activity in the foreground of the screen (at the top of the stack), it is active or running.
  • If an activity has lost focus but is still visible (that is, a new non-full-sized or transparent activity has focus on top of your activity), it ispaused. A paused activity is completely alive (it maintains all state and member information and remains attached to the window manager), but can be killed by the system in extreme low memory situations.
  • If an activity is completely obscured by another activity, it is stopped. It still retains all state and member information, however, it is no longer visible to the user so its window is hidden and it will often be killed by the system when memory is needed elsewhere.
  • If an activity is paused or stopped, the system can drop the activity from memory by either asking it to finish, or simply killing its process. When it is displayed again to the user, it must be completely restarted and restored to its previous state.

각 단계별로 실제로 돌아가는 걸 볼래면, 한번 만들어보고 이벤트 걸고 추적해봐야 겠다.





저작자 표시 비영리
신고

안드로이드 프로그래밍 구조의 기본


조대협 (http://bcho.tistory.com)


해당 포스팅은 https://class.coursera.org/androidpart1-005/lecture/13 의 내용을 바탕으로 정리하였습니다


안드로이드 애플리케이션의 주요 구성 요소

안드로이드 애플리케이션을 이루는 주요 구성 요소는 크게 다음 4가지이다.


  • Activity : 가장 메인이 되는 컴포넌트로, 모바일 앱의 특성상, 모바일앱은 하나의 UI가 떠서 사용자로 부터 입력을 받고, 출력을 담당한다. 즉 하나의 화면 인터페이스에 해당한다고 보면된다.
  • Service : 백그라운드에서 도는 컴포넌트로 UI가 없이 동작한다. 가장 쉬운 예로 음악 플레이 처럼 화면이 없는 상태에서 백그라운드로 도는 케이스가 가장 대표적인 예이다.
  • BroadCastReceiver : 이벤트를 처리하는 컴포넌트로, 안드로이드의 Intent를 받아서 처리한다. 이 Intent는 Pub/Sub형태로 바인딩되며, 특정 intent가 발생하면, 이를 subscribe하는 BroadCastReceiver가 이를 받아서 처린한다.
  • ContentsProvider : ContentsProvider는 일종의 Database를 추상화 해놓은 개념으로 , 단순히 데이타를 저장하는 것 뿐만 아니라, 저장된 데이타를 다른 앱간에 공유하는 기능도 지원한다.
안드로이드 빌드 & 배포 프로세스
안드로이드 프로젝트는 어떻게 빌드 및 패키징 되서, 단말에서 동작할까.
안드로이드 프로젝트를 빌드하게 되면 코드 컴파일 작업이 수행되고, 컴파일 후에는 컴파일된 코드 이외에 리소스를 포함하여 *.apk라는 파일로 패키징이 된다.
  • 패키징된 파일안에는 코드를 컴파일한 내용인 *.dex 파일이 생성되고
  • 컴파일된 리소스 파일이 *.arsc라는 파일로 저장된다.
  • 그리고 컴파일 되지 않은 리소스 (무엇일까?)가 같이 저장되고
  • 안드로이드 애플리케이션의 설정을 지정하는 AndoridManifest.xml 파일이 저장된다.

출처 : http://developer.android.com/tools/building/index.html


다음으로, 이  apk 파일을 싸이닝을 한다. 싸이닝(Signing)이란, 키를 이용하여 이 APK에 서명을 하는 작업으로, 이 APK 가 변조되었을 경우 서명 값이 틀려지게 되는데, 싸이닝은 apk가 타인에 의해서 위변조 되는 것을 방지하고, 내가 이 애플리케이션을 만들었다는 표시를 하는 과정이다. (나중에 애플리케이션이 업데이트가 되면 같은 키로 싸이닝을 해서, 내가 만든 애플리케이션임을 증명한다.)




저작자 표시 비영리
신고

안드로이드 플랫폼 기본 아키텍쳐


조대협 (http://bcho.tistory.com)


안드로이드 플랫폼의 기반 아키텍쳐를 살펴보면 다음 그림과 같다.




리눅스 커널
일단 가장 아랫단에, Linux 커널 이 올라가 있다. 일반적인 Linux 커널과 크게 다르지는 않지만, 모바일 디바이스에 최적화된 전력 관리 기능이나 안드로이드에 최적화된 Binder IPC (프로세스간 커뮤니케이션) 부분등이 포함되어 있다.

시스템 라이브러리
리눅스 커널위에는 C로 구현된 몇가지 네이티브 라이이브러리들이 올라가 있다. 3차원 그래픽을 위한, OPEN GL, 로컬 데이타 베이스를 제공하는 SQLLite 데이타 베이스, 웹 브라우징을 위한 WebKit, 멀티미디어 재생을 위한 Media Framework들이 올라가 있다. 
이러한 시스템 라이브러리들은 내부적으로 JNI 인터페이스를 통해서 자바 코드로부터 호출되게 된다. 

안드로이드 런타임
이러한 시스템 라이브러리 위에, 안드로이드 런타임이 올라가 있는데, 안드로이드 런타임은 JVM (Java Virtual Machine)이다. 단, 모바일 애플리케이션을 위해서 최적화된 JVM으로 안드로이드는 달빅(Dalvik)이라는 이름의 VM이 올라간다. 이 달빅 JVM이 실제로 안드로이드 애플리케이션을 시작하게 되낟.   그리고, 그위에 코어 자바라이브러리들이 올라가게 된다. (java.*, javax.* ,org.* ...등)


애플리케이션 프레임웍
안드로이드 런타임 까지 기본 JVM과 자바 라이브러리가 올라갔다면 애플리케이션 개발 프레임웍은 라이브러리이다. 마치 서버 개발에서 자바 위에, JEE 나 스프링,Hibernate와 같은 프레임웍이 있는 것 같이 애플리케이션 개발용 프레임웍이 올라가 있다. 
  • Package manager : 어떤 애플리케이션들이 설치되어 있는지를 관리한다. 
  • Windows manager : 윈도우 화면을 관리 (윈도우란, 영역으로 맨 윗부분의 네비게이션바, 다이얼로그 형식으로 나오는 윈도우등등 모든 윈도우 시스템을 관리하는 부분이다.)
  • View manager : 기본적인 그래픽 컴포넌트를 관리 한다. 라디오 버튼이나, 탭, 버튼등. 
  • Resource manager  : 컴파일이 되지 않는 리소스를 관리한다. 예를 들어 폰 애플리케이션에 같이 패키징된 string, 이미지 파일등을 관리한다. (안드로이드 프로젝트상 main/res 내에 있는 것을 관리하는 듯)
  • Activity manager : 안드로이드의 액티버티를 관리한다. 이 액티버티는 안드로이드 애플리케이션내의 하나의 화면에 해당(?)하는 것으로, 이 액티버터의 생성 및 소멸까지의 라이프 싸이클을 관리한다.
  • Contents provider: 데이타 저장소에 대한 추상화된 계층으로, 이 Contents Provider 계층을 통하여, 데이타를 저장할 수 있고, 이 저장소를 다른 애플리케이션에게 공유하여 애플리케이션 간에 데이타를 공유할 수 도 있다.  
  • Location manager : 위치 관련 서비스 기능을 제공한다.  
  • Notification manager : notification bar에 중요한 이벤트를 보여주는 기능을 제공한다. (푸쉬 시스템도 여기서 관리 하나?)


기본 애플리케이션

그위에, 기본적으로 폰에 프리로드 되어 설치되는 애플리케이션들이 존재한다. 연락처, 메신져, 브라우져, 카메라등의 기본적인 애플리케이션 등이 이에 해당한다. 


저작자 표시 비영리
신고

안드로이드 프로그래밍 시작하기 (둘러보기)


조대협 (http://bcho.tistory.com)


요즘 서버쪽 기술은 다소 시들해진것도 같고, 하는 일이 모바일 서비스인지라 (얼마전에도 그랬지만..) 알아야할것 같아서, 안드로이드 프로그래밍을 보기 시작했다. 
아무래도 서버 개발자 출신이다 보니 안드로이드 개발이라는 생소한 분야를 이 나이에 공부하면서 정리하는 내용이니 혹여나 내용이 허접하더라도.. 이해해주시기를.. (초보자 시각에서 정리한 내용입니다. )

구글에서 안드로이드 IDE인 안드로이드 스튜디오를 다운받아서 설치하면, 통합 개발 환경을 사용할 수 있다. 이 IDE는 보니, JetBrain의 IntelliJ를 기반으로 만들어진 환경이다. (구글이 이런 투자는 잘한듯..)
그러다 보니 이클립스를 쓰면서 불편했던 문제는 없이 제법 꽤 완성도가 높은 개발도구로 개발이 가능할 듯 하다.

스튜디오를 다운받아서 실행을 하고, 새로운 프로젝트를 만들면, 특이 한것이 다음과 같이, “Minimum SDK” 버전이라는 것을 고르는게 나온다. 즉, 어느 SDK버전으로 개발을 할것인지를 선택하는 것인데, 




이는 앱의 호환성과 기능 문제와 결부 된다. 너무 낮은 SDK버전으로 개발을 하게 되면 여러 앱을 커버할 수 있는 호환성은 높겠으나, 고급 기능을 사용할 수 없고, 높은 SDK 버전을 사용하게 되면 낮은 단말을 지원할 수 없어서 많은 사용자를 놓칠 수 있다.

위자드 상에서 가이드를 보면, 안드로이드 4.0.3 아이스크림 샌드위치 버전을 권장하는데, 이 버전을 사용하면 현존 단말이 90.4% 가 커버가 가능하다고 나온다.




약 10%가 커버가 안된다는 것인데, 말이 10%이지 100만 배포의 경우에는 10만대, 1000만 배포의 경우에는 100만대의 시장을 잃어버린다는 것이지 어마어마한 규모이다. (근대 2.X 대에서 요즘 앱이 잘돌아가나? 가벼운 앱이면 몰라도, 어느정도 기능이 있으면 어렵지 않을까??)

안드로이드 스튜디오에서 프로젝트를 생성하면 다음과 같은 파일 구조가 나온다. 몇몇 중요한 점만 짚고 넘어가보자




자바 소스 코드들은 main/java 디렉토리에 들어가 있고,
기타 리소스 파일들은 main/res 디렉토리에 들어가 있다. 일반적인 자바 프로젝트와 크게 다르지 않다
빌드 스크립트가 특이하게 gradle로 생성이된다. 예전에는 무엇으로 생성이 되었는지 모르겠지만, maven에 익숙한 나로써는 다소 챌린지이다.
그리고, proguard 관련 설정 파일이 생성되는 것으로 봐서, 기본으로 사용이 가능한거 같은데, proguard는 난독화 (소스 코드를 읽기 어렵게 만들어서 디컴파일시에 소스코드를 봐도 이해 하기가 어렵게 만드는 도구) 기능이 통합 되어 있는 듯하다.

몇몇 눈에 띄는 중요한 디렉토리와 파일들이 있어서 정리를 해보면 다음과 같다.

layout 파일 
모바일 프로그래밍은 GUI가 있는 만큼 역시나 안드로이드도 레이아웃 시스템을 사용한다. 예전 Java Swing이나, 현대 웹 프로그래밍의 BootStrap UI 프로임웍과 크게 다르지 않다. IDE에서 에디팅을 통해서 작성할 수 있기 때문에, 일반 컴포넌트를 사용하는 GUI 배치 구성은 layout이라는 파일에 작성하여 지정을 한다.

string.xml 파일
라벨등에 사용되는 기타 문자열들을 string.xml이라는 파일에 몰아서 저장해놓고 불러 쓸 수 있는데, 이는 Localization(지역화)를 위해서 디자인 되었다. 하나의 파일에 모든 텍스트를 지정해놓고 사용하고 다른 언어를 바꾸게 되면 이 string.xml 파일만 교체하면 되기 때문에, 지역화에 대응하기 좋은 구조이다.




그런데, 하나의 앱으로 여러 언어를 표현할 수 있는 Globalization(세계화)지원은 어떻게 하지? 



저작자 표시 비영리
신고