728x90

Android 패키지 파일을 분할하는 중에 문제가 발생하였습니다.

실행하려는 폰의 앱이 서버에 있는 앱보다 버전이 낮으면 아래와 같은 메시지를 띄운다.


서버와 앱 버전 체크를 하고 나서 파일 다운로드를 시도하면서 이런 메시지를 뿌린다.

Android 패키지 파일을 분할하는 중에 문제가 발생하였습니다.


defaultConfig {
    applicationId "com.android.USBController"
    minSdkVersion 19
    targetSdkVersion 27
    versionCode 3
    versionName "1.2"

}


버전 코드를 더 낮게 해서 컴파일 하는 것은 아닌지 체크를 해봐야 한다.


Android 앱 개발중 Release 버젼으로 .apk 파일을 생성해 모바일에 직접 설치하고 싶을 때
“패키지 파일을 분할하는 중에 문제가 발생하였습니다” 라는 에러가 나면서 설치되지 않는다면
APK 파일의 minSdkVersion (개발중인 안드로이드 버전)이
설치되는 폰의 Android Version 보다 높기 때문이다.
반대로 얘기하면, 실행하려는 폰이 개발된 앱보다 버젼이 낮기 때문이다.

블로그 이미지

Link2Me

,
728x90

class DownloadFileAsync extends AsyncTask<String, String, String> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        showDialog(DIALOG_DOWNLOAD_PROGRESS);
    }

    @Override
    protected void onPostExecute(String unused) {
        dismissDialog(DIALOG_DOWNLOAD_PROGRESS);



해결방법


class DownloadFileAsync extends AsyncTask<String, String, String> {

private ProgressDialog pDialog;

    @Override
    protected void onPreExecute()
    {
        super.onPreExecute();
        pDialog = new ProgressDialog(context);
        pDialog.setMessage("Please wait...");
        pDialog.setIndeterminate(false);
        pDialog.setCancelable(true);
        pDialog.show();
    }

    @Override
    protected void onPostExecute(String unused) {
        pDialog.dismiss();

}

블로그 이미지

Link2Me

,
728x90

아래 코드는 아직 해결이 안된 상태다.

하지만 적어두어야 해결책을 찾으면 수정할 수 있으니 그냥 적는다.


Notification n = new Notification();
n.flags |= Notification.FLAG_AUTO_CANCEL;
// 아이콘 모양
n.icon = R.drawable.icon_small;
// 메시지 내용
n.tickerText = text;
System.out.println("msg======" + text);
// 메시지가 나타나는 시간
n.when = System.currentTimeMillis();
// 메시지의 제목 NotificationCompat.Builder
n.setLatestEventInfo(context,
        context.getResources().getString(R.string.app_name), text,
        pendingIntent(context));
// 메시지를 띄워준다.
nm.notify(mLastId, n);


해결방법

NotificationCompat.Builder 를 사용해서 변경한다.

import android.support.v4.app.NotificationCompat;


NotificationCompat.Builder n = new NotificationCompat.Builder(context);
n.setSmallIcon(R.drawable.icon_small);
n.setTicker(text); //알림이 뜰때 잠깐 표시되는 Text
n.setWhen(System.currentTimeMillis()); // 알림이 표시되는 시간
n.setNumber(10); // 미확인 알림의 개수
n.setContentTitle(context.getResources().getString(R.string.app_name));
n.setContentText(text); //상단바 알림 내용
n.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
n.setContentIntent(pendingIntent(context));
n.setAutoCancel(true); //터치하면 자동으로 지워지도록 설정하는 것

nm.notify(mLastId, n.build()); // 알림을 구분할 상수


import android.support.v4.app.NotificationCompat; 를 했는데 에러메시지가 나오면

project properties->java build path->libraries click add external jar



이렇게 하면 에러메시지는 사라지는데 컴파일을 해서 동작시키면 앱이 죽어버린다.



블로그 이미지

Link2Me

,
728x90

ListView는 사용자가 정의한 데이터 목록을 아이템 단위로 구성하여 화면에 출력하는 ViewGroup의 한 종류다.
즉, ListView 는 어댑터(Adapter)를 통해 데이터를 뿌려주는 형식의 View 다.

ListView는 항목들을 수직으로 보여주는 어댑터 뷰로 상하로 스크롤이 가능하다.

ListView에 표시되는 아이템은 단순히 Text만 출력하는 구조가 될 수 있고, Image, Button, CheckBox 등 여러 View의 조합으로 구성되는 좀 더 복잡한 형태(Custom)가 될 수도 있다.

가장 간단하게 텍스트만 보여주는 예제를 살펴보자.

번호 1, 번호2, 번호 3과의 관계가 어떻게 되는지 아래 코드에서 보면된다.


 3번 화면에 출력할 ListView 추가 (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">

    <ListView
        android:id="@+id/istview_01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

 1번 배열 리스트 생성 및 2번 어댑터 생성, ListView에 어댑터 연결

 public class MainActivity extends AppCompatActivity {

    ArrayList<String> arrayList = null;
    ArrayAdapter<String> adapter = null;
    ListView listView = null;

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

        // 데이터 배열 리스트 생성
        arrayList = new ArrayList<String>();
        arrayList.add("이순신");
        arrayList.add("홍길동");
        arrayList.add("유관순");
        arrayList.add("강감찬");
        arrayList.add("김유신");
        arrayList.add("강길덕");

        // 어댑터 생성 (안드로이드에서 기본 제공하는 어댑터 활용)
        adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,arrayList);

        // AdapterView
        listView = (ListView) findViewById(R.id.istview_01);
        listView.setAdapter(adapter); // ListView 에 어댑터 설정
    }
}


이렇게 데이터 배열 리스트를 생성하는 경우는 없지만 개념으로 알아두자.


ArrayAdapter(Context context, int textViewResourceID, 배열);

- context : 현재 어플리케이션 컨텍스트

- int textViewResourceID : 레이아웃 아이디


ArrayAdapter 클래스는 제네릭 클래스로 정의되어 있다.

android.R.layout.simple_list_item_1 은 안드로이드 시스템 내부에 미리 만들어진 리소스중 하나로 SDK를 설치하면 자동으로 설치되며 어떠한 추가 작업 없이 개발자가 바로 사용할 수 있다.

 android.R.layout.simple_list_item_1

 하나의 텍스트 뷰 사용

 android.R.layout.simple_list_item_checked

 항목당 체크 표시

 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE)

 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE)



이제 데이터 리스트 배열을 만드는 방법을 바꿔보자.


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="contact">
        <item>김유신 010-0001-00001</item>
        <item>강감찬 010-0001-00002</item>
        <item>이순신 010-0001-00003</item>
        <item>유관순 010-0001-00004</item>
        <item>홍길동 010-0001-00005</item>
        <item>사임당 010-0001-00006</item>
        <item>최영 010-0001-00007</item>
        <item>이성계 010-0001-00008</item>
        <item>이방원 010-0001-00009</item>
        <item>정몽주 010-0001-00010</item>
        <item>김태성 010-0001-00011</item>
        <item>강길덕 010-0001-00012</item>
    </string-array>
</resources>


 public class MainActivity extends AppCompatActivity {

    ArrayAdapter<CharSequence> adapter = null;
    ListView listView = null;

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

        adapter = ArrayAdapter.createFromResource(
                                this,R.array.contact,android.R.layout.simple_list_item_checked);

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

        listView.setAdapter(adapter);
        listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        ColorDrawable colorDrawable = new ColorDrawable(Color.RED);
        listView.setDivider(colorDrawable);
        listView.setDividerHeight(3);
    }
}


블로그 이미지

Link2Me

,
728x90

구글링을 해서 전화수신 기능을 테스트하다보니 ITelephony 에서 에러가 나온다.


해결방법은 첨부파일을 다운로드 받아서 Android Studio app/src/main/ 폴더에서 압축을 풀면 된다.

ITelephony_aidl.zip

app\src\main\java\com\android\internal\telephony\ITelephony.aidl 에 파일이 복사된다.

압축파일은 다른 폴더로 옮기거나 삭제한다.


이제 에러 표시가 되는 곳에서 Alt + Enter 키를 누르면 아래 화면이 나온다. 여기에서 Create interface 'ITelephony' 를 선택한다.



com.android.internal.telephony 를 입력한다.



그러면 Interface 파일이 생성된다.

아래 코드를 복사하여 붙여넣기 한다.

붙여넣기는 http://stackoverflow.com/questions/31473793/where-to-copy-itelephony-java-in-android-studio-and-how-to-import-it-please-don 에서 복사해서 넣으면 된다.


package com.android.internal.telephony;

public interface ITelephony {
    void answerRingingCall();

    boolean endCall();

    void silenceRinger();

    boolean showCallScreenWithDialpad(boolean showDialpad);
}


이렇게 하면 에러 메시지가 사라진다.


블로그 이미지

Link2Me

,
728x90

안드로이드 어플에서 전화수신 팝업 기능을 테스트 해보고자 검색해보니 http://gun0912.tistory.com/46 에 자료가 있다.


코드를 받아서 보니 Butter Knife 라이브러리를 사용했더라.

한번도 사용을 해보지 않은 거라 그런지 소스코드를 붙여넣기 하니 에러가 발생한다.


검색으로 찾아보니 이 분 블로그에 방법이 나온다.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.mcxiaoke.volley:library-aar:1.0.0'
    compile 'com.android.support:support-annotations:24.2.0'
    compile 'com.google.firebase:firebase-messaging:9.6.1'
    // butterknife 추가해주는 것만으로 알아서 import 해 줌
    compile 'com.jakewharton:butterknife:7.0.1'

}

이렇게 하고 나면 Sync Now 를 눌러줘야 한다.


CallingService.java 파일내에 코드에서 에러가 해결되지 않는 곳이 있다.

@InjectView(R.id.tv_call_number) TextView tv_call_number;

ButterKnife.inject(this, rootView);


import butterknife.InjectView;
부분을 인식할 수 없다고 나온다.


문제 해결을 위해서 구글링을 해보니 http://jakewharton.github.io/butterknife/ 에서 확인하라고 나온다.


버전이 높아지면서 기능이 변경되었다는 걸 알 수 있었다.

compile 'com.jakewharton:butterknife:8.5.1'


기 존

수 정

 @InjectView(R.id.tv_call_number)
 TextView tv_call_number;
 @BindView(R.id.tv_call_number)
 TextView tv_call_number;

 ButterKnife.inject(this, rootView);

 ButterKnife.bind(this, rootView);


Gradle 에 버전을 수정해주고 코드를 수정하고나서 에러 메시지가 없어졌다.


이제 컴파일하여 코드를 테스트하는데 코드가 비정상적으로 동작하면서 죽는다.

원인이 정확하게 해결되지 않았나 보다..

원인 해결되면 수정 정리해야겠다.


블로그 이미지

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

하나의 안드로이드 앱에서 다룰 수 있는 메모리의 한계 때문에 큰 사이즈 이미지는 OutofMemory exception이나 퍼포먼스 문제가 생길 수 있다. 작은 뷰의 크기에 맞지 않는 고해상도의 이미지를 불러오는 것은 괜한 리소스를 낭비하게 만든다.


=== BitMap.java ===

 import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.View;

public class BitMap extends View {
    public BitMap(Context context) {
        super(context);
    }

    protected void onDraw(Canvas canvas){
        canvas.drawColor(Color.CYAN);

        // Resource 폴더에 저장된 그림파일을 Bitmap 으로 만들어 리턴해준다
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.car1);
        // 이미지가 너무 크면 화면에서 잘려서 보임
        canvas.drawBitmap(bitmap,10,10,null); // 비트맵 이미지를 캔버스에 출력하는 함수
    }
}

 Bitmap myImage = BitmapFactory.decodeFile(“/sdcard/car1.jpg”);
 BitmapFactory.decodeFile() : 로컬에 존재하는 파일을 그대로 읽어올 때 쓴다.
 파일경로를 파라미터로 넘겨주면 FileInputStream 을 만들어서 decodeStream 을 한다.

 Canvas.drawBitmap() 은 비트맵 이미지를 캔버스에 출력하는 함수
 - 1번째 파라미터는 비트맵 이미지 객체, 2번째 파라미터는 원본 이미지의 영역좌표,
 - 3번째 파라미터는 이미지가 표시되는 화면상의 영역좌표, 4번째 파라미터는 Paint 객체
 - 2번째와 4번째파라미터는 생략 가능하다.

 === MainActivity.java ===

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

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

        BitMap myView = new BitMap(this);
        setContentView(myView);
    }
}



https://ringsterz.wordpress.com/2014/11/27/imageview%EC%97%90-%EB%8C%80%EC%9A%A9%EB%9F%89-bitmap-%ED%9A%A8%EA%B3%BC%EC%A0%81%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EB%94%A9%ED%95%98%EA%B8%B0/

에 자동으로 줄이는 함수가 있어서 이걸 이용해서 테스트 해봤다.


BitmapFactory.Options : BitmapFactory 가 사용하는 옵션클래스이다.
Options 객체를 생성하고 설정하고자 하는 옵션을 넣은후 BitmapFactory 의 함수 실행시 파라미터로 넘기면된다.
inSampleSize : decode 시 얼마나 줄일지 설정하는 옵션인데 1보다 작을때는 1이 된다.
1보다 큰값일 때 1/N 만큼 이미지를 줄여서 decoding 하게 된다. 보통 2의 배수로 설정한다.


import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.View;

public class BitMap extends View {
    public BitMap(Context context) {
        super(context);
    }

    protected void onDraw(Canvas canvas){
        canvas.drawColor(Color.CYAN);

        // Resource 폴더에 저장된 그림파일을 Bitmap 으로 만들어 리턴해준다
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.car2);
        // 이미지가 너무 크면 화면에서 잘려서 보임
        canvas.drawBitmap(bitmap,10,10,null);

        Bitmap resize = resize_decodeResource(getResources(), R.drawable.car2, 100, 100);
        canvas.drawBitmap(resize,10,900,null); // 비트맵 이미지를 캔버스에 출력하는 함수
    }

    public static Bitmap resize_decodeResource(Resources res, int resId,

                                                                               int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}


이미지 출력 결과


Bitmap resize = resize_decodeResource(getResources(), R.drawable.car2, 100, 100);

에서 width 와 height 지정된 크기로 이미지가 줄어드는게 아니라 원래 이미지 크기가 width 와 height 중에서 작은 것 기준으로 맞춰서 줄여진다.


그래서 높이 기준으로 줄여주는 걸로 수정을 했다.

protected void onDraw(Canvas canvas){
    canvas.drawColor(Color.CYAN);

    Bitmap resize = autoresize_decodeResource(getResources(), R.drawable.car2, 100);
    canvas.drawBitmap(resize,10,300,null); // 비트맵 이미지를 캔버스에 출력하는 함수
}

public static Bitmap autoresize_decodeResource(Resources res, int resId, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInAutoSize(options, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

public static int calculateInAutoSize(BitmapFactory.Options options, int reqHeight) {
    // 원본 이미지의 높이와 너비
    final int height = options.outHeight;
    final int width = options.outWidth;

    float ratio = width / height;
    int reqWidth = Math.round((float) ratio * width);

    int inSampleSize = 1;
    if (height > reqHeight) {
        final int halfHeight = height / 2;
        while ((halfHeight / inSampleSize) > reqHeight) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
} 


블로그 이미지

Link2Me

,