16 Aug 2017
|
Android
Stack
여기서 스택(Stack)은 각 Activity가 실행될 때 그 순서가 저장되는 공간입니다.
‘뒤로 가기(Back)’ 버튼을 누르면 최상단의 Activity가 종료되고 그 다음 Activity가
화면에 출력됩니다.
Task
Activity는 그룹으로 묶여서 관리됩니다. 여러 Activity를 하나의 그룹으로 묶은 것이
태스크(Task)입니다. 안드로이드 공식 사이트에서는 ‘Task는 사용자 관점에서 보는
App 단위’라고 말하고 있습니다.
Task 역시 스택에 저장됩니다.
복수의 Activity와 복수의 Task간 관계
예를 들어 다음과 같은 App이 있다고 가정합니다.
시나리오는 다음과 같습니다.
- 메일 작성 App을 실행한다.
- 첨부하기 버튼을 눌러 갤러리 App의 사진 선택 화면을 실행한다.
- 메일 작성칸에 사진이 첨부된다.
이 경우, 갤러리 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.xml
의 activity
의 속성 중 launchMode
라는 속성으로 설정할 수 있습니다.
옵션 |
설명 |
standard |
기본값이며 여러 인스턴스 생성이 가능 |
singleTop |
Task 상단에 해당 Activity 인스턴스가 있는 경우, 새로운 인스턴스 생성을 하지 않고 기존 인스턴스의 onNewIntent() 를 호출 |
singleTask |
Activity가 새로운 Task의 루트 인스턴스로 실행됨. 다른 Task에 속할 수 없음. Activity가 이미 존재하는 경우는 기존 Activity의 onNewIntent() 를 호출 |
singleInstance |
singleTask 와 기본적으로 동일하지만, 시스템은 인스턴스를 보유하고 있는 것 외에 다른 Activity를 실행하지 않음. Task와 Activity는 항상 하나만 있음 |
일반적으로 standard
와 singleTop
를 많이 사용하며, singleTask
와 singleInstance
는 개발 사이트에서도 ‘일반적인 경우에는 권장하지 않는다’라고 되어 있습니다.
15 Aug 2017
|
Android
IDE
Tips
안드로이드 스튜디오를 보다 편리하게 사용할 수 있는 단축키들입니다.
기능 |
단축키 |
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 + → |
14 Aug 2017
|
리눅스 명령어
Grep
grep
는 파일의 내용을 확인해서 찾는 문자열이 포함되었는지 아닌지 찾아주는 명령어입니다. ‘Global Regular Expression Print’의 약어입니다.
사용 방법
grep -r "검색하고 싶은 문자열" /home/pi
-r
옵션은 하위 폴더까지 검색하도록 하는 옵션입니다. /home/pi
는 검색 시작 위치입니다.
만약 대소문자 구분을 하지 않으려면 -i
옵션을 사용하면 됩니다. -E
옵션은 파일 내용의 대소문자 차이를 무시하고 정규 표현식으로 검색하는 옵션입니다.
Regular Expression
grep
에서 사용할 수 있는 간단한 정규식은 대표적으로 다음과 같습니다.
코드 |
설명 |
( ) |
그룹화 |
| |
좌우 중 하나 |
? |
직전의 표현이 0회 또는 1회 등장 |
* |
직전의 표현이 0회 이상 연속해서 등장 |
+ |
직전의 표현이 1회 이상 연속해서 등장 |
. |
임의의 한 문자 |
^ |
줄 머리 |
$ |
줄 끝 |
14 Aug 2017
|
Android
UX
사용자 정의 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>
13 Aug 2017
|
C++
네트워크
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