시간(Time) 관련 함수 코드

|

다음 함수들은 시간과 관련된 기능을 제공하는 함수들입니다. 그때그때 필요한 기능들을 일일이 찾아서 사용해도 되지만, 제 경우는 별도의 클래스로 만들어서 입맛에 맞게 사용하는 것이 더 편리하더군요. 그래서 저는 시간 관련 함수들을 TimeUtils Class로 만들어서 사용하고 있습니다.


현재 시간을 Long 값으로 리턴하는 코드

public static long getTimeAsLong() {
  Calendar calendar = Calendar.getInstance();
  return calendar.getTimeInMillis();
}


현재 시간을 String으로 리턴하는 코드

public static String getTimeAsString(String format) {
  Date date = new Date(getTimeAsLong());
  SimpleDateFormat sdf = new SimpleDateFormat(format,
      Locale.getDefault());

  return sdf.format(date);
}


입력받은 시간(String)을 Long으로 리턴하는 코드

public static Long getTimeAsLong(String format, String text) {
  try {
    SimpleDateFormat sdf = new SimpleDateFormat(format,
        Locale.getDefault());
    Date date = sdf.parse(text);
    return date.getTime();
  } catch(Exception e) {
    e.printStackTrace();
  }
  return new Long(-1);
}


입력받은 시간(Long)을 String으로 리턴하는 코드

public static String getTimeAsString(String format, long time) {
  Date date = new Date(time);
  SimpleDateFormat sdf = new SimpleDateFormat(format,
      Locale.getDefault());

  return sdf.format(date);
}


오늘 시작 시간을 Long으로 리턴하는 코드

public static Long getTodayFrom() {
  String date = SnowTimeUtil.getTimeAsString("yyyy-MM-dd");
  long time = SnowTimeUtil.getTimeAsLong("yyyy-MM-dd", date);

  Calendar calendar = Calendar.getInstance();
  calendar.setTimeInMillis(time);
  calendar.set(Calendar.HOUR, 0);
  calendar.set(Calendar.MINUTE, 0);
  calendar.set(Calendar.SECOND, 0);

  return calendar.getTimeInMillis();
}


오늘 종료 시간을 Long으로 리턴하는 코드

public static Long getTodayTo() {
  String date = SnowTimeUtil.getTimeAsString("yyyy-MM-dd");
  long time = SnowTimeUtil.getTimeAsLong("yyyy-MM-dd", date);

  Calendar calendar = Calendar.getInstance();
  calendar.setTimeInMillis(time);
  calendar.set(Calendar.HOUR, 0);
  calendar.set(Calendar.MINUTE, 0);
  calendar.set(Calendar.SECOND, 0);
  calendar.add(Calendar.DAY_OF_MONTH, 1);

  return calendar.getTimeInMillis() - 1;
}

Foreground Service 용 Notification 꾸미기

|

안드로이드에서 서비스는 크게 백그라운드 서비스(Background Service)와 포그라운드 서비스(Foreground Service)로 나눌 수 있습니다.


Background Service

우리가 흔히 말하는 서비스가 백그라운드 서비스 형태입니다. 시스템의 메모리가 부족할 경우 시스템이 해당 서비스를 강제로 종료시킬 수 있습니다. onStartCommand()의 파라메터를 이용하여, 서비스의 종료시 동작을 정의할 수 있습니다.

  • START_NOT_STICKY : 서비스가 종료되었을 때, 서비스 재 실행을 하지 않음
  • START_STICKY : 서비스가 종료되었을 때, 서비스를 재 실행 함. onStartCommand()를 호출하며 파라메터는 null 임
  • START_REDELIVER_INTENT : 서비스를 재 실행하며, 기존의 Intent 파라메터를 이용하여 onStartCommand()를 호출함


Foreground Service

서비스의 우선 순위가 높아서, 시스템의 메모리가 부족하더라도 강제로 종료시키지 않습니다. 대신 상태바에 Notification이 표시됩니다. 과거에는 상태바에 Notification을 표시하지 않고도 Foreground Service로 실행할 수가 있었는데, 현재는 구글 정책으로 Foreground Service를 수행하기 위해서는 무조건 사용자에게 표시를 해야 하도록 변경되었습니다.


Foreground Service 만들기

서비스는 기본적으로 Background Service 입니다. Foreground Service를 만들기 위해서는 서비스 내부에 다음과 같은 코드를 작성하면 됩니다.

startForeground(1, new Notification());

이렇게 하면, 알아서 Notification을 표시해주며 서비스는 Foreground 형태로 동작하게 됩니다.


Customize Notification 예제

이 때, Notification의 모습을 바꾸고 싶을 때는 어떻게 하면 될까요? 다음과 같은 코드를 이용해서 Notification의 모습을 바꿔줄 수 있습니다.


notification_foreground.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="horizontal">

  <ImageView
    android:layout_width="44dp"
    android:layout_height="44dp"
    android:layout_marginLeft="10dp"
    android:layout_gravity="center_vertical"
    android:src="@mipmap/ic_launcher" />

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:orientation="vertical">

    <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="center_vertical"
      android:text="@string/notification_title"
      android:textColor="@color/black"
      android:textSize="18sp" />

    <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="center_vertical"
      android:text="@string/notification_message"
      android:textColor="@color/darkgray"
      android:textSize="14sp" />
  </LinearLayout>
</LinearLayout>


SampleService.java

RemoteViews remoteViews = new RemoteViews(getPackageName(),
    R.layout.notification_foreground);
Notification.Builder mBuilder = new Notification.Builder(this)
    .setSmallIcon(R.mipmap.ic_launcher).setContent(remoteViews);

startForeground(1, mBuilder.build());


여기에 Notification을 눌렀을 때 동작하는 PendingIntent를 연결하고 싶으면 다음과 같이 작성하면 됩니다.

Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
RemoteViews remoteViews = new RemoteViews(getPackageName(),
    R.layout.notification_foreground);
Notification.Builder mBuilder = new Notification.Builder(this)
    .setSmallIcon(R.mipmap.ic_launcher)
    .setContent(remoteViews)
    .setContentIntent(pendingIntent);
startForeground(1, mBuilder.build());

스트래티지(Strategy) 패턴

|

스트래티지(Strategy) 패턴의 UML은 다음과 같습니다.

Image

디자인 패턴 중에 가장 많이 쓰이는 패턴 중 하나입니다. 이름을 보면 ‘전략’이라는 뜻을 의미하고 있고, 실제로 알고리즘 등을 캡슐화하여 쉽게 교체해서 쓸 수 있도록 해주는 디자인 패턴입니다. 게임 등에서 난이도나 적들의 인공지능 들을 동적으로 쉽게 변경할 때도 많이 쓰입니다.


예제

Strategy 패턴 예제로 가장 많이 사용되고 있는 오리(Duck) 클래스를 예로 들도록 하겠습니다.

public abstract class Duck {

  public Duck() {}

  public abstract void display();

  public void fly() {
    System.out.println("Flying...");
  }

  public void quack() {
    System.out.println("Quack! Quack! Quack!");
  }
}

Duck이라는 추상 클래스가 있습니다. fly()quack() 메소드는 구현이 되어 있고, display() 메소드는 상속받아서 구현하도록 되어 있습니다.

이제 상속을 이용해서 노란 오리, 청둥 오리, 집 오리 등등 다양한 오리 클래스를 만들 수 있습니다.


상속의 한계점

하지만, ‘모형 오리’를 만들어야 하는 경우가 발생했습니다. 모형 오리는 날지도 못하고, 소리도 내지 못합니다.

물론, 상속을 이용해서 다음과 같이 fly() 메소드와 quack() 메소드 내부를 비워둔채로 구현해도 됩니다.

public class ModelDuck extends Duck {

  @Override
  public void display() {
    System.out.println("This is a ModelDuck.");
  }

  public void fly() {}

  public void quack() {}
}

이와 같은 방식으로 향후 추가될지도 모르는 ‘노란 모형 오리’, ‘태엽으로 움직이는 모형 오리’, ‘건전지로 움직이는 모형 오리’ 등 다양한 오리 클래스들도 모두 구현할 수 있습니다.

하지만, 수십/수백개의 클래스를 각각 만들어서 구현하고 관리하는 건 여간 번거로운 일이 아닐 수 없습니다. 특히 프로젝트 초기에는 모형 오리들이 말을 못했지만, 나중에 출시되는 제품들은 말을 할 수 있게 바꿔야 한다면, 각각의 클래스들을 전부 수정하는 것도 쉬운 일이 아닙니다.


Strategy 패턴 적용

이와 같은 경우 Strategy 패턴을 적용할 수 있습니다. Strategy 패턴은 각각의 알고리즘들을 캡슐화해서 쉽게 교체할 수 있게 할 수 있습니다.

여기서 fly()quack()라는 각각의 행동을 캡슐화할 수 있습니다. 예를 들어 각각의 행동을 IFlyBehavior, IQuackBehavior이라는 인터페이스로 치환할 수 있습니다.

public interface IFlyBehavior {
  void fly();
}
public interface IQuackBehavior {
  void quack();
}

그리고 Duck 클래스는 다음과 같습니다.

public abstract class Duck {

  IFlyBehavior iFlyBehavior;
  IQuackBehavior iQuackBehavior;

  public Duck() {}

  public abstract void display();

  public void performFly() {
    iFlyBehavior.fly();
  }

  public void performQuack() {
    iQuackBehavior.quack();
  }

  public void setFlyBehavior(IFlyBehavior fb) {
    iFlyBehavior = fb;
  }

  public void setQuackBehavior(IQuackBehavior fb) {
    iQuackBehavior = fb;
  }
}


각 행동들을 구현

fly()quack()라는 행동을 인터페이스로 정의했기 때문에 각 행동군들을 묶어서 클래스로 구현해줍니다.

public class FlyWithWings implements IFlyBehavior {

  @Override
  public void fly() {
    System.out.println("Fly with wings...");
  }
}
public class Quack implements IQuackBehavior {

  @Override
  public void quack() {
    System.out.println("Quack! Quack! Quack!");
  }
}

날지 못하는 오리들을 위한 NoFly 클래스도 만들어줍니다.

public class NoFly implements IFlyBehavior {

  @Override
  public void fly() {
    System.out.println("I can not fly.");
  }
}

이런 식으로 각 행동들을 알고리즘군으로 묶어서 캡슐화를 해주면, 각각의 오리 클래스들의 행동을 쉽게 구현하고 제어할 수 있게 됩니다.


오리 클래스 구현

모형 오리 클래스는 다음과 같습니다.

public class ModelDuck extends Duck {

  public ModelDuck() {
    setFlyBehavior(new NoFly());
    setQuackBehavior(new NoQuack());
  }

  @Override
  public void display() {
    System.out.println("I am a ModelDuck.");
  }
}


사용 예제

다음과 같은 코드를 이용해서 ‘ModelDuck’ 인스턴스를 만들고, 나중에 Fly 동작을 동적으로 변경이 가능합니다.

public class Main {

  public static void main(String[] args) {

    Duck duck = new ModelDuck();
    duck.display();
    duck.performFly();
    duck.performQuack();

    // change the behavior
    duck.setFlyBehavior(new FlyWithWings());
    duck.performFly();
  }
}

StringBuffer vs StringBuilder

|

안드로이드 개발을 하면서 StringBuffer 또는 StringBuilder를 많이 사용하게 될 것입니다. 특히 개발하면서 로그(Log) 메세지 등을 길게 붙여서 만들 때, StringBuffer나 StringBuilder를 모르는 경우 다음과 같이 코드를 작성하는 경우가 많습니다.

String strLog;
void log(String message) {
  strLog = strLog + message + "\n";
}


String

String은 Immutable 하기 때문에 한 번 할당되면 메모리 공간이 변하지 않습니다. 무슨 말이냐면, 위의 예제에서 strLog 라는 변수에 ‘strLog + message + “\n”’ 이라는 값을 넣게 되면, 새로운 String 인스턴스가 생기고 기존의 String은 제거가 되게 됩니다. (정확히는 가비지 컬렉터(Garbage Collector)가 제거할 것입니다.) 즉, 메모리 할당과 삭제가 빈번하게 일어나기 때문에 성능 하락의 원인이 될 수 있습니다.

이런 경우에는 String 대신 StringBuffer나 StringBuilder를 사용하면 성능 향상에 도움이 됩니다. 두 클래스는 append() 메소드를 이용하여 기존에 할당받은 메모리 공간을 유연하게 늘리면서 사용을 하게 됩니다. 그래서 메모리 할당과 삭제가 빈번하게 일어나는 일을 방지할 수 있습니다.

그러면 StringBuffer와 StringBuilder의 차이점을 알아보도록 하겠습니다.


StringBuffer vs StringBuilder

StringBuffer와 StringBuilder는 겉으로 보기에 크게 차이가 없습니다. 다만, 내부적으로 StringBuffer는 synchronized 키워드가 있어서 멀티쓰레드(Multi-Thread) 환경에서 좀 더 안전하다는 장점이 있습니다. 대신 성능은 StringBuilder 보다 약간 떨어지겠죠?

즉, 멀티쓰레드 환경에서는 StringBuffer, 그 외에는 StringBuilder를 사용하면 효율적으로 사용할 수 있습니다. 두 개를 외우기 싫으면 그냥 StringBuffer를 사용하시면 됩니다.

팩토리 메소드(Factory Method) 패턴

|

팩토리 메소드(Factory Method) 패턴의 UML은 다음과 같습니다.

Image

‘메소드’라는 단어를 생략하고 팩토리 패턴이라고도 불리우는 팩토리 메소드(Factory Method) 패턴에 대해서 알아보도록 하겠습니다.

Factory Method 패턴의 가장 큰 특징은 어떤 클래스의 인스턴스를 생성하는 방법을 바깥으로 노출시키지 않겠다는 것입니다. 인스턴스 생성은 서브 클래스에서 하게 되고 바깥에서는 Factory 역할을 하는 특정 클래스를 통해 해당 클래스의 인스턴스를 획득하게 됩니다.


예제 코드

예제를 보도록 하겠습니다.

public abstract class Pizza {

  String mName;
  String mDough;
  String mSauce;
  ArrayList<Topping> mToppingList = new ArrayList<Topping>();

  public void prepare() {
    System.out.println("Preparing " + mName);
    System.out.println("Tossing dough...");
    System.out.println("Adding sauce...");
    System.out.println("Adding topping: ");
    for(int i = 0; i < mToppingList.size(); i++) {
      System.out.println("   " + mToppingList.get(i));
    }
  }

  public void bake() {
    System.out.println("Bake for 25 minutes");
  }

  public void cut() {
    System.out.println("Cutting the pizza into diagonal slices");
  }

  public void box() {
    System.out.println("Place pizza in official PizzaStore box");
  }

  public String getName() {
    return mName;
  }
}


이렇게 Pizza 라는 추상 클래스를 만들고, Pizza를 상속하는 다양한 Pizza 클래스를 만듭니다.

class CheesePizza extends Pizza {
  // TODO
}

class PepperoniPizza extends Pizza {
  // TODO
}

class ClamPizza extends Pizza {
  // TODO
}

class VeggiePizza extends Pizza {
  // TODO
}


그리고 Pizza를 만들어주는 추상 클래스인 PizzaStore를 만들도록 하겠습니다.

public abstract class PizzaStore {

  public PizzaStore() {
  }

  Pizza orderPizza() {
    Pizza pizza = createPizza();

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

    return pizza;
  }

  abstract Pizza createPizza();
}


마찬가지로 PizzaStore를 상속받는 Concrete Class들을 만들어주면 됩니다.

class CheesePizzaStore extends PizzaStore {

  @Override
  Pizza createPizza() {
    return new CheesePizza();
  }

}

class PepperoniPizzaStore extends PizzaStore {

  @Override
  Pizza createPizza() {
    return new PepperoniPizza();
  }
}


이렇게 하면 각각의 PizzaStore에서 createPizza() 만 호출하면 그에 맞는 피자들의 인스턴스가 생성이 됩니다.