728x90

안드로이드 서비스 기능을 추가한 어플이 이상종료되는 증상이 발생한다.

코드 구현에서 고려하지 못한 사항이 있어서일까?

그래서 이번에 Service 에 대한 여러 자료를 참조하여 정리를 해둔다.


안드로이드 Application을 구성하는 4대 컴포넌트
1. Activity
2. Service : 백그라운드에서 동작하는 컴포넌트
3. Broadcast Receiver
4. Contents Provider


안드로이드에서 UI가 없어도 백그라운드에서 실행되는 동작이 필요할 때가 있다.
- 웹사이트 데이터 읽어오기 : AsyncTask 사용
- 음악재생, 전화수신 팝업 : Service 사용

Service는 Activity가 종료되어 있는 상태에서도 동작하기 위해서 만들어진 컴포넌트다.
앱이 종료되어도 서비스는 백그라운드에서 계속 실행된다.
만약 Service가 실행되고 있는 상태라면, 안드로이드 OS 에서는 해당 Process를 왠만하면 죽이지 않도록 방지하고 관리한다.
그렇기 때문에 메모리 부족이나, 특별한 경우를 제외하고는 Background 동작을 수행하도록 설계 되었다.
모든 컴포넌트(Activity, Service, Broadcast Receiver, Contents Provider)는 Main Thread(UI 작업을 처리해주는 Thread) 안에서 실행된다.



서비스 수명 주기.
왼쪽의 다이어그램은 서비스가 startService()로 생성된 경우의 수명 주기를 나타내며
오른쪽의 다이어그램은 서비스가 bindService()로 생성된 경우의 수명 주기를 나타낸다.

자세한 내용은 https://developer.android.com/guide/components/services.html 를 읽어보자.


- (액티비티와 같은) 컴포넌트가 startService()를 호출하면, 서비스는 "started" 상태가 된다.
서비스가 실행되면(started), 그 서비스를 실행한 컴포넌트가 종료되더라도 (할 일을 모두 마칠 때까지) 서비스는 종료되지 않는다.

- 일반적으로 서비스는 onCreate() → onStartCommand() → Service Running → onDestroy() 순으로 실행된다.

- onCreate() : 서비스에서 가장 먼저 최초 1번만 호출된다.

- Service 실행도중에 startService() 메서드를 실행하게 되면 Service의 onStartCommand() 메서드를 호출하게 한다.

- Service의 종료메서드인 stopService() 메서드를 호출하면 종료된다.

  stopService() 메소드를 호출하지 않으면 프로세스가 종료되더라도 다시 살아난다.

  서비스가 할 작업을 모두 마쳤을 때 stopSelf()나 stopService()를 호출하는 부분도 구현해야 한다.

- 프로세스에 의해 종료된 Service는 onCreate() -> onStartCommand() 순으로 실행된다.

- onStartCommand() 메서드의 3가지 return type

 START_STICKY

 Service가 강제 종료되었을 경우 시스템이 다시 Service를 재시작시켜 주지만 intent 값을 null로 초기화 시켜서 재시작

 START_NOT_STICKY

 시스템에 의해 강제로 종료된 Service가 재시작되지 않는다

 START_REDELIVER_INTENT

 Service가 강제 종료되었을 경우 시스템이 다시 Service를 재시작시켜 주며, intent 값을 그대로 유지시켜 준다.

- onDestroy() : 서비스가 종료될 때 실행되는 메소드



Service 구현
1. Service (*.java)를 만든다
2. AndroidManifest.xml 에 Service를 등록한다
3. Service 를 실행/중단하는 Intent 코드를 작성한다.


예제1.

- startService를 호출하면 서비스의 onStartCommand() 메소드가 동작된다.

- stopService 를 호출하면 서비스의 onDestroy() 메소드가 동작된다. 즉, 서비스를 종료시킨다.

<?xml version="1.0" encoding="utf-8"?>
<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="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_servicestart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_gravity="center"
        android:text="Service Start"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="30dp"/>

    <Button
        android:id="@+id/btn_servicestop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Service Stop"
        android:layout_gravity="center"
        android:gravity="center"
        android:textSize="14sp"
        android:textAllCaps="false"
        android:layout_marginTop="20dp"/>
</LinearLayout>


import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    Context mContext;
    private Button btnServiceStart;
    private Button btnServiceStop;

    private int mCount = 0;
    private Thread mThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;

        btnServiceStart = findViewById(R.id.btn_servicestart);
        btnServiceStop = findViewById(R.id.btn_servicestop);

        btnServiceStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "서비스를 시작합니다.", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(mContext,MyService.class);
                startService(intent);
            }
        });

        btnServiceStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "서비스를 종료합니다.", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(mContext,MyService.class);
                stopService(intent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG,"MainActivity onDestroy");
        stopService(new Intent(this,MyService.class)); // 현재 Activity가 종료되면 서비스 중지시킴
        super.onDestroy();
    }
}


import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    private int mCount = 0;
    private Thread mThread;

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(mThread == null){
            mThread = new Thread("My Thread"){
                @Override
                public void run() {
                    while (!Thread.currentThread().isInterrupted()){
                        try {
                            mCount++;
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            this.interrupt();
                        }
                        Log.e("My Thread", "서비스 동작 중 " + mCount);
                    }
                }
            };
            mThread.start();
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.e(TAG,"MyService onDestroy");
        super.onDestroy();
        if(mThread != null && mThread.isAlive() ){
            mThread.interrupt();
            mThread = null;
            mCount = 0;
        }

    }
}

실행결과

Stop 버튼 클릭시


Activity 종료시



Bound Service

startService() 메소드 대신 bindService() 메소드를 통해 시작되는 서비스를 서비스 바인딩이라 한다.

이 서비스는 Activity가 클라이언트 역할을 하고, 서비스가 서버 역할을 한다.

- Activity가 서비스에 어떤 요청을 하고, 서비스는 결과를 반환할 수 있다.

  앱 컴포넌트가 bindService()를 호출하면, 서비스는 "bound" 상태(바인드 된 상태)가 된다.

  바인드 된 서비스는, 앱 컴포넌트들이 서비스와 상호작용(요청 및 응답, 프로세스간 통신(IPC)) 할 수 있도록 해주는 클라이언트-서버 인터페이스를 제공해 준다.

- 하나의 서비스에 다수의 Activity(컴포넌트)가 바인드 될 수 있다.

- 서비스 바인딩은 연결된 Activity가 사라지면 서비스는 종료된다.


바인드 서비스 예제는 https://link2me.tistory.com/1763 에 있다.

블로그 이미지

Link2Me

,
728x90

안드로이드에서 설정(Setting) 정보를 변경하는 Activity 를 만들어 두어야 할 때 필요할 거 같아서 적어둔다.


준비사항

토글버튼 이미지 2개


toggle_drawable.zip


토글버튼 이미지 2개를 drawable 폴더에 복사하고, toggle_selector.xml 파일을 만든다.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/toggle_on" android:state_checked="true"/>
    <item android:drawable="@drawable/toggle_off" android:state_checked="false"/>

</selector>


activity_setting.xml 은 Layout을 정한다.

여러개의 환경설정 정보가 필요하다면 TableLayout 을 선택하는 것이 좋을 거 같다.

 <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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ToggleButton
        android:id="@+id/btn_toggle1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:background="@drawable/toggle_selector"
        android:checked="true"
        android:text=""
        android:textOff=""
        android:textOn="" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_toggle1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceMedium" />

</RelativeLayout>


SettingActivity.java

토글버튼을 선택함에 따라 SharedPreferences 에 값이 변경되도록 하여 관리정보를 알 수 있도록 한다.

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.ToggleButton;

public class SettingActivity extends Activity {

    SharedPreferences pref;
    ToggleButton btn_choice;
    TextView text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_setting);

        btn_choice = (ToggleButton) findViewById(R.id.btn_toggle1);
        text = (TextView) findViewById(R.id.textView1);
        
        pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        if(pref.getString("choice", "").equals("1")){
            text.setText("ON");
        } else {
            text.setText("OFF");
        }

        btn_choice.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
                if(isChecked == true){
                    text.setText("ON");
                    SharedPreferences.Editor editor = pref.edit();
                    editor.putString("choice", "1");
                    editor.commit();
                } else {
                    text.setText("OFF");
                    System.out.println("토글버튼 해제");
                    SharedPreferences.Editor editor = pref.edit();
                    editor.putString("choice", "0");
                    editor.commit();
                }
            }
        });

    }

}



블로그 이미지

Link2Me

,
728x90

안드로이드 코딩하면서 유용한 걸 적어둔다.


오늘날짜 구하기

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

SimpleDateFormat formater = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);
Date current = new Date();
String date = formater.format(current);
Toast.makeText(getApplicationContext(), "date ==="+date, Toast.LENGTH_SHORT).show();


소요시간 측정하기

걸리는 시간을 측정해서 대체 코드를 구현해야 할지 판단할 때 좋을 듯하다.

final SimpleDateFormat FORMATTER = new SimpleDateFormat("mm:ss.SSS");
long startTime = System.currentTimeMillis();
System.out.println("Time taken to insert " + FORMATTER.format(new Date(System.currentTimeMillis() - startTime)));


배열 선언

int[] apple = {20, 27, 24, 26, 32, 40};
위의 행은
int[] apple = new int[] {20, 27, 24, 26, 32, 40};
와 같다.


- apple.length는 배열의 길이를 의미

- 배열은 첨자(index, 인덱스)값으로 접근한다.
- 첨자값은 0부터 시작한다.


ArrayList 를 배열로 변환

toArray() 메소드를 이용한다.


ArrayList<Integer> pbook = new ArrayList<Integer>();
  pbook.add(1);
  pbook.add(2);
  pbook.add(3);
  pbook.add(4);

// List -> Integer 배열
Integer[] pArray = pbook.toArray(new Integer[pbook.size()]);

int i=0;
for(Integer temp : pbook){
 
pArray [i++] = temp;
}


ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Test1");
arrayList.add("Test2");
arrayList.add("Test3");

String[] array = new String[arrayList.size()];
int i=0;
for(String temp : arrayList){
  array[i++] = temp;
}


배열을 ArrayList 로 변환

// String[] -> List

String[] array = {"aaa", "bbb", "ccc"};

배열을 ArrayList 또는 List로 변환하는 것은 아래 3개중에서 한줄이면 된다.

List<String> list = Arrays.asList(array);
List<String> list = Arrays.<String>asList(array);
ArrayList<String> arrayList = new ArrayList<>(Arrays.asList(array));


List<String> list = Arrays.asList(str.split("\\s*,\\s*")); // 콤마로 구분해서 문자열을 넣어준다.

화면 출력하는 방법은

Iterator<String> ie = arrayList.iterator();
while (ie.hasNext()) {
    System.out.println(ie.next());
}


무식한 방법으로 for문을 사용하는 방법은

String[] array = new String[3];
array[0] = "Test1";
array[1] = "Test2";
array[2] = "Test3";
ArrayList<String> mList = new ArrayList<>();
for(String temp : array){
  mList.add(temp);
}



배열을 매개변수로 받는 메소드

public static int[] addNumArray(int[] pArray){
    for(int i=0; i < pArray.length; i++)
        pArray[i] += 10;
    return pArray;
}


ArrayList 검색

- ArrayList에 indexOf(Object o) 메소드를 사용하면 해당 값이 ArrayList에서 몇번째 위치해 있는지 찾아준다.

- indexOf 결과가 없으면 -1 을 반환한다.

- index 와 get 메소드를 이용하여 원하는 값을 찾아낼 수 있다.

- index 값이 -1 인 경우 get(indexOf(Object o)) 를 하니까 에러를 발생시킨다.

  이 경우에는 반드시 분리해서 처리를 해야 에러가 없이 동작된다.

String temp_key = "";
String temp_mNO = "";
String temp_oNO = "";
if(temp_mobileID.indexOf(ContactId) > -1){
    temp_key = temp_keyIDX.get(temp_mobileID.indexOf(ContactId));
    temp_mNO =temp_mobileNO.get(temp_mobileID.indexOf(ContactId));
}
if(temp_officeID.indexOf(ContactId)>-1){
    temp_oNO = temp_officeNO.get(temp_officeID.indexOf(ContactId));
}

- indexOf 는 동일한 값이 2개 이상 존재하면 가장 먼저 찾은 결과를 반환하기 때문에 결과가 부정확할 수도....


ArrayList 수정

ArrayList<String> arraylist = new ArrayList<String>();

arraylist.set(인덱스번호,"변경할 값");



문자열 비교

등위 연산자(==)

- 같은 자료형을 비교하면 같다(true)

- 기본 자료형과 참조형을 비교하면 다르다(false)


equalsIgnoreCase(), equals()

- 기본 자료형과 참조형을 비교하면 같다(true)

  주소 비교가 아니라 값을 비교한다.

- equals() 는 대소문자를 구분하여 비교한다.

- equalsIgnoreCase()는 대소문자를 구분하지 않고 비교한다.



블로그 이미지

Link2Me

,
728x90

반응이 너무 느려서 서버에서 데이터를 가져오는데 걸리는 시간을 측정하는 코드를 추가했다.

0.5 ~ 0.6초 정도 걸리는데 Local 에서 가져온 데이터를 처리하는 로직 문제인지 너무 오래 걸린다.


long start, end; // 서버에서 가져오는 시간 측정
start = System.currentTimeMillis();
end = System.currentTimeMillis();
System.out.println("get JSONData Time :" + (end - start)/1000.0);

블로그 이미지

Link2Me

,
728x90

안드로이드 PUSH 서버, 메신저 만드는 방법을 검색해보다가 알게된 내용을 적어둔다.


아파치 웹서버

nginx

  • 쓰레드/프로세스 기반 구조
  • 클라이언트의 요청이 들어오면 쓰레드를 생성
  • 사용자가 많으면 많은 쓰레드 생성 ==> 메모리 및 CPU 낭비, Context-Switching Overhead 발생
  • 비동기(async) 이벤트(ioctl, send, recv, epoll)기반 구조
  • 다수의 연결을 효과적으로 처리
  • 대부분의 코어 모듈이 Apache보다 적은 리소스로 더 빠르게 동작


안드로이드 앱의 구성 요소와 데이터 전달




  • 제작 : 기획부터 개발까지 하나의 서비스를 온전히 만들어내는 것
  • 개발 : 결정된 기획과 디자인을 바탕으로 소프트웨어를 구현하는 것
  • 고객이 불편한 부분을 이해하고 체감해야 한다.
  • UI 쓰레드는 UI만 처리하게 하라.
  • 다양한 화면 해상도를 지원하라
  • 화면 해상도에 맞게 다양한 리소스를 제공하라.
  • 네트워크는 항상 느리다고 가정하라.
  • 다양한 하드웨어에 대비하라. (터치스크린, 키보드, 센서)
  • 좋은 코드를 작성하라 (CPU 사용량 낮고, 메모리 사용량 낮고, 가독성은 높게)
    - 필요한 기능을 최대한 간결히 작성하라
    - 클래스/메소드/변수 이름을 명확히 하라.
    - 코드를 어렵게/복잡하게 작성하지 마라.
  • 객체는 최대한 적게 생성하라
    - static 메소드를 사용해서 객체 생성을 줄일 수 있다.
    - 반복문에서는 객체 생성을 줄여야 한다.
    - 객체를 재사용한다.
  • 불필요한 코드를 제거하라
    - 쓸데없는 캐스팅을 줄여야 한다.
  • 메소드는 정적(static) 메소드로 선언하라.
  • 클래스 내에서는 Getter 와 Setter 를 사용해서 변수에 접근하지 말라.
  • 값이 고정된 변수는 상수로 선언하라.
  • enum은 되도록 사용하지 말아라.
  • 부동소수형은 되도록 사용하지 말아라.
    정수형이 부동소수형보다 일반적으로 2배 더 빠르다.
  • 네티브 코드는 되도록 사용하지 말아라.
    - 디바이스에 따라 호환되지 않을 수 있다.
    - 디버깅을 하는 것이 쉽지 않다.
  • 자바의 코드 노하우가 반드시 안드로이드의 코드 노하우는 아니다.
    - 가독성을 위해 중첩 클래스를 사용하지 마라.
    - 클래스는 되도록 작게 생성하라.
  • 버전은 어떤 범위까지 지정할 것인가?
    - 지원하려는 플랫폼 구버전의 범위를 지정하는 것은 재량이지만,
      최신 버전의 플랫폼을 지원하는 것은 기본으로 한다.
  • 오픈소스로 공개된 많은 커스텀된 GUI 컴포넌트 코드들이 있으며, 이를 구글 검색 등을 통해 쉽게 찾아낼 수 있다. https://github.com/wasabeef/awesome-android-ui
  • 안드로이드 내부에서 기본 이미지를 제공하지만 매우 제한적이다.
    구글이 Material Design 풍의 아이콘 셋들을 오픈소스로 공개했다.
    https://github.com/google/material-design-icons/releases
  • 안드로이드 앱 개발할 시에는 다양한 버전에서의 테스트가 필요한데, Genymotion을 이용하면 쉽게 해결할 수 있다. https://www.genymotion.com/
  • 출시할 때에는 꼭 앱이 디버깅용이 아닌 출시용으로 빌드가 됐는지 확인하고, 서버도 디버깅 모드로 배포되었는지 여부를 확인한다.
  • 모든 준비가 완료되었다면, Play 스토어 등과 같은 모바일 어플리케이션 마켓 플레이스에 배포를 진행하면 된다.


블로그 이미지

Link2Me

,
728x90

// Back 버튼을 눌러도 종료되지 않게 처리
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if( keyCode == KeyEvent.KEYCODE_BACK ) {
        return true;
    }       
    return super.onKeyDown(keyCode, event);
 }


// Back 버튼을 누르면 어플 종료여부 확인 처리
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if( keyCode == KeyEvent.KEYCODE_BACK ) {
        new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle("Quit").setMessage("어플을 종료하시겠습니까?").setPositiveButton("Yes", new DialogInterface.OnClickListener() {
            @Override
            public void onClick( DialogInterface dialog, int which) {
                moveTaskToBack(true); // 본 Activity finish후 다른 Activity가 뜨는 걸 방지.
                finish();
                //application 프로세스를 강제 종료
                android.os.Process.killProcess(android.os.Process.myPid() );
            }
        }).setNegativeButton( "No", null ).show();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}




블로그 이미지

Link2Me

,
728x90

어플 기능을 추가하면서 메모리 누수 문제로 고생을 좀 했다.

그냥 무심코 넘긴 Activity 생명주기가 얼마나 중요한지에 대해 새삼 알게되었다.


Activity 생명주기

안드로이드 생명주기(수명주기)란 Activity 의 상태정보가 변하는 것이다.

안드로이드 응용 프로그램은 PC용과 달리 화면이 작으므로 동시에 여러개의 Activity(화면)이 나올 수 없다.

화면 하나만 활성화된 상태이고, 나머지는 모두 비활성화된 상태로 남게 된다.


- 응용 프로그램이 시작되면 onCreate(), onStart(), onResume()가 차례로 수행되고, MainActivity 화면이 나온다.


  홈 버튼을 눌러서 Activity를 백그라운드로 보내버리거나, NewActivity가 실행되어 화면 전환이 될 때

  onPause()  --> onStop() 이 두 메소드가 연달아 호출되며 백그라운드 스택으로 내려간다.

 

- NewActivity 를 요청하면 오른쪽 방향의 onPause(), onStop()이 수행되고, MainActivity는 중지되며 NewActivity 화면이 나온다.

- New Activity 의 사용을 종료하면 onRestart(), onStart(), onResume()가 수행되고 다시 MainActivity 화면이 나온다.


- MainActivity 를 끝내면 아래 방향의 onPause(), onStop(), onDestroy()가 차례로 수행되고 응응 프로그램이 종료된다.


상태 메소드

 설명

 onCreate()

 - Activity 가 처음 만들어졌을 때 호출됨

 - 화면에 보이는 뷰들의 일반적인 상태를 설정하는 부분

 - 이전 상태가 저장되어 있는 경우에는 번들 객체를 참조하여 이전 상태 회복 가능

 - 이 메소드 다음에는 항상 onStart() 메소드가 호출됨

 onStart()

 - Activity가 화면에 보이기 바로 전에 호출됨

 - Activity가 화면상에 보이면 이 메소드 다음에 onResume() 메소드가 호출됨

 - Activity가 화면에서 가려지게 되면 이 메소드 다음에 onStop() 메소드가 호출됨

 onResume()

 - Activity가 사용자와 상호작용하기 바로 전에 호출됨

 onRestart()

 - Activity가 중지된 이후에 호출되는 메소드로 다시 시작하기 바로 전에 호출됨

 - 이 메소드 다음에는 항상 onStart() 메소드가 호출됨

 onPause()

 - 또다른 Activity를 시작하려고 할 때 호출됨

 - 저장되지 않은 데이터를 저장소에 저장하거나 애니메이션 중인 작업을 중지하는 등의

   기능을 수행하는 메소드임

 - 이 메소드가 리턴하기 전에는 다음 Activity가 시작될 수 없으므로 이 작업은 매우 빨리

   수행된 후 리턴되어야 함.

 - Activity가 이 상태에 들어가면 시스템은 Activity를 강제 종료할 수 있음

 onStop()

 - Activity가 사용자에게 더 이상 보이지 않을 때 소멸됨
 -
Activity가 소멸되거나 또다른 Activity가 화면을 가릴 때 호출됨
 -
Activity가 이 상태에 들어가면 시스템은 Activity를 강제 종료할 수 있음

 onDestroy()

 - Activity가 소멸되어 없어지기 전에 호출됨

 - 이 메소드는 Activity가 받는 마지막 호출이 됨

 - Activity가 애플리케이션에 의해 종료(finish)되거나 시스템이 강제로 종료시키는 경우에

   호출될 수 있음.

 - 위의 두가지 경우를 구분할 때 isFinishing() 메소드를 이용함

 - Activity가 이 상태에 들어가면 시스템은 Activity를 강제 종료할 수 있음.



Activity stack 처리하는 방법

Activity를 그냥 intent생성해서 만들면 계속해서 새로 Activity를 start하고 스택으로 쌓이는 문제가 발생한다.
예를 들어서 A, B, C Activity를 A->B->C->A->C->B로 이동하고 뒤로가기를 누르면 다시 화면이 B->C->A->C->B->A 순으로 왔던 순서 반대로 나타난다.
문제점 검토
1. 뜨면 안될 화면도 뜨게 된다.(접근해선 안되는 이전화면으로 돌아가버린다)
2. 그냥 다시 뒤로가면 되는 화면인데 새로운 화면을 만들어서 그 위에 쌓아나간다.(이전 화면의 Activity를 새로 생성해서 위에 쌓는다)

해결방법
1. 접근해선 안되는 이전화면으로 돌아가버리는 문제
   해당 Activity 의 JAVA 소스 파일에서 intent를 생성한 바로 다음에
   intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); //기존에 쌓여있던 스택을 모두 없앤다.
   intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // task를 새로 생성한다
   를 추가해준다.

2. 이전 화면의 Activity로 돌아가야하는 문제
   intent.addFlags(intent.FLAG_ACTIVITY_CLEAR_TOP); // 현재 Activity 없애고 이전 화면을 새로운 화면으로 지정


3. 어플 실행도중 전화가 왔을 때의 처리

    onResume() 와 onPause() 에서 관련 코드 추가

    @Override
    public void onResume() {
        super.onResume();  // Always call the superclass method first
        if(mediaPlayer != null && mediaPlayer.isPlaying() == false){
            mediaPlayer.start();
        }
        // 전화통화가 끝나면 멈춤상태 음악이 다시 플레이된다.
    }

    @Override
    public void onPause() {
        super.onPause();  // Always call the superclass method first
        // 홈버튼을 누르면 백그라운드로 들어가면서 재생되는 음악이 멈춘다.
        // 전화를 받으면 재생되던 음악이 자동으로 멈춘다.
        if(mediaPlayer != null && mediaPlayer.isPlaying() == true){
            mediaPlayer.pause();
        }
    } 



※ 참고로 Activity 생성시에 사용되는 Intent Flag 정리는 아래 블로그에 잘 정리되어 있다.

    http://theeye.pe.kr/archives/1298


    http://androidhuman.com/260 Activity 생애주기 샘플코드 및 설명



아래 사항은 여기에 추가하는 것이 적절한 거 같아서 콘솔 앱을 만들면서 적용한 걸 적어둔다. (Updated 2019.5.17)


서비스에서 Activity를 띄울 때는 인텐트에 플래그를 주어야 하며, 메인 Activity가 이미 메모리에 만들어져 있는 경우에는 메인 Activity의 onNewIntent() 메서드로 데이터가 전달된다.


@Override
protected void onNewIntent(Intent intent) {
    // If the activity has already been created/instantiated, the event will arrive through the 'onNewIntent()' method
    super.onNewIntent(intent);
    connectUsb();
    LayoutMode();


Intent intent = new Intent(Setting.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);

FLAG_ACTIVITY_SINGLE_TOP 은 Activity를 재사용 하기 위해서, 필요없는 Activity를 제거하기 위해서 FLAG_ACTIVITY_CLEAR_TOP 을 같이 조합하였다.
이런경우 MainActivity 에서는 onNewIntent()를 override 해서 intent를 받아야 한다.
호출되는 순서는 onCreate() 대신에 onNewIntent()가 호출되고 다음으로 onResume() 이 호출된다.

처음 startActivity 호출시
1. onCreate  → 2. onStart
3. onResume

해당 Activity가 실행된 상태에서  FLAG를 추가하고 다시 startActivity 호출시
1. onNewIntent
2. onResume

블로그 이미지

Link2Me

,
728x90

Avtivity 화면에서
XML로 정의되어 있는 View 와 Layout을 객체화 시키는 메소드가 inflate 다.
inflate 메소드를 통해서 XML 리소스 정보를 해석하여 View를 생성하고 rootView를 리턴한다.
리턴된 rootView를 setContentView() 라는 메소드를 통해 보여줄 수 있다.
setContentView를 이용하면 XML 레이아웃을 인플레이션 한 후 화면에 보여주는 기능을 하기도 하고, 인플레이션 된 위젯을 화면에 구성하는 기능을 한다.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);



XML 로 작성된 메뉴(옵션 메뉴, 컨텍스트 메뉴)는 inflate(팽창 → 프로그래밍 객체로 변환) 하면 실제 메뉴가 생성된다.


기본적인 사용 패턴은

// 1. inflater 얻어오기

MenuInflater inflater = getMenuInflater();

LayoutInflater inflater = getLayoutInflater();

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


// 2. View inflate 하기 (XML 파일과 연결 (R. 으로 시작되는 resource 파일들만 inflate 가능))

inflater.inflate(R.menu.mymenu, menu);

View v = (View) inflater.inflate( R.layout.inflate_example, parent, false );


// 3. 화면에 표시하기

setContentView( v );


다시 정리하면

LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE );
View v = (View) inflater.inflate( R.layout.inflate_example, parent, false );
setContentView(v);



Main View 에 부분 xml 을 가져와서 보여주는 것에 대한 예제를 보자.


LinearLayout add_layout = (LinearLayout) findViewById(R.id.addLayout);
// 인플레이션
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.add_layout,add_layout,true); 



=== activity_main.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:id="@+id/txt01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="부분 레이아웃"
        android:layout_marginTop="30dp"
        android:gravity="center_horizontal"
        android:textSize="20dp"
        android:textColor="#a88" />

    <Button
        android:id="@+id/btn1"
        android:layout_gravity="center_horizontal"
        android:text="Add"
        android:textAllCaps="false"
        android:textSize="20dp"
        android:onClick="btn_addlayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:id="@+id/addLayout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </LinearLayout>
</LinearLayout>


부분 Layout 을 추가할 add_layout.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">

    <Button
        android:id="@+id/btnTest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="동의 토글"
        android:textSize="20dp"
        android:textStyle="bold" />

    <RadioGroup
        android:id="@+id/rg1"
        android:orientation="horizontal"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:layout_marginRight="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <RadioButton
            android:id="@+id/rb1"
            android:text="독서"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_marginRight="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <RadioButton
            android:id="@+id/rb2"
            android:text="여행"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_marginRight="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <RadioButton
            android:id="@+id/rb3"
            android:text="스포츠"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_marginRight="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <RadioButton
            android:id="@+id/rb4"
            android:text="영화"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </RadioGroup>

    <LinearLayout
        android:gravity="center"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:text="동의"
            android:textSize="20dp"
            android:layout_margin="10dp"
            android:textColor="#f00"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <CheckBox
            android:id="@+id/agree"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>


이제 MainActivity.java 파일에 inflation 을 시킨다.

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 버튼을 클릭했을 때 동작처리 메소드
    public void btn_addlayout(View view){
        addLayout();
    }

    // add_layout.xml 을 activity_main.xml 에 추가하는 메소드
    private void addLayout(){
        LinearLayout add_layout = (LinearLayout) findViewById(R.id.addLayout);

        // 인플레이션
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.add_layout,add_layout,true);

        // add_layout.xml 에 버튼 객체 참조
        Button btnTest = (Button) findViewById(R.id.btnTest);
        final CheckBox agree = (CheckBox) findViewById(R.id.agree);

        btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(agree.isChecked()){
                    agree.setChecked(false);
                } else {
                    agree.setChecked(true);
                }
            }
        });

    }
}



블로그 이미지

Link2Me

,
728x90

사용자가 버튼을 클릭하면, 클릭 이벤트가 발생한다.

Button 요소에 onClick 속성을 추가한다.

클릭 이벤트가 발생하면 onClick 속성에 저장된 메소드가 호출된다.

 

<Button
        android:id="@+id/btn1"
        android:layout_gravity="center_horizontal"
        android:text="Add"
        android:textAllCaps="false"
        android:textSize="20dp"
        android:onClick="btn_addlayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

 

 

MainActivity.java 에 클릭 이벤트를 처리하는 메소드를 정의한다.

onClick 명을 btn_addlayout 이라고 했으므로 클릭 이벤트 이름도 동일하게 해주어야 한다.

onClick 속성에 선언하는 메소드는 public 이어야 하고, void 형을 가지며, View 를 메소드의 인수로 가진다.

 

// 버튼을 클릭했을 때 동작처리 메소드
public void btn_addlayout(View view){
    addLayout();
}

 

 

다른 방법은 가장 흔하게 사용하는 방법이다.

이 방식으로 코딩하는 걸 추천한다.

Button button = (Button) findViewById(R.id.btn1);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        addLayout();
    }
});

 

코틀린 에서 처리 방법

val product_name = findViewById<TextView>(R.id.product_name)
val product_count = findViewById<TextView>(R.id.product_count)


product_name.setOnClickListener { _ ->
    
}

 

Android Studio 3.6 이상에서 ViewBinding 처리 방법을 권장하고 있다.

https://link2me.tistory.com/1974 을 참조하시라.

블로그 이미지

Link2Me

,
728x90

프로세스(process)는 '실행중인 프로그램'이다. 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.

프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며, 프로세스 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.

그래서 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재한다.


안드로이드는 multi-threading 을 지원한다. 즉 하나의 애플리케이션은 동시에 여러가지 작업을 할 수 있다. 안드로이드는 자바의 multi-threading 을 그대로 지원한다.

개발자들은 하나의 애플리케이션 안에 동시에 실행되는 여러 Thread를 만들 수 있으며, 이 Thread 들은 자바 Virtual Machine 에 의해 동시에 실행된다.


애플리케이션이 구동되면 안드로이드 시스템은 애플리케이션을 위한 실행 Thread를 생성한다. 이 Thread는 Main Thread 라고 불리며, UI(user interface) Thread라고 불리기도 한다. Main Thread 는 사용자 인터페이스 위젯에 이벤트를 전달하거나 화면을 그리는 작업을 담당하고 있다.
동일한 Process 안에서 실행된느 모든 컴포넌트는
Main Thread 안에서 실행된다.

그리고 컴포넌트의 시스템 호출도 Main Thread 안에서 이루어진다.

안드로이드 UI는 기본적으로 싱글 쓰레드 모델로 작동한다.
안드로이드의 애플리케이션을 실행하면 시스템은 AndroidManifest.xml 파일에 기록된 최초의 액티비티를 메모리로 올려 프로세스를 만든다. 이때 Main Thread 가 자동으로 생성된다.


안드로이드 Thread 모델은 자바의 Thread 모델을 그대로 따른다.


쓰레드를 구현하는 방법은

Thread Class 를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법이 있다.


Thread Class 를 상속받으면 다른 Class를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적이다. Runnable 인터페이스를 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있어 보다 객체지향적인 방법이라고 할 수 있다.


Runnable 인터페이스를 구현한 경우, Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread 클래스의 생성자의 매개변수로 제공해야 한다.

Runnable runnable = new Runnable();
Thread thread = new Thread(runnable);
thread.setDaemon(true); // 메인 쓰레드 종료시 같이 종료처리
thread.start();
 


쓰레드를 생성했다고 해서 자동으로 실행되는 것은 아니다. start()를 호출해야만 쓰레드가 실행된다.

하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다.

한번 실행이 종료된 쓰레드는 다시 실행할 수 없다.

실행중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.


Thread 안에서 직접적으로 UI(user interface) 위젯을 변경하면 안된다.

동기화 문제를 처리하기 위해 안드로이드에서는 Main Thread 에서만 UI 작업이 가능하도록 제한했다.

// Runnable 인터페이스를 구현한 클래스
class Runnable implements java.lang.Runnable {
    @Override
    public void run() {
        while(true){
            backValue++;  // 작업스레드 값 증가
            // 쓰레드 안에서 UI를 조작하면 에러가 발생한다.
            backText.setText("작업스레드 Value: " + backValue);
            try {
                Thread.sleep(10000); // 1000ms 단위로 실행(멈춤)
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
} // end class Runnable
 


UI Thread가 아닌 다른 Thread 에서 UI를 조작하면, UI 툴킷에 접근하지 말라는 원칙을 위배해서 오류를 발생시킨다.

이 문제를 해결하기 위해서 안드로이드는 다른 Thread(Sub Thread) 에서 UI Thread(Main Thread) 에 접근하는 많은 방법을 제공하고 있다.

- Handler : 일반적인 Thread 간 상호 작용을 위한 클래스

  work Thread가 여러개이고, 서로 비슷한 메시지를 보내야 한다면
  핸들러 클래스를 정의하는 것이 낫다.

- view.post(Runnable())  : UI Thread의 메시지큐에 작업을 추가. 해당 view 객체에 접근 가능한 경우에 사용

- View.postDelayed(Runnable())  : 일정 시간후에 작업처리

- Activity.runOnUiThread(Runnable())  : 핸들러 없이 UI Thread 의 메시지큐에 작업을 전달(post)

  runOnUiThread() 메소드는 Runnable 객체를 넘겨 받는데,
  넘겨 받는 코드가 백그라운드 Thread 이거나 UI Thread 인지 상관없이 무조건 UI 스레드에서 실행

- AsyncTask  : http://link2me.tistory.com/1031 참조



여러가지 방법중에 Handler 를 이용하는 방법에 대해 살펴보자.

UI 를 조작하기 위해서는 Handler와 Looper를 이용해 다른 Thread에서 Main Thread로 UI 처리 작업을 전달하던지, Main Thread 내에서 자체적으로 처리하던지 해야한다.

<이미지 출처 : http://itmining.tistory.com/5>


Handler

안드로이드에서는 Main Thread 를 통해서만 화면 UI를 변경할 수 있기 때문에 핸들러를 통해서 Main Thread 에 접근하여 UI를 수정할 수 있다.

- 핸들러를 생성하는 Thread 만이 다른 Thread 가 전송하는 Message와 Runnable 객체를 받을 수 있다.

- 핸들러가 처리하는 대상은 메시지(Message)와 런어블(Runnable) 객체이며 단일 쓰레드에 단 하나의 루퍼와 메세지 큐가 존재하지만, 핸들러는 다수 존재 가능하다.

- Handler 객체를 새로 생성하해당 쓰레드의 Message Queue에 바인드(bind)된다.

- 자료구조에서 메시지를 쌓아두는 공간이 MessageQueue 이다.

- Looper는 MessageQueue가 비어있는 동안은 아무 행동도 안하고 메시지가 들어오면 해당 메시지를 꺼내 적절한 Handler로 전달한다.

- 쓰레드 간의 통신을 위해서 메시지가 필요하다면 핸들러의 obtainMessage() 메서드를 사용할 수 있다.
obtainMessage() 메서드는 각 메서드에 따라 what, arg1, arg2, obj 등을 초기화할 수 있는 기능을 제공한다
-
Runnable 인터페이스는 run() 추상 메서드를 가지고 있으므로 상속받은 클래스는 run()코드를 반드시 구현해야 한다.


Handler 를 구현하는 방법은 http://netrance.blog.me/110130561171 에 잘 설명되어 있다.


1. 안드로이드는 핸들러를 구현할 수 있도록 Handler라는 베이스 클래스를 제공한다.
2. Handler 가 받을 메시지들의 종류를 결정한다.
3. 자식 Handler 클래스를 어디에 구현할지 결정한다.
   - Activity 등과 같은 특정 클래스의 내부
   - 메소드 내 (익명 클래스로 구현)
   - 별도의 자바 파일
4. 메시지의 종류가 무엇인지 식별한다.
   메시지 코드들을 정의한다.
   // Message Codes
   private static final int MSG_DAY_CHANGED = 1;
   private static final int MSG_MONTH_CHANGED = 2;
   private static final int MSG_YEAR_CHANGED = 3;

5. Message를 전달하는 방식
1. sendMessage() - 메시지를 바로 전달
2. sendMessageAtTime() - 메시지를 특정시간에 전달
3. sendMessageDelayed() - 메시지를 일정시간이 지난뒤에 전달
4. sendMessageAtFrontQueue() - 딜레이된 메시지가 있는경우 가장 앞에 전달


 Handler Class를 생성하여 workThread 내용을 갱신처리

 - 버튼을 클릭하면 Runnable 객체 생성한 Thread가 시작되어 workThread 값이 계속 증가된다.

 - 증가된 값은 Handler 객체를 통해 UI Thread에 값을 표시한다.

Thread_handler-01.zip

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    int mainValue = 0;
    int workValue = 0;
    TextView mainText;
    TextView workText;
    Button button;

    ProgressHandler progressHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainText = (TextView)findViewById(R.id.mainvalue);
        workText = (TextView)findViewById(R.id.backvalue);
        button = (Button) findViewById(R.id.btn_increase);

        progressHandler = new ProgressHandler();
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startCount();
            }
        });
    } // end of onCreate

    private void startCount(){
        mainValue++;

        // 2. Runnable 객체  생성으로 구현
        Runnable runnable = new Runnable();
        Thread thread2 = new Thread(runnable);
        thread2.setDaemon(true);
        thread2.start();

        mainText.setText("메인스레드 값: " + mainValue );
    }

    class ProgressHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0){
                // 메인 Thread의 UI 내용 변경
                workText.setText("BackValue:" + workValue);
            }
        }
    }

    // Runnable 인터페이스를 구현한 클래스
    class Runnable implements java.lang.Runnable {
        @Override
        public void run() {
            while(true){
                workValue++;  // 작업스레드 값 증가
                // 쓰레드 안에서 UI를 조작하면 에러가 발생한다.
                //workText.setText("작업스레드 Value: " + workValue);

                // 메인에서 생성된 Handler 객체의 sendEmptyMessage 를 통해 Message 전달
                progressHandler.sendEmptyMessage(0);

                try {
                    Thread.sleep(3000); // 1000ms(1초)이므로 3초 단위로 실행
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    } // end class Runnable

}


progressHandler.sendEmptyMessage(0); 대신에
Message msg = progressHandler.obtainMessage();
progressHandler.sendMessage(msg);
를 쓸 수 있다.


 익명 클래스를 이용한 Handler 객체 생성하여 workThread 내용을 갱신 처리 

Thread_handler-02.zip

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    int mainValue = 0;
    int backValue = 0;
    TextView mainText;
    TextView backText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainText = (TextView)findViewById(R.id.mainvalue);
        backText = (TextView)findViewById(R.id.backvalue);

        // Runnable 객체 생성으로 구현
        Runnable runnable = new Runnable();
        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.start();
    } // end of onCreate

    public void OnClick(View v){
        mainValue++;
        mainText.setText("메인스레드 값: " + mainValue );
    }

    // Main Thread 에서 Handler 객체를 생성한다.

    // Handler 객체를 생성한 쓰레드 만이 다른 쓰레드가 전송하는 Message 나 Runnable 객체를 수신 가능

    // 익명클래스를 이용한 Handler 객체를 생성
    Handler handler = new Handler(){ //클래스 내부의 클래스, 재사용하지 않을 클래스를 만들때 주로 사용
        // Handler 객체는 handlerMessage() 를 오버라이딩 하여 Message 를 수신
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 0){   // Message id 가 0 이면
                backText.setText("BackValue:" + backValue); // 메인스레드의 UI 내용 변경
            }
        }
    };

    // Runnable 인터페이스를 구현한 클래스
    class Runnable implements java.lang.Runnable {
        @Override
        public void run() {
            while(true){
                backValue++;  // 작업스레드 값 증가
                // 쓰레드 안에서 UI를 조작하면 에러가 발생한다.
                //backText.setText("작업스레드 Value: " + backValue);
                // 메인에서 생성된 Handler 객체의 sendEmptyMessage 를 통해 Message 전달
                handler.sendEmptyMessage(0);


                try {
                    Thread.sleep(1000); // 1000ms(1초) 단위로 실행
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    } // end class Runnable
}
 


Handler 와 WorkerThread 사용하는 방법


 import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    Button handlerStartButton;
    Button handlerStopButton;
    Handler mainThreadHandler;
    TextView handlerExampleTextView;
    int workValue = 0;
    boolean running = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread);

        handlerStartButton = findViewById(R.id.btn_start);
        handlerStopButton = findViewById(R.id.btn_stop);
        handlerExampleTextView = findViewById(R.id.tv);
        mainThreadHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what == 1){
                    handlerExampleTextView.setText("BackValue:" + workValue); // 메인스레드의 UI 내용 변경
                }
            }
        };

        handlerStartButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = true;
                startCount();
            }
        });
        handlerStopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = false;
                workValue = 0;
            }
        });
    }

    private void startCount(){
        // Start a child thread when button is clicked.
        WorkerThread thread = new WorkerThread();
        thread.start();
    }

    private class WorkerThread extends Thread{
        @Override
        public void run() {
            super.run();
            while(running){
                workValue++;  // 작업스레드 값 증가
                try {
                    java.lang.Thread.sleep(1000); // 1000ms(1초)이므로 1초 단위로 실행
                    System.out.println(java.lang.Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Create a message in child thread.
                Message childThreadMessage = new Message();
                childThreadMessage.what = 1;
                childThreadMessage.arg1 = workValue;
                // Put the message in main thread message queue.
                mainThreadHandler.sendMessage(childThreadMessage);
            }

        }
    }
}


블로그 이미지

Link2Me

,
728x90

기본 알림창인 AlertDialog 다이얼로그

AlertDialog 는 사용자에게 메세지나 경고를 알리기 위한 기능으로 Android 에서 지원하는 Dialog 이다. Toast 와는 다르게 Dialog 라서 Activity의 Focus를 가져간다.

=== activity_main.xml ===
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tistory.link2me.app9.MainActivity">

    <Button
        android:id="@+id/btnAlert"
        android:layout_centerInParent="true"
        android:text="대화상자 보기"
        android:textSize="20dp"
        android:onClick="btnClicked"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="대화상자를 표시하려면 아래 버튼을 누르세요"
        android:textSize="15dp"
        android:textColor="#999"
        android:gravity="center_horizontal"
        android:layout_above="@id/btnAlert"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</RelativeLayout>


=== MainActivity.java ===

package com.tistory.link2me.app9;

import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView textView;
    String msg;
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.tv01);
    }

    public void btnClicked(View view){
        AlertDialog alertDialog = makeDialog();
        alertDialog.show();
    }

    // 알림 대화상자 생성
    private AlertDialog makeDialog(){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("알림");
        builder.setMessage("항목중에 하나를 선택하세요.");
        builder.setIcon(android.R.drawable.ic_dialog_alert);

        builder.setPositiveButton("확인", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                msg="OK 버튼 선택 : "+ Integer.toString(which);
                textView.setText(msg);
            }
        });

        builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                msg="NO 버튼 선택 : "+ Integer.toString(which);
                textView.setText(msg);
                dialog.dismiss();
            }
        });

        // Cancel 버튼 설정
        builder.setNeutralButton("CANCEL", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                msg="CANCEL 버튼 선택 : "+ Integer.toString(which);
                textView.setText(msg);
            }
        });

        // Builder 클래스의 create() 메소드를 호출하여 대화상자 생성
        AlertDialog dialog = builder.create();

        return dialog;
    }
}



블로그 이미지

Link2Me

,
728x90

동영상 강좌를 수강하면서 보니까 Android Studio 2.1.2 버전과 2.2.3 버전의 단축키 사용법이 좀 다른가보다.

동영상 강좌에서는 단축키를 사용해서 코드를 생성하는 방법이 나오지 않는다.

오프라인 강의를 통해서 Android Studio 강의를 들었던 단축키 사용법이 없다.


Android Eclipse 기반으로 작년도 6월에 잠깐 연습을 해보고 나서 회사 업무때문에 Android 독학하는 것이 어렵다는 생각에 포기하고 있다가 올해 2월에 야간 강의를 들으면서 다시 Android Studio 연습하면서 간단하게 정리를 해두려고 한다. 전문적인 개발이 아니다보니, 업무를 이것저것 하다보면 금새 잊어버리게 된다.







7번까지 실행하고 나면 자동으로 아래 화면이 만들어진다.



여기까지가 기본적인 Android Studio 사용법을 위한 준비과정이다.

이제 Layout 부분과 MainActivity.java 파일에 내용을 추가하면 된다.


Exercise_app1.zip


Android Studio 파일은 실행파일을 생성하고 나면 파일 사이즈가 상당히 커서 파일을 전부 올릴 수가 없다.

그래서 핵심적인 것만 첨부한다.

이 첨부된 코드를 보면서 필요한 부분을 복사해서 붙여넣기 해가면서 테스트하면 된다.


Java 의 정석 책도 같이 보고 있다.

DoIt 안드로이드 앱 프로그램 책의 동영상 강좌에 보면, Java 를 몰라도 Anroid 배울 수 있다고 설명한다.

하지만 Java 에 대한 기본개념이 약하면 응용력이 생길수 없다.

PHP도 잘 하는 것은 아니지만 프로그램을 좀 해보면서 느끼는 것은 배열 다루는 것이 정말 중요하다는 걸 많이 배웠다. Java 에서는 Class 개념과 배열, ArrayList 에 대한 개념이 강해야 할 거 같아서 Java 정석 책을 병행 공부중이다.


public class MainActivity extends AppCompatActivity

- C++에서는 여러 클래스로부터 상속받은 다중상속을 허용하지만, 자바에서는 단일 상속만을 허용한다.


접근제어자

- private : 같은 클래스 내에서만 접근이 가능하다.

- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다.

- public : 접근 제한이 전혀 없다.


==== MainActivity.java ====

import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import static android.R.attr.data;

public class MainActivity extends AppCompatActivity {

    public static final int REQUEST_Code = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = (Button) findViewById(R.id.btn_01);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // new 액티비티로 전환
                Intent intent = new Intent(getApplicationContext(),NewActivity.class);
                intent.putExtra("name","mike");
                startActivityForResult(intent,REQUEST_Code);
            }
        });

        // 버튼의 배경색 지정
        btn.setBackgroundColor(Color.parseColor("#FF00FF"));
    }

    // new 액티비티에서 응답을 받았을 때 처리사항
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUEST_Code){
            Toast.makeText(getBaseContext(), "요청코드:"+resultCode, Toast.LENGTH_SHORT).show();

            if(resultCode == Activity.RESULT_OK){
                String name = data.getExtras().getString("name");
                Toast.makeText(getBaseContext(), "응답이름:"+name, Toast.LENGTH_SHORT).show();
            }
        }
    }
}




==== NewActivity.java ===

import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class NewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new);

        Intent intent = getIntent();
        String name = intent.getExtras().getString("name");
        Toast.makeText(getApplicationContext(), "onCreate 호출됨:"+name, Toast.LENGTH_SHORT).show();

        Button btn = (Button) findViewById(R.id.backBtn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "돌아가기 버튼이 눌렸어요.", Toast.LENGTH_LONG).show();
                close();
            }
        });
    }

    // Back 키를 눌렀을 때 액티비티를 닫고 메인화면으로 이동
    public boolean onKeyDown(int keyCode, KeyEvent key){
        if(keyCode == key.KEYCODE_BACK){
            close();
            return true;
        }
        return false;
    }

    protected void close(){
        // 응답값을 전달하기 위한 인텐트 생성
        Intent resultIntent = new Intent();
        resultIntent.putExtra("name","Link2me");

        // 응답값을 전달
        setResult(Activity.RESULT_OK,resultIntent);
        finish(); // 액티비티 닫기(현재 화면을 종료)
    }
}



keyCode

 상수 설명 
 KEYCODE_DPAD_LEFT  왼쪽 이동키
 KEYCODE_DPAD_RIGHT  오른쪽 이동키
 KEYCODE_DPAD_UP  위쪽 이동키
 KEYCODE_DPAD_DOWN  아래쪽 이동키
 KEYCODE_DPAD_CENTER  이동키 중앙의 버튼
 KEYCODE_A  알파벳 A (B부터는 KEYCODE_B 방식)
 KEYCODE_0  숫자 0 (1부터는 KEYCODE_1 방식)
 KEYCODE_CALL  통화
 KEYCODE_ENDCALL  통화종료
 KEYCODE_HOME  홈
 KEYCODE_BACK  뒤로
 KEYCODE_VOLUME_UP  볼륨증가버튼
 KEYCODE_VOLUME_DOWN  볼륨감소버튼


==== activity_main.xml ====

본인이 생성하는 부분과 달라지는 부분이 뭔지부터 알아보자면 색깔이 표시된 부분이 다르다.

Android Studio 2.2.3 에서는 자동완성 기능이 워낙 뛰어나서 코드 작성이 정말 편하다.

개념을 잘 이해하고 있다면 쉽게 코드를 작성할 수가 있겠더라.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.a05rg.app4.MainActivity">

    <Button
        android:id="@+id/btn_01"
        android:layout_centerInParent="true"
        android:text="new activity make"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

블로그 이미지

Link2Me

,
728x90

Intent에서 putExtra를 해주게 되면 값을 넘겨 주고 getIntent를 이용해서 값을 받아 사용할 수 있다


Intent intent = new Intent(this, ClassB);
String[] telNumArr = new String[] {"test", "test2"};
intent.putExtra("strings", telNumArr);
startActivity(intent);


public void onCreate() {
  Intent intent = getIntent();
  String[] telNumArr = intent.getStringArrayExtra("strings");
}


Intent intent = new Intent(context,SendSMS.class);
intent.putExtra("mobileNoArr", telNumArr);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 스택에 기존에 사용하던 Activity가 있다면 그 위의 스택을 전부 제거해 주고 호출
context.startActivity(intent);


배열의 길이는 telNumArr.length

System.out.println(telNumArr.length);


배열에서 일정한 개수로 분할해서 처리해야 할 경우에 사용하는 함수

copyOfRange
int[] subArray = Arrays.copyOfRange(array, startIndex, endIndex);
int[] array = new int[]{1, 2, 3, 4, 5};
int[] subArray = Arrays.copyOfRange(array, 1, 3);

결과 : subArray = [2, 3]


1. 가져올 배열의 개수를 구한다.
   Array.length;


2. 배열 개수를 25개로 나누어 몫을 구한다.

   int limitCnt= 25;

   Array.length /limitCnt ;
   3항 연산자 : 몫의 개수 =
(Array.lenght % limitCnt == 0) ? Array.lenght / limitCnt : Array.lenght / limitCnt + 1;


3. 몫의 개수만큼 foreach 문을 돌린다.
   - String[] newArray = Arrays.copyOfRange(oldArray, startIndex, endIndex);
   for(i=0;i<몫의개수;i++){
    startIndex = i*
limitCnt ;
    int endIndex = startIndex + limitCnt <= telNumArr.length ? startIndex + limitCnt : telNumArr.length;

    String[] subArray = Arrays.copyOfRange(Array, startIndex, endIndex);

    System.out.println(subArray);
   }



기타 addFlags

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);



블로그 이미지

Link2Me

,
728x90

연습하는 과정에서 중도에 이름을 변경해서 다시 하느라고 이름이 좀 다르다.

IntentBasedWebView 가 아니라 그냥 WebView 만드는 과정이다.

IntentBasedWebView 라는 항목이 나오는 걸 없애는 것도 알고 싶은데 아직은 모르겠다. (ActionBar 제거하는 메소드로 처리하면 됨)

티스토리, 네이버, 블로그 라는 글자만 누르면 하단에 내용이 바뀌도록 구현했다.

기능을 추가해보면서 발생하는 문제점 등이 뭔지 파악도 하면서 배우는 중이다.

내용은 처음 작성하고 나서 계속 학습하면서 내용을 보완하고, 하단에 추가하는 방식으로 작성하고 있다.

남들을 위한 강의가 아니라 내가 이걸 배워가는 과정을 기록하고 있으니까 ^^




1 . WebView를 보여줄 준비과정


2. Layout 구성 과정

LinearLayout 으로 변경했다.

완벽하게 내것으로 소화를 하려면 좀 더 연습이 필요하다.


첨부된 파일은 activity_main.xml 과 MainActivity.java 파일이다.

mywebview_example.zip


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:layout_width="match_parent"
    android:layout_height="match_parent"   
    android:orientation="vertical" >   
   
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
      
         <TextView
             android:id="@+id/TextView01"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:gravity="center"
             android:padding="20dip"
             android:text="티스토리"
             android:textColor="#000000" />

         <TextView
             android:id="@+id/TextView02"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:gravity="center"
             android:padding="20dip"
             android:text="네이버"
             android:textColor="#000000" />

         <TextView
             android:id="@+id/TextView03"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:gravity="center"
             android:padding="20dip"
             android:text="블로그"
             android:textColor="#000000" />
       
    </LinearLayout>       
   
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        >     
   
        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"           
            android:scrollbars="none"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:adjustViewBounds="true"
            android:scaleType="fitStart"           
            />         
   
    </LinearLayout>
   
</LinearLayout>


3. 퍼미션 설정


4. WebView 코딩과정

package com.example.mywebview;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.TextView;

public class MainActivity extends Activity {
    private WebView mWebView;
    private TextView TextView01;
    private TextView TextView02;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);       


        // ActionBar 제거하기
        ActionBar actionbar = getActionBar();
        actionbar.hide();   


        setLayout("http://link2me.tistory.com"); // default webview       
       
        TextView01 = (TextView) findViewById(R.id.TextView01);
        TextView02 = (TextView) findViewById(R.id.TextView02);       
       
        TextView01.setOnClickListener(onclick1);
        TextView02.setOnClickListener(onclick2);
        findViewById(R.id.TextView03).setOnClickListener(onclick3);  // 이런 방법으로도 가능하다는 거      
    }
   
    OnClickListener onclick1 =new OnClickListener() {
        @Override
        public void onClick(View v) {
            String url = "http://www.tistory.com";
            setLayout(url);           
        }       
    };
   
    OnClickListener onclick2 =new OnClickListener() {
        @Override
        public void onClick(View v) {
            String url = "http://m.naver.com";
            setLayout(url);           
        }       
    };
   
    OnClickListener onclick3 =new OnClickListener() {
        @Override
        public void onClick(View v) {
            String url = "http://link2me.tistory.com";
            setLayout(url);           
        }       
    };

    @SuppressLint("SetJavaScriptEnabled")
    private void setLayout(String url) {
        mWebView = (WebView) findViewById(R.id.webview);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.setWebViewClient(new WebViewClient()); // 이걸 안해주면 새창이 뜸
        mWebView.loadUrl(url);
    }
       
}


화면 확대 축소 옵션

webView.getSettings().setBuiltInZoomControls(true);
webView.setDisplayZoomControls(false);


버튼 또는 TextView 를 클릭하면 다른 Activity 로 이동하게 하고 싶은 경우

이 부분에서 에러가 발생한다.

<check_framebuffer_attachment:854>: Invalid texture format! Returning error!

검색해도 해결책을 구할 수가 없다.


OnClickListener onclick2 =new OnClickListener() {
        @Override
        public void onClick(View v) {
            //switch(v.getId()){
                //case R.id.TextView02:
                    Toast.makeText(getApplicationContext(), "네이버 사이트로 이동합니다.", Toast.LENGTH_LONG).show();
                    Intent intent = new Intent(MainActivity.this, ButtonActivity.class);
                    startActivity(intent);
                    //break;
            //}
        }       
    };


이렇게 주석처리를 해가면서 해보니 Toast 메시지만 출력하는 것은 문제가 안된다.

그 다음 Intent 처리하는 부분에서 잘못된거 같은데 아직 왜 그러는지 원인을 모르겠다.


안드로이드 OnClick 메소드에 대해 설명이 잘된 사이트

http://nanstrong.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B0%95%EC%9D%9807-AndroidOnClick%EB%A5%BC-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-4%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95



OnClickListener 메소드 부분은 아래와 같이 다시 보완했다.(2016-06-19 일)

ButtonActivity.java 와 button.xml 파일을 생성해서 테스트를 해봤더니 Intent 로 화면이 전환되는 것을 확인했다.

개념이 부족한 상태에서 화면을 복사해서 대략 붙여넣기하면서 수정을 했더니 뭔가 꼬인 부분이 있는거 같다.

아직 Layout 만들고 화면에 불러서 처리하는 부분에 대한 지식이 부족해서인가 보다.


.setJavaScriptEnabled - 웹뷰에서 자바 스크립트 사용
.setSupportZoom - 손으로 확대, 축소를 할 수 있도록 사용
.setBuiltInZoomControls - WebView 내장 Zoom 사용
.onPageStarted() - 웹뷰에서 url이 로드 될때 호출 되는 함수
.onPageFinished() - 웹뷰에서 url 로딩이 완료되면 호출 되는 함수
WebViewClient의 shouldOverrideUrlLoading 함수는 웹뷰 내에서 웹 페이지를 돌아다니기 위해서 사용
.onJSAlert() -  웹에서 띄우는 팝업 창 같은 것을 웹뷰에서 보여주기 위해 사용



블로그 이미지

Link2Me

,
728x90


모바일 플랫폼은 저성능 CPU,  용량이 작은 메모리, 적은 화면 크기, 배터리 용량 등 제약조건이 있는 환경이다. 따라서 윈도우 기반의 PC/노트북  실행환경과는 프로그램 처리 방식이 다르다.


안드로이드 앱(APP)은 여러개의 Activity들로 구성된다.

안드로이드에서는 앱을 실행시키면 실행되는 단위가 Activity 이다.

하나의 Activity 에서 다른 Activity 를 시작하려면 startActivity()를 호출한다.

안드로이드는 새로운 Activity 가 시작될 때마다 Activity 를 스택에 추가한다. 사용자가 back 키를 누르면 현재 Activity는 스택에서 제거되고 스택에 저장된 이전 Activity로 되돌아간다. 즉, 이전 Activity가 실행 Activity로 복귀된다.


안드로이드 장치에서 사용자와 상호 작용하는 객체(object)는 View 클래스이다.
버튼을 클릭했더니 화면이 바뀐다. 이것은 하나의 Activity 에서 또다른 Activity 로 전환되는 것이다.

안드로이드에서는 Activity 간에 정보 전송을 Intent 라는 메커니즘을 사용하여 한다.

안드로이드는 사용될 Activity 를 AndroidManifest.xml 에 꼭 등록해줘야 한다.

메인 Activity는 자동 등록되지만, 추가한 Activity는 별도로 직접 등록해줘야 한다. (Android Studio 에서는 자동 추가됨)


앱(application) 하나를 만들 때 화면에서 다른 화면으로 데이터가 전송되고 다른 화면에서 데이터를 받는 것을 처리하는 과정을 살펴보자. (테스트 첨부파일은 제일 하단에 있음)


Intent 에는 명시적 인텐트와 암시적 인텐트가 있다.

Intent(Context, Class) 형태는 명시적 인텐트다.


MainActivity.java

 public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.btn01);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,NewActivity.class);
                intent.putExtra("key","값전달 : 90");

                intent.putExtra("age", 32);
                startActivity(intent);
            }
        });
    }
}

 NewActivity.java

 public class NewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new);

        // startActivity 로 보낸 Intent 를 getIntent 메서드를 호출하여 가져온다.
        Intent intent = getIntent();
        // MainActivity 의 putExtra로 지정했던 key 값
        String key = intent.getExtras().getString("key");

        int age = intent.getExtras().getInt("age");

        TextView textView = (TextView) findViewById(R.id.tv_received);
        textView.setText(key);
    }
}


코드의 실행순서

1. NewActivity 인스턴스가 생성된다.

2. 이 인스턴스가 현재 태스크 스택의 최상단에 푸시된다.

3. 액티비티가 시작되며 포그라운드로 가져온다.




// 화면 하단에 글자가 잠깐 나왔다가 사라지는 기능(Toast)

Toast.makeText(getApplicationContext(), "주소록에 저장되었습니다.", Toast.LENGTH_LONG).show();


// 화면에 나타낼 View를 지정, XML Layout의 내용을 메모리상에 객체화하는 역할

// Activity 화면 전체를 설정하는 역할을 한다.

setContentView(R.layout.activity_main); 


// id가 button1 인 것을 찾아 참조변수 button1 에 저장.

Button button1 = (Button) findViewById(R.id.button1); 


// 무명(anonymous) class로 이벤트를 처리 (이벤트를 처리하는 클래스는 다른 곳에서는 전혀 필요 없음)

button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(getApplicationContext(), "번호 저장버튼이 눌렸습니다.", Toast.LENGTH_LONG).show();
    }           
});


invalidate();  // View 의 화면을 다시 그리게 하는 메소드



※ 다른 Activity 로 데이터를 보내기만 할 경우에는 startActivity(intent); 메서드를 사용하면 된다.

    응답을 받으려면 startActivity가 아니라 반드시 startActivityForResult를 호출하여 Activity를 실행해야 한다.


MainActivity.java

 public class MainActivity extends AppCompatActivity {

    // requestCode 정의
    public static final int REQUEST_CODE = 101;
    TextView received_text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        received_text = (TextView) findViewById(R.id.tv_received);

        Button button = (Button) findViewById(R.id.btn01);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,NewActivity.class);
                intent.putExtra("key","값전달 : 90");
                startActivityForResult(intent,REQUEST_CODE);
            }
        });
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent intent){
        if(requestCode == REQUEST_CODE){
            Toast.makeText(getBaseContext(), "응답 요청코드:"+requestCode +" ,
            결과코드:"+resultCode, Toast.LENGTH_LONG).show();
            if(resultCode == RESULT_OK){
                String key = intent.getExtras().getString("newkey");
                Toast.makeText(getBaseContext(), "응답 key :"+ key, Toast.LENGTH_LONG).show();
                received_text.setText("응답 key :"+ key);
            }
        }
    }
}

 NewActivity.java

 public class NewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new);

        // startActivity 로 보낸 Intent 를 getIntent 메서드를 호출하여 가져온다.
        Intent intent = getIntent();
        // MainActivity 의 putExtra로 지정했던 key 값
        String key = intent.getStringExtra("key");

        TextView textView = (TextView) findViewById(R.id.tv_received);
        textView.setText(key);

        Button prevBtn = (Button) findViewById(R.id.btn_replay);
        prevBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {               

                Intent resIntent = new Intent(NewActivity.this,MainActivity.class);
                resIntent.putExtra("newkey","3000");

                // 응답을 하기 위한 메소드
                setResult(RESULT_OK, resIntent);
                finish(); // 현재 화면을 종료(다른 화면을 나타내기 위해)
            }
        });
    }
}



2. 암시적 인텐트

약속된 Action 을 지정하여 안드로이드에서 제공하는 기존 응용 프로그램을 실행하는 것이다.

- 전화번호 입력(Dial)
- 전화 걸기 (Call)
- 오디오 파일 불러오기
- 전화번호부 데이터 불러오기


public void onClick(View v) {
  Intent intent = new Intent(Intent.ACTION_CALL); // 전화를 즉시 걸어주는 기능
  intent.setData(Uri.parse("tel:01067604567"));

  if(intent.resolveActivity(getPackageManager()) != null){

      startActivity(intent);

  }

}



public void onClick(View v) {
  Intent intent = new Intent(Intent.ACTION_DIAL); // 전화번호를 표시만 해주는 기능
  intent.setData(Uri.parse("tel:01067604567"));
  startActivity(intent);
}



public void onClick(View v) {
   Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
   intent.setType("audio/*");
   startActivityForResult(intent, 0);
}


AndroidManiFest.xml 파일에 아래의 퍼미션을 추가해줘야 한다.
<uses-permission android:name="android.permission.CALL_PHONE" />


전화를 거는 동작은 액션으로 ACTION_CALL을 가지고, 전화번호를 입력하는 동작은 액션으로 ACTION_DIAL을 가지며, 두 액션 모두 데이터(Data)로 전화를 걸거나 입력할 번호를 필요로 한다.

보통, 데이터에는 실제 데이터가 아닌 데이터의 주소(URI; Uniform Resource Identifier)가 들어가는데, Dialer, 웹 브라우저, 구글맵 어플리케이션 등에서는 위의 데이터처럼 실제 값을 받게 된다.


다른 어플리케이션 상에 존재하는 Acitivity를 수행 시키고 싶은 경우가 있다.
이런 경우 ComponentName을 이용하여 해당 어플리케이션의 Activity를 호출 가능하다.
ComponentName의 인자로 들어가는 packageName은 패키지명
- 예) com.tistory.link2me.addresschart
ComponentName의 인자로 들어가는 className은 패키지명을 포함하는 클래스명
- 예) com.tistory.link2me.addresschart.MyActivity

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ComponentName name = new new ComponentName(String packageName, String className);
intent.setComponent(name);
startActivity(intent);



3. 배열 데이터를 전달하고 싶은 경우

다른 Activity로 넘길때 데이터를 옮길 수 있게 아래의 코드와 같이 getter, setter가 있는 Value Object를 준비한다.
그리고 직렬화 할 수 있도록 Serializable 상속한다.


public class Person implements Serializable {
    private static final long serialVersionUID = 789722909905522166L;
   
    String name;
    int age;
   
    // 생성자 추가
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
   
    // 마우스 우클릭하여  Source --> Generate Getters and Setters ... 선택 (Eclipse 방법)

    // 마우스 우클릭하여 Generate (Alt + Enter)를 선택하고 Getter, Setter 선택(Android Studio)
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }   

}

Serializable(직렬화)는 객체의 내용을 바이트 단위로 변환하여 파일 또는 네트워크를 통해서 스트림(송수신)이 가능하게 하는 것이다.
 - 객체 직렬화를 하기 위해서는 먼저 객체의 Serializable 인터페이스를 구현해야 한다.
 - ObjectInputStream 클래스는 객체를 직렬해제 하는 기능을 제공해 주고 있다.
   . 파일에 저장되어 있는 객체
   . 네트워크를 통해 직렬화되어 전달된 객체
 - ObjectOutputStream 클래스는 객체들을 출력하는 기능을 제공해 주고, 출력 스트림에 출력하기 전에 직렬화를 수행한다.


 보내는

 Activity

 // 보낼 데이터 생성
 Person person = new Person();
 person.setName("홍길동");
 person.setAge(25);

 // 명시적 인텐트 생성 및 보낼 객체 담기
 Intent intent = new Intent(getApplicationContext(), TargetActivity.class);
 intent.putExtra("OBJECT", person);

 // 액티비티 시작
 startActivity(intent);

 받는

 Activity

 Intent intent = getIntent();
 Person person = (Person) intent.getSerializableExtra("OBJECT");


보내는

 Activity

 Intent intent = new Intent(this, ClassB);
 String[] myStrings = new String[] {"test", "test2"};
 intent.putExtra("strings", myStrings);
 startActivity(intent);

 받는

 Activity

 Intent intent = getIntent();
 String[] myStrings = intent.getStringArrayExtra("strings");



마지막으로 새로운 Activity를 작성하면 반드시 AndroidManifest.xml 파일에 등록하여 안드로이드 시스템이 찾을 수 있도록 해야 한다.

Android Studio 에서는 java 파일과 xml 파일을 한꺼번에 생성한 경우에는 AndroidManifest.xml 파일에 자동 등록된다.


----------------------------------------------------------------------------------------------------------------------------------


intent_src.zip


Android Studio 는 main 폴더 파일만 복사하면, 내가 생성하여 연습하는 코드에 붙여넣기가 쉽다.

블로그 이미지

Link2Me

,
728x90

Spinner 기능은 HTML 의 select 와 유사하다.


Drop down list 로 보여지는 기능을 http://android-town.org/ 사이트에 나오는 DoIt 안드로이드 동영상 강좌를 보고 따라서 해보고 구글링을 해서 약간 변경을 해본 것이다.

동영상 강좌를 보면서 배우는 가장 좋은 점은 eclipse 사용법인 거 같다.

이 사이트에 나오는 동영상은 예제 중심으로 연습하면서 안드로이드가 이런 것이구나 하도록 알려주는 데 효과가 좋은거 같다.

책은 들여다봐도 아직은 감이 잘 잡히지는 않는다. 동영상을 들어야만 비로소 이해가 된다고나 할까...


SQL 교육을 받을 때에도 계속 연습을 시키는데 처음에는 왜 개념 완전히 잡히게 설명안해주고 실습부터 시킬까 하는 의구심이 들었던 적이 있다.

하지만 계속 실습을 하면서 아하 이렇구나 하고 도움이 되기는 하더라.

물론 나중에 다시 복습하고 개념이 이런 것이구나 하고 개념이 잡히는 순간에 활용이라는 걸 할 수 있게 되었지만 말이다.


내것으로 만들기 위해 간단하지만 적어두고 나중에 추가하여 완전히 내것으로 소화를 할 것이다.

이것만으로는 응용할 수는 없다. DB 자료와 연동하여 처리가 될 때 이 기능의 가치가 발휘될 것이다.

개념파악이 되고 나면 다시 이 자료에 살을 붙여서 추가를 할 것이다.


Graphical Layout 에서는 세밀한 조정을 할 수가 없다.

세부적인 화면 구성을 하려면 XML 파일에서 직접 적어줘야 된다는 걸 알았다.

따라서 Layout 에 대한 의미를 제대로 파악하고 있어야 한다.



아래 Graphcal Layout 의 의미를 파악하려면 XML 소스를 살펴보면 된다.


@ : 리소스에서 참조한다는 의미

+ : 새로 생성한다는 의미

id : 식별자를 나타내는 패키지

textView1 : 식별자


XML Layout 에 정의된 View들은 앱이 시작될 때 메모리 상에서 Inflation 이라는 객체화 과정을 거쳐 만들어진다. 메모리상에 만들어진 객체를 참조하기 위해 id 를 지정한다.

id 값은 유일해야 한다. id가 중복되면 에러가 발생한다.

컴파일할 때 eclipse  JUNO 버전에서 문제가 없었는데 eclipse LUNA 버전에서는 에러 메시지가 나오는 걸 확인했고 이 원인으로 인해서 삼성 갤럭시 S7 폰에서 앱이 동작이 안되는 걸 알게되었다.

eclipse Mars 버전에서는 기존 기능이 제대로 동작 안되는 현상도 겪어서 eclipse LUNA 버전 중심으로 코드 연습을 하고 있다.




setContentView(R.layout.activity_main);  // 보여주는 View 가 어떤 Layout 인지 알 수 있다.


spinner1 = (Spinner) findViewById(R.id.spinner1); // id가 spinner1 인 것을 찾아 참조변수 spinner1 에 저장한다.


list 는 C#과 비슷한 거 같은데 세부적인 것은 아직 파악하지 못한 상태다.

list.add 를 하면 계속 사용자를 추가할 수 있고, 중간 끼어넣기 등이 편리하다.

이렇게 직접 처리하는 것보다는 DB 연결해서 처리하는 영역이 될 것이므로 이 부분은 나중을 위해 그런가보다 참고만 하면 된다.



연습하며 작성한 소스코드

MySpinner.zip


참고 : http://www.tutorialspoint.com/android/android_spinner_control.htm 에 가면 설명이 잘되어 있다.


간단한 예제 읽어보기 좋은 곳 http://www.mkyong.com/tutorials/android-tutorial/



Audio Player 만들어보면서 추가한 재생속도 조절 부분만 일부 발췌한 것이다.


public class Player extends AppCompatActivity implements View.OnClickListener {
    // speed values displayed in the spinner
    private String[] getSpeedStrings() {
        return new String[]{"1.0","0.9","1.1","1.2","1.3"};
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);

        setSpeedOptions();
    }

    private void setSpeedOptions() {
        // 재생 속도 조절
        final Spinner speedOptions = (Spinner) findViewById(R.id.speedOptions);
        String[] speeds = getSpeedStrings();
        ArrayAdapter<String> arrayAdapter =  new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, speeds);
        speedOptions.setAdapter(arrayAdapter);
        // change player playback speed if a speed is selected
        speedOptions.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                if (mediaPlayer != null) {
                    float selectedSpeed = Float.parseFloat(
                            speedOptions.getItemAtPosition(i).toString());

                    changeplayerSpeed(selectedSpeed);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }

    private void changeplayerSpeed(float speed) {
        // this checks on API 23 and up
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(speed));
            } else {
                mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(speed));
                mediaPlayer.pause();
            }
        }
    }

}



블로그 이미지

Link2Me

,