Activity와 Task, 실행 Flag

|

Stack

여기서 스택(Stack)은 각 Activity가 실행될 때 그 순서가 저장되는 공간입니다. ‘뒤로 가기(Back)’ 버튼을 누르면 최상단의 Activity가 종료되고 그 다음 Activity가 화면에 출력됩니다.


Task

Activity는 그룹으로 묶여서 관리됩니다. 여러 Activity를 하나의 그룹으로 묶은 것이 태스크(Task)입니다. 안드로이드 공식 사이트에서는 ‘Task는 사용자 관점에서 보는 App 단위’라고 말하고 있습니다.

Task 역시 스택에 저장됩니다.


복수의 Activity와 복수의 Task간 관계

예를 들어 다음과 같은 App이 있다고 가정합니다.

  • 메일 App
  • 갤러리 App

시나리오는 다음과 같습니다.

  1. 메일 작성 App을 실행한다.
  2. 첨부하기 버튼을 눌러 갤러리 App의 사진 선택 화면을 실행한다.
  3. 메일 작성칸에 사진이 첨부된다.

이 경우, 갤러리 App에 있는 사진 선택 화면 Activity메일 App의 Task에 포함됩니다. 갤러리 App의 Activity이지만, 사용자 관점에서는 메일 App에 포함된 것으로 보이기 때문입니다.


새로운 Task로 시작하기

위의 시나리오처럼 메일 App과 사진 선택 Activity가 밀접하게 연동되는 경우가 아니라 완전히 다른 App을 실행하는 경우는 서로 다른 Task로 시작되어야 합니다.

    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);


Affinity

일반적으로 같은 Task에서 실행하는 Activity는 같은 Affinity를 갖고 있습니다. Affinity는 AndroidManifest.xml에서 taskAffinity 속성에서 지정할 수 있습니다. 기본적으로 패키지 이름을 갖습니다.

보통은 크게 신경쓰지 않아도 되는 속성이지만, 만약 여러 개의 App을 하나의 패키지로 묶어서 배포하는 경우 각각의 App에 해당하는 Activity 들은 별도의 Affinity를 지정하는 것이 좋습니다. 그렇게 하지 앟은 경우, 예를 들어 하나의 패키지내에 2개의 Activity가 있으며 각 Activity는 별개의 App 처럼 동작할 때 Affinity가 같다면, 실행 아이콘을 클릭시 전혀 의도하지 않은 Activity가 실행되는 등의 예상하지 못한 문제가 발생할 수 있습니다.


Activity 이중 실행 방지

AndroidManifest.xmlactivity의 속성 중 launchMode라는 속성으로 설정할 수 있습니다.

옵션 설명
standard 기본값이며 여러 인스턴스 생성이 가능
singleTop Task 상단에 해당 Activity 인스턴스가 있는 경우, 새로운 인스턴스 생성을 하지 않고 기존 인스턴스의 onNewIntent()를 호출
singleTask Activity가 새로운 Task의 루트 인스턴스로 실행됨. 다른 Task에 속할 수 없음. Activity가 이미 존재하는 경우는 기존 Activity의 onNewIntent()를 호출
singleInstance singleTask와 기본적으로 동일하지만, 시스템은 인스턴스를 보유하고 있는 것 외에 다른 Activity를 실행하지 않음. Task와 Activity는 항상 하나만 있음

일반적으로 standardsingleTop를 많이 사용하며, singleTasksingleInstance는 개발 사이트에서도 ‘일반적인 경우에는 권장하지 않는다’라고 되어 있습니다.

Android Studio 단축키 모음

|

안드로이드 스튜디오를 보다 편리하게 사용할 수 있는 단축키들입니다.

기능 단축키
Find Action Ctrl + Shift + A
자동 완성(Basic Completion) Ctrl + Space
오류 수정(Quick Fix) Alt + Enter
선언부 이동(Navigate Declaration) Ctrl + B
메소드 호출한 곳 찾기(Navigate Call Hierarchy) Ctrl + Alt + H
Formatter 적용(Reformatting) Ctrl + Alt + L
파라메터 정보(Parameter Info) Ctrl + P
최근 파일(Recent Files) Ctrl + E
이전/다음 변경 내역 이동 Ctrl + Alt + 또는 Ctrl + Alt +

grep 사용법

|

Grep

grep는 파일의 내용을 확인해서 찾는 문자열이 포함되었는지 아닌지 찾아주는 명령어입니다. ‘Global Regular Expression Print’의 약어입니다.


사용 방법

grep -r "검색하고 싶은 문자열" /home/pi

-r 옵션은 하위 폴더까지 검색하도록 하는 옵션입니다. /home/pi는 검색 시작 위치입니다.

만약 대소문자 구분을 하지 않으려면 -i 옵션을 사용하면 됩니다. -E 옵션은 파일 내용의 대소문자 차이를 무시하고 정규 표현식으로 검색하는 옵션입니다.


Regular Expression

grep에서 사용할 수 있는 간단한 정규식은 대표적으로 다음과 같습니다.

코드 설명
( ) 그룹화
| 좌우 중 하나
? 직전의 표현이 0회 또는 1회 등장
* 직전의 표현이 0회 이상 연속해서 등장
+ 직전의 표현이 1회 이상 연속해서 등장
. 임의의 한 문자
^ 줄 머리
$ 줄 끝


CustomView 만들기

|

사용자 정의 View

안드로이드에서 CustomView를 만드는 방법입니다.

custom_view.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:padding="10dp"
  android:orientation="horizontal">

  <ToggleButton
    android:id="@+id/button1"
    android:layout_width="0px"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textOff="Off"
    android:textOn="On" />

  <ToggleButton
    android:id="@+id/button2"
    android:layout_width="0px"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textOff="Off"
    android:textOn="On" />

  <ToggleButton
    android:id="@+id/button3"
    android:layout_width="0px"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textOff="Off"
    android:textOn="On" />

</LinearLayout>


attrs.xml에 속성 추가

그런 다음, CustomView에 대한 속성을 정의합니다. Java 코드 내에서 속성을 지정할 때는 굳이 이런 과정이 필요없지만, XML에서도 속성을 지정하고 싶을 때는 아래와 같이 attrs.xml에 원하는 속성을 지정해야 합니다. attrs.xml 파일은 values 폴더 아래에 위치합니다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="custom_view">
    <attr format="integer" name="selected" />
  </declare-styleable>
</resources>


CustomView.java

package snowdeer.customviewexample;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.ToggleButton;

public class CustomView extends LinearLayout {

  ToggleButton[] buttons = new ToggleButton[3];
  int selectedId = 0;

  public CustomView(@NonNull Context context,
      @Nullable AttributeSet attrs,
      @AttrRes int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    inflateViews(context, attrs);
  }

  public CustomView(@NonNull Context context,
      @Nullable AttributeSet attrs) {
    super(context, attrs);
    inflateViews(context, attrs);
  }

  void inflateViews(Context context, AttributeSet attrs) {
    LayoutInflater inflater = (LayoutInflater) context
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    inflater.inflate(R.layout.custom_view, this);

    if (attrs != null) {
      TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.custom_view);
      selectedId = array.getInteger(0, 0);
      array.recycle();
    }
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();

    buttons[0] = (ToggleButton) findViewById(R.id.button1);
    buttons[1] = (ToggleButton) findViewById(R.id.button2);
    buttons[2] = (ToggleButton) findViewById(R.id.button3);

    setSelected(selectedId);
  }

  public void setSelected(int id) {
    if ((id < 0) || (id > 3)) {
      return;
    }

    selectedId = id;
    for (ToggleButton btn : buttons) {
      btn.setChecked(false);
    }

    buttons[id].setChecked(true);
  }

  public int getSelectedId() {
    return selectedId;
  }

}


활용

이제 CustomView 클래스의 생성이 끝났기 때문에 원하는 곳에서 사용을 하면 됩니다. 예를 들어, activity_main.xml에서 사용을 한다면 다음과 같이 배치해주면 됩니다.

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

  <snowdeer.customviewexample.CustomView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:selected="1" />

</LinearLayout>

SocketAddress 및 SocketAddressFactory 예제

|

SocketAddress.h

#ifndef SNOWSOCKET_SOCKETADDRESS_H
#define SNOWSOCKET_SOCKETADDRESS_H

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <w32api/inaddr.h>

using namespace std;

class SocketAddress {
 public:
  SocketAddress(uint32_t address, uint16_t port) {
    getSockAddrIn()->sin_family = AF_INET;
    getSockAddrIn()->sin_addr.s_addr = htonl(address);
    getSockAddrIn()->sin_port = htons(port);
  }

  SocketAddress(string address, uint16_t port) {
    getSockAddrIn()->sin_family = AF_INET;
    getSockAddrIn()->sin_addr.s_addr = inet_addr(address.c_str());
    getSockAddrIn()->sin_port = htons(port);
  }

  SocketAddress(const sockaddr &sockAddr) {
    memcpy(&mSockAddr, &sockAddr, sizeof(sockaddr));
  }

  size_t size() const {
    return sizeof(sockaddr);
  }

 private:
  sockaddr mSockAddr;
  sockaddr_in *getSockAddrIn() {
    return reinterpret_cast<sockaddr_in *> (&mSockAddr);
  }
};

using SocketAddressPtr = shared_ptr<SocketAddress>;

#endif //SNOWSOCKET_SOCKETADDRESS_H


SocketAddressFactory.h

#ifndef SNOWSOCKET_SOCKETADDRESSFACTORY_H
#define SNOWSOCKET_SOCKETADDRESSFACTORY_H

#include <cstdio>
#include <netdb.h>
#include "SocketAddress.h"

using namespace std;

class SocketAddressFactory {
 public:
  static SocketAddressPtr createIPv4SocketAddress(const string &address) {
    string host, service;

    auto pos = address.find_last_of(':');
    if (pos != string::npos) {
      host = address.substr(0, pos);
      service = address.substr(pos + 1);
    } else {
      host = address;
      service = "0";
    }

    addrinfo hint;
    memset(&hint, 0, sizeof(hint));
    hint.ai_family = AF_INET;

    addrinfo *_result = nullptr;
    int error = getaddrinfo(host.c_str(), service.c_str(), &hint, &_result);
    addrinfo *result = _result;

    if (error != 0 && _result != nullptr) {
      freeaddrinfo(result);

      return nullptr;
    }

    while (!_result->ai_addr && _result->ai_next) {
      _result = _result->ai_next;
    }

    if (!_result->ai_addr) {
      freeaddrinfo(result);

      return nullptr;
    }

    auto toRet = make_shared<SocketAddress>(*_result->ai_addr);

    freeaddrinfo(result);

    return toRet;
  }
};

#endif //SNOWSOCKET_SOCKETADDRESSFACTORY_H