App에서 주기적이며 반복적인 작업이 필요한 경우가 있습니다.  화면을 주기적으로 갱신 하던가 서버와 데이터를 일정시간마다 반복적으로 보내고 받아야 하는 경우등에는 Timer를 사용하는것이 소스가 간단해서 무척 편합니다.  한가지 단점은 중지 시켰다 다시 시작하는 기능이 없기 때문에 만일 화면에서 버튼으로 제어 하려면 제어를 한다기 보다는 Timer를 매번 새로 생성을 해야 합니다. 두가지 경우를 샘플을 만들어 테스트 해보겠습니다

.

 

1. 타이머 생성 및 종료

 

먼저 타이머를 사용하기 위해 Activity에 선언을 합니다.

 

private Timer timerCall;
private int nCnt;

 

그리고 반복적으로 사용할 TimerTaskonCreate()에 생성합니다.

생성된 TimerTask Timer3초에 한번씩 호출 하도록 schedule 합니다.

 

TimerTask timerTask = new TimerTask() {
   
@Override
   
public void run() {
        someWork();
    }

);

nCnt = 0;

timerCall = new Timer();
timerCall.schedule(timerTask,0,3000);

 

3초에 한번씩 someWork()을 호출 하는 함수는 단순 로그 출력 입니다.

 

private void someWork() {

    Log.d("Test==>", nCnt + " work!!!");
   
if(nCnt >= 10) {
        timerCall.cancel();
    }


    nCnt++;
}

 

앱이 종료 할때까지 타이머가 작동하며 잘 출력 됩니다.

 

D/Test==>: 1 work!!!
D/Test==>: 2 work!!!
D/Test==>: 3 work!!!
D/Test==>: 4 work!!!
D/Test==>: 5 work!!!
D/Test==>: 6 work!!!
D/Test==>: 7 work!!!
D/Test==>: 8 work!!!
D/Test==>: 9 work!!!
D/Test==>: 10 work!!!

 

전체 소스

 

package copycoding.tistory.timertest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

   
private Timer timerCall;
   
private int nCnt;

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

       
nCnt = 0;

        TimerTask timerTask = new TimerTask() {
           
@Override
           
public void run() {
                someWork();
            }
        };

       
timerCall = new Timer();
       
timerCall.schedule(timerTask,0,1000);
    }

   
private void someWork() {

        Log.d(
"Test==>", nCnt + " work!!!");
       
if(nCnt >= 10) {
           
timerCall.cancel();
        }

       
nCnt++;
    }
}

 

 

2. 타이머 버튼 제어

 

이번에는 타이머를 버튼으로 제어를 해봅니다.   먼저 선언을 하고

 

private Timer timerCall;
private int nCnt;
TimerTask
timerTask;

 

onCreate()에 초기 값을 생성합니다.

 

nCnt = 0;

timerCall = new Timer();

 

start button을 클릭 하면 기존에 TimerTask가 있다면 삭제하고 다시 생성을 하여 타이머를 가동시켜주면 됩니다.

 

public void btnStart(View view) {
    Log.d(
"BTN Start==>", nCnt + " work!!!");
   
if(timerTask != null) {
       
timerTask.cancel();
    }
   
timerTask = new TimerTask() {
       
@Override
       
public void run() {
            someWork();
        }
    };
   
timerCall.schedule(timerTask,0,3000);
}

 

TimerTask를 삭제하지 않고 계속 생성 하면 기존 타이머가 죽지 않은 상황에서 계속 새로운 프로세스의 타이머가 만들어져 타이머로서의 기능을 할 수 없게 됩니다.

 

stop button을 클릭 하면 TimerTask가 작동중인지 확인하고 삭제를 하게 됩니다.

 

public void btnStop(View view) {
    Log.d(
"BTN Stop==>", nCnt + " work!!!");
   
if(timerTask != null) {
       
timerTask.cancel();
    }
}

 

타이머가 호출하는 작업 메소드는 단순히 증가하는 숫자를 로그로 남겨줍니다.

 

private void someWork() {

    Log.d(
"Test some work ==>", nCnt + " work!!!");

   
nCnt++;
}

 

화면은 그냥 버튼 2개만 추가 하였습니다.

 

 

로그에 남겨진 텍스트를 보면 어떻게 작동하는지 알 수 있습니다.  start 버튼에 cancel 로직을 주석처리해서 비교해 보면서 자신에 맞는 로직으로 수정하면 됩니다.

  

D/BTN Start==>: 0 work!!!
D/Test some work ==>: 0 work!!!
D/Test some work ==>: 1 work!!!
D/Test some work ==>: 2 work!!!
D/Test some work ==>: 3 work!!!
D/BTN Stop==>: 4 work!!!
D/BTN Start==>: 4 work!!!
D/Test some work ==>: 4 work!!!
D/Test some work ==>: 5 work!!!
D/Test some work ==>: 6 work!!!
D/BTN Stop==>: 7 work!!!

 

 

 

전체 소스

 

package copycoding.tistory.timertest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

   
private Timer timerCall;
   
private int nCnt;
    TimerTask
timerTask;

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

       
nCnt = 0;

       
timerCall = new Timer();

    }

   
private void someWork() {

        Log.d(
"Test some work ==>", nCnt + " work!!!");

       
nCnt++;
    }

   
public void btnStart(View view) {
        Log.d(
"BTN Start==>", nCnt + " work!!!");
       
if(timerTask != null) {
           
timerTask.cancel();
        }
       
timerTask = new TimerTask() {
           
@Override
           
public void run() {
                someWork();
            }
        };
       
timerCall.schedule(timerTask,0,3000);
    }

   
public void btnStop(View view) {
        Log.d(
"BTN Stop==>", nCnt + " work!!!");
       
if(timerTask != null) {
           
timerTask.cancel();
        }
    }
}

 

 

App을 새로 생성해서 테스트 하려고 하니 오류가 나옵니다.

 

Installed Build Tools revision 31.0.0 is corrupted. Remove and install again using the SDK Manager.

 

 

Gradle을 살펴보고

 

android {
   
compileSdkVersion
31
   
buildToolsVersion "31.0.0"

   
defaultConfig {
       
applicationId
"copycoding.tistory.sample"
       
minSdkVersion 16
       
targetSdkVersion 31
       
versionCode 1
       
versionName "1.0"

       
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   
}

 

설치된 SDK Tool도 살펴봐도

 

 

 

별 문제 없는것 같은데..

 

 

1. 버전 낮추기

 

다시 설치하긴 귀찮고 전체적으로 버전을 30으로 낮추어 봅니다. 

 

android {
   
compileSdkVersion
30
   
buildToolsVersion "30.0.3"

   
defaultConfig {
       
applicationId
"copycoding.tistory.sample"
       
minSdkVersion 16
       
targetSdkVersion 30
       
versionCode 1
       
versionName "1.0"

       
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.5.0'

 

 

이런.  새로운 오류가 발생 합니다.

 

 

ERROR:C:\Users\will\.gradle\caches\transforms-2\files-2.1\4eb2fc21ccc2dce4da190845bc3482ac\material-1.5.0\res\values-v31\values-v31.xml:3:5-94: AAPT: error: resource android:color/system_neutral1_1000 not found.

 

Material Component Library 버전이 업데이트 되어서 31을 사용해야 한다고 합니다.

답을 찾았네요.  material 버전을 낮추면 되는거군요.

 

android {
   
compileSdkVersion 30
   
buildToolsVersion "30.0.3"

    
defaultConfig {
       
applicationId
"copycoding.tistory.sample"
       
minSdkVersion 16
       
targetSdkVersion 30
       
versionCode 1
       
versionName "1.0"

       
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   
}

   
buildTypes {
       
release {
           
minifyEnabled
false
           
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       
}
    }
   
compileOptions {
       
sourceCompatibility JavaVersion.
VERSION_1_8
       
targetCompatibility JavaVersion.VERSION_1_8
   
}
}



dependencies {

   
implementation 'androidx.appcompat:appcompat:1.3.1'
   
implementation 'com.google.android.material:material:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
   
testImplementation 'junit:junit:4.+'
   
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
   
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

  

material 버전을 1.5.0에서 1.2.0으로 변경 하나 잘 됩니다.  물론 1.4.0로 낮추어도 됩니다.

 

 

 

 

2. buildToolsVersion 낮추기

 

다른건 그대로 두고 buildToolsVersion만 낮추어서 해결해 봅니다.

Android Studio에서 Gradle을 열고 오른쪽 상단을 보면 Open (Ctrl+Alt+Shift+S)가 있는데

 

 

이걸 누르면 창이 하나 뜨고

 

 

여기서 Build Tools Version을 확장하여 31.0.0 대신 하나 낮은걸 선택 합니다.

  

android {
   
compileSdkVersion
31
   
buildToolsVersion '30.0.3'

   
defaultConfig {
       
applicationId
"copycoding.tistory.test"
       
minSdkVersion 16
       
targetSdkVersion 31
       
versionCode 1
        
versionName "1.0"

 

 

 

그리고 실행하면 다른 오류가 발생 합니다.

 

Manifest merger failed : Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined. See https://developer.android.com/guide/topics/manifest/activity-element#exported for details.

 

 

android:exported 이게 문제라는 군요.  참조하라는 링크를 따라가보면 설명이 있습니다.

https://developer.android.com/guide/topics/manifest/activity-element#exported

 

어째든 android:exportedManifest에 추가하고 값을 넣으면 됩니다.  만일 false를 넣으면 앱이 생성은 되는데 실행하려고 하면 app is’nt installed라는 토스트만 나오니 true를 적어 줍니다.

값을 설정하는 위치는 activity에 해주어야 합니다.

  

<activity android:name=".MainActivity"
   
android:exported="true"
>
    <
intent-filter>
        <
action android:name="android.intent.action.MAIN" />

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

 

이렇게 하면 앱을 잘 테스트할 수 있습니다.

 

 

이건 제가 간단한 테스트를 하려고 한것이고 실제로는 높은 버전으로 컴파일 해야 최신 핸드폰에서도 잘 돌아가는 앱을 만들 수 있겠죠.

 

- copy coding -

 

화면에 텍스트를 출력 하는데 일부 글자를 강조하기 위해 색을 사용해야 하는데 필요에 따라 텍스트가 변경되는 경우 이미지로 하기도 어려운 조건인 경우 html tag를 이용하여 쉽게 해결할 수 있습니다.

사용법도 그냥 html tag를 그대로 사용할 수 있기 때문에 전혀 어렵지도 않습니다.  먼저 text를 출력할 TextViewButton을 하나씩 만들어 주고

 

<TextView
   
android:id="@+id/tvHtml"
   
android:layout_width="match_parent"
   
android:layout_height="321dp"
   
android:textAlignment="center"/>

<
Button
   
android:id="@+id/button"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
android:layout_marginBottom="180dp"
   
android:text="Button"
   
app:layout_constraintBottom_toBottomOf="parent"
   
app:layout_constraintEnd_toEndOf="parent"
   
app:layout_constraintHorizontal_bias="0.498"
   
app:layout_constraintStart_toStartOf="parent" />

 

java 파일에서 Html.formHtml()만 사용하면 됩니다.

 

String htmlText = "<h1>Hi</h1><br>blue<font color='green'>Blue</font>bl<font color='red'>u</font>e";
htmlText +=
"<p><i>test</i> <u>under</u>";
TextView tv = (TextView)findViewById(R.id.
tvHtml);
tv.setText(Html.fromHtml(htmlText));

String htmlBtn =
"B<font color='red'>u</font>tt<font color='blue'>o</font>n";
Button btnHtml = (Button)findViewById(R.id.
button);
btnHtml.setText(Html.fromHtml(htmlBtn));

 

 

이렇게 일반 텍스트와 버튼에도 쉽게 적용이 가능 합니다.

 

- copy coding -

 

안드로이드에서 사용하는 이미지는 해상도에 따라 다르게 적용하는데 최초에 파일을 저장하려면 drawable v24 폴더만 존재하기 때문에 고해상도용 이미지를 저장할 폴더는 신규로 생성해야 합니다.  생성 방법은 몇번의 마우스 클릭만으로 가능 합니다.

 

res에 마우스 포인터를 위치하고 우클릭을 해줍니다. 

New > Android Resource Directory

 

 

그리고 팝업 메뉴에서 Android Resource Directory를 선택 합니다.

그러면 팝업 창이 나오는데 여러 리소스들을 추가할 수 있지만 여기서는 이미지 관련 작업을 하기위한 선택을 합니다.

 

Resource type을 drawable로 선택하면 Directory Name은 자동으로 변경됩니다.

그리고 좌측 하단에서 Density를 클릭하고 중앙에 있는 [>>] 버튼을 클릭하면 아래와 같이 우측에 Density라는 타이틀이 생성되면서 select 박스가 나오는데 확장해 보면 해상도별 생성 가능한 폴더 목록이 표시됩니다.

 

 

select option 중 원하는 해상도 폴더를 선택하고 [OK] 버튼을 클릭해 줍니다.  눈에 보이지는 않지만 drawable 폴더에 새로운 폴더가 생성이 되었습니다.

 

확인을 하러면 drawable에 파일을 복사해보는 수 밖에 없는데 파일을 하나 추가 하면

 

처음에 없던 새로 생성한 폴더가 선택할 수 있는 목록에 추가되어 나타납니다.

다른 해상도 폴더도 동일하게 생성해서 사용하면 됩니다.

 

- copy coding -


12345···17

+ Recent posts