터미널에서 tree 명령어 사용하기

|

tree 명령어

tree는 폴더 구조를 시각적으로 알아보기 쉽게 트리 형태로 보여주는 명령어입니다. 불행히도 Mac에는 이 명령어를 제공하지 않습니다. 그래서 Homebrew를 이용해서 tree를 설치해보도록 하겠습니다.

먼저 Homebrew를 설치해야 합니다.

tree 설치는 다음 명령어를 이용해서 진행할 수 있습니다.

brew install tree

설치 후 tree 명령어를 수행하면 다음과 같이 폴더 구조를 트리 형태로 표현해줍니다.

image

Homebrew 설치하기

|

Homebrew

Mac에서 홈브류(Homebrew)는 패키지 매니저(Package Manager)로 생각하면 됩니다. ruby로 개발되어졌으며, 애플에서 제공하지는 않지만 유용한 프로그램들을 손쉽게 다운로드하고 설치할 수 있게 해주고 있습니다.

image


설치 방법

Homebrew를 설치하기 위해서는 먼저 CLD(Command Line Developer Tool)를 설치해야 합니다. 터미널에서 다음 명령어를 입력하면 됩니다.

xcode-select –install

그 다음에는 다음 명령어를 이용하여 Homebrew를 설치합니다.

ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”

최신 설치 명령어는 여기에서 확인할 수 있습니다.

설치를 시작하면, ‘Press RETURN to continue or any other key to abort’ 라는 메세지가 나오고 Return 키 를 누르면 설치가 시작됩니다.

Mac에서 안드로이드 SDK의 Path 설정

|

Mac에서 터미널을 통해 adb 명령어를 실행하고 싶을 때가 종종 있습니다. Windows에서는 Android SDK가 설치된 경로를 환경변수로 등록해놓으면 되는데, Mac에서도 방법은 비슷합니다. 다만, Mac에서 Android SDK가 설치되어 있는 폴더 위치를 몰라서 Path 등록을 못하는 경우가 종종 있습니다.


Mac에서 Android SDK가 설치되는 폴더

사용자가 임의로 폴더 위치를 수정하지 않았다면, 기본적으로 Android SDK는 다음 위치에 설치됩니다.

/Users/snowdeer/Library/Android


.bash_profile 수정

환경 변수를 등록하기 위해서 .bash_profile을 수정하면 됩니다. 터미널에서 nano를 이용해서 수정을 해도 되고, 편하게 GUI 상의 텍스트편집기를 이용해서 수정을 해도 됩니다. 기본적으로 .bash_profile은 속성이 ‘숨김(Hidden)’으로 되어있습니다. Finder에서 숨김 파일을 보기 위해서는 Finder 창 안에서 단축키 shift + command + . 을 누르면 됩니다. .bash_profile/User/[사용자계정] 아래에 위치하고 있습니다.

그리고 .bash_profile 내에 다음과 같은 라인을 추가합니다.

export ANDROID_PATH=/Users/snowdeer/Library/Android
export PATH=$PATH:$ANDROID_PATH/sdk/platform-tools

이제 터미널을 다시 시작하고 adb 명령어를 입력해보면 잘 실행되는 것을 확인할 수 있습니다.

Map, Set 등 배열 내부 값 순회하기

|

프로그래밍을 하다보면, 리스트를 쓰는 경우는 피할 수가 없을 것입니다. 가장 많이 쓰는 것들이 일반적인 배열이나 ArrayList 들입니다. 그리고 성능이나 여러가지 장점을 위해 HashMap 등을 사용하게 되는 경우도 많은데 일반적인 배열들과 사용법이 조금 달라 혼란스러운 경우가 종종 있습니다.

물론, 구글링을 하면 바로 나오긴 하지만 나중에 좀 더 찾기 쉽게 하기 위해 여기다가 예제 코드를 작성해봅니다.


Iterator 사용

가장 기본적인 건 Iterator를 사용하는 것입니다. Iterator는 디자인 패턴 중 하나이기도 하고, 거의 모든 리스트에 적용가능할 정도로 범용적입니다. 하지만, 보통 개발자들은 Iterator 없이 단순 인덱스만 이용해서 for 문이나 while 반복문으로 접근하는 경향이 있습니다. 둘 다 문제없는 사용법이긴 하지만, 여기서는 먼저 Iterator를 이용한 접근 방법부터 살펴보도록 하겠습니다.

Map<String, String> map = new HashMap<>();

map.put("Key1", "Value1");
map.put("Key2", "Value2");
map.put("Key3", "Value3");

Iterator<String> iter = map.keySet().iterator();

System.out.println("#1. Using Iterator");
while(iter.hasNext()) {
    String key = iter.next();
    System.out.println(" -- " + key + ", " + map.get(key));
}


Set<String> set = new HashSet<>();

set.add("Value4");
set.add("Value5");
set.add("Value6");

Iterator<String> iter = set.iterator();

System.out.println("#1. Using Iterator");
while(iter.hasNext()) {
    String value = iter.next();
    System.out.println(" -- " + value);
}


for 문 사용

for 문을 사용하더라도 간단하게 조회할 수 있습니다. 다음 예제는 각각 Map과 Set에 대한 for 문으로의 접근 방법입니다.

System.out.println("#2. Using For");
for(String key : map.keySet()) {
    System.out.println(" -- " + key + ", " + map.get(key));
}


System.out.println("#2. Using For");
for(String value : set) {
    System.out.println(" -- " + value);
}

Notification 정보 가져오기

|

안드로이드 노티피케이션(Notification)의 정보를 가져오는 코드를 작성해보도록 하겠습니다. 안드로이드에서 NotificationListenerService라는 서비스 형태의 컴포넌트를 제공하고 있습니다. API 버전 18부터 사용가능하며, 원할하게 쓰려면 API 버전 19 이상을 추천합니다.

안드로이드 SDK에 기본으로 탑재되어 있기 때문에 gradle 등에 별도로 추가할 필요는 없고, manifest.xml에 다음과 같이 서비스를 등록해주면 됩니다.


AndroidManifest.xml

<application
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:supportsRtl="true"
  android:theme="@style/AppTheme">
  ...
  <service
    android:name=".SnowNotificationListenerService"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
      <action android:name="android.service.notification.NotificationListenerService"/>
    </intent-filter>
  </service>
</application>


그리고, Java 코드는 다음과 같습니다.

SnowNotificationListenerService.java

import android.app.Notification;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;

public class SnowNotificationListenerService extends NotificationListenerService {

  @Override
  public void onCreate() {
    super.onCreate();
    Log.i("NotificationListener", "[snowdeer] onCreate()");
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i("NotificationListener", "[snowdeer] onStartCommand()");
    return super.onStartCommand(intent, flags, startId);
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.i("NotificationListener", "[snowdeer] onDestroy()");
  }

  @Override
  public void onNotificationPosted(StatusBarNotification sbn) {
    Log.i("NotificationListener", "[snowdeer] onNotificationPosted() - " + sbn.toString());
    Log.i("NotificationListener", "[snowdeer] PackageName:" + sbn.getPackageName());
    Log.i("NotificationListener", "[snowdeer] PostTime:" + sbn.getPostTime());

    Notification notificatin = sbn.getNotification();
    Bundle extras = notificatin.extras;
    String title = extras.getString(Notification.EXTRA_TITLE);
    int smallIconRes = extras.getInt(Notification.EXTRA_SMALL_ICON);
    Bitmap largeIcon = ((Bitmap) extras.getParcelable(Notification.EXTRA_LARGE_ICON));
    CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
    CharSequence subText = extras.getCharSequence(Notification.EXTRA_SUB_TEXT);

    Log.i("NotificationListener", "[snowdeer] Title:" + title);
    Log.i("NotificationListener", "[snowdeer] Text:" + text);
    Log.i("NotificationListener", "[snowdeer] Sub Text:" + subText);
  }

  @Override
  public void onNotificationRemoved(StatusBarNotification sbn) {
    Log.i("NotificationListener", "[snowdeer] onNotificationRemoved() - " + sbn.toString());
  }

}


참고로 이 서비스는 별도로 startService를 해주지 않아도, 사용자의 권한만 주어지면 자동으로 시작되기 때문에 startService를 구현할 필요는 없습니다.

사용자의 권한을 요청하는 코드는 다음과 같습니다.

Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(intent);


하지만, 위 코드를 매번 호출하는 것은 사용자에게 상당히 번거로운 일이기 때문에, 다음과 같은 코드를 이용해서 기존에 사용자가 해당 App에 권한을 부여한 적이 있는지 확인하는 것이 좋습니다.


MainActivity.java

import android.content.Intent;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

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

    boolean isPermissionAllowed = isNotiPermissionAllowed();

    if(!isPermissionAllowed) {
      Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
      startActivity(intent);
    }
  }

  private boolean isNotiPermissionAllowed() {
    Set<String> notiListenerSet = NotificationManagerCompat.getEnabledListenerPackages(this);
    String myPackageName = getPackageName();

    for(String packageName : notiListenerSet) {
      if(packageName == null) {
        continue;
      }
      if(packageName.equals(myPackageName)) {
        return true;
      }
    }

    return false;
  }
}