구글이 제공하는 안드로이드의 

Material design incon들이 

나름 산뜻한 느낌을 주는 디자인들이다.

이런 아이콘들을 앱에 사용하고자 한다면 

다음 사이트에서 해당 아이콘들을 각각 다운로드 받을수 있다.


https://material.io/tools/icons/?style=baseline



이 중에서 특정 아이콘을 다운로드 받고자한다면 해당 아이콘을 클릭하면 다음과 같은 다운로드 선택 창이 하단에 보이게 된다.




위의 이미지에서 파란색 띠가 보이고 원하는 것을 선택하면 된다. 

선택할 수 있는 내용은 


 -. 이미지 크기(위의 경우는 23dp. 18dp, 24dp, 36dp, 48dp의 4종류가 있다)

 -. 이미 색상(black, white)

 -. 파일의 종류(svg, png형태)


예를들어서 Dark Action Bar의 경우는 balck 아이콘을 사용하면 아이콘이 보이지 않을 것이다. 따라서 이럴 경우는 white를 체크해서 다운 받으면 된다.

다운 받는 이미지 파일의 형태도 svg와 png 형태를 선택할 수 있다.







Handler를 이용해서 특정 시간 후에 특정 작업을 실행시키기


특정 시간 후에 특정한 작업이 실행되도록 하는 방법으로 AlarmManager를 이용할수 있지만 간단하게 처리할수 있는 방법으로는 Handler를 이용하면 더 쉽게 처리할수가 있다.

대표적으로 ProgressDialog를 특정 시간 경과 후 종료 시키는 방법을 구현한다면 아래와 같이 간단히 처리할수 있다.

아래 코드는 7초 후에 ProgressDialog를 중지시키는 코드이다.

실행하기 원하는 작업(코드)를 Runnable 인터페이스의 추상 메소드인 public void run() 안에 두면 run안의 코드가 특정 시간 경과후(여기서는 7초) 실행이 된다.

즉 ProgressDialog의 객체인 pDialog를 show() 한 후에 


      ProgressDialog pDialog = new ProgressDialog(this, 

                                                              AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);

      pDialog.setCancelable(false);

      pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);

      pDialog.setMessage("주변의 BLE 장치를 검색 중입니다.");

      pDialog.show();


      mHandler.postDelayed(new Runnable() {

          @Override

          public void run() {

              pDialog.dismiss();

          }

      }, 7000);



Handler 클래스의 해당 메소드를 보면 다음과 같이 되어 있다.


public final boolean postDelayed (Runnable r, long delayMillis)

⇒ Causes the Runnable r to be added to the message queue, to be run after the specified amount of time elapses. The runnable will be run on the thread to which this handler is attached.


r : The Runnable that will be executed.

delayMillis : The delay (in milliseconds) until the Runnable will be executed.


Runnable 객체를 message queue에 추가한 후에 delayMillis 시간 경과 후에 Runnalbe 객체 r을 실행시킨다. 이렇게 처리하면 Thread로는 처리 불가능한 UI 관련 작업도 처리할수 있다.


r : 실행할 코드를 담고 있는 Runnable 객체. Runnable의 추상 메소드 public void run()안에 있는 코드를 실행한다.

delayMillis : 여기에 설정된 시간만큼 경과 후에 r을 실행한다.





현재의 디바이스가 BLE(Bluetooth Low Energy)를 지원하는지 확인하는 법


일단은 Manifest파일에 아래의 permission을 추가해야한다.


    <uses-permission android:name="android.permission.BLUETOOTH"/>


    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>


    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>


BLE가 지원되는 여부를 파악하기 위해서는 PackageManager의 도움을 받으면 된다.

PackageManager의 객체는 Context 클래스에 있는 아래 메소드로부터 객체를 획득할수 있다.


public abstract PackageManager getPackageManager ()


그리고 PackageManager 클래스에 있는 아래 메소드를 통해 BLE 지원 여부를 확인할 수 있다.


public abstract boolean hasSystemFeature (String name)


아래는 코드 조각이다.


        PackageManager pkgMan = getPackageManager();

        if (pkgMan.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {

            Toast.makeText(this, "BLE가 지원되는 디바이스 입니다.", 1).show();

        } else {

            Toast.makeText(this, "BLE가 지원되지 않습니다.", 1).show();

        }






Android Studio에서 화면 넓게 사용하는 Tip


※ Android Studio 1.5 버전을 중심으로


요즘의 노트북들은 세로 화면보다는 가로 화면이 넓은 형태인데 eclipse를 사용하다가 Android Studio를 사용해 보면 화면이 좁다는 느낌을 갖게된다. 

이러한 불편함을 해소할수 있는 기능이 Android Studio에서는 아주 잘 제공이 되고 있다. 어떤 면에서는 감탄스럽다.


(1) 불필요한 몇 가지 Bar 표시 안되게

Status Bar와 Navigation Bar 표시 안되게

아래 그림과 같은 Status bar와 Navigation Bar는 굳이 없어도 될만한 정보이다. 이 2개의 bar를 표시되지 않게 할려면 View 메뉴에서 Status Bar와 Navigation Bar의 체크 상태를 해제하면 된다.




(2) Distraction Free Mode 사용하기

아래 이미지에서와 같이 오직 메뉴만 보여지는 상태로 코딩할수 있는 화면이다.

View - Enter Distraciton Free Mode를 클릭하면 아주 넓은 화면상태에서 여유롭게 코딩을 할수가 있다. 이 상태를 해제시는 다시 View메뉴로 들어가서 Exit Distraction Free Mode를 클릭하면된다. 





(3) Full Screen 사용하기
아래 이미지와 같이 Tool 버튼들과 각 클래스 탭만으로 보여주는 화면 상태가 Full Screen 상태이다. 
View - Enter Full Screen를 클릭하면 사용할수 있는 화면이다.
이때 만일 메뉴들을 보기 원하면 F10키나 마우스를 화면 최상단으로 옮기면 숨겨져 있는 메뉴가 다시 나타나게된다.
이 상태의 해제는 View - Exit Full Screen을 클릭하면된다.



(4) Presentation Mode 사용하기
아래 이미지와 같이 코딩할수 있는 공간 외에는 다른 아무것도 화면에 보이지 않는 상태이다. 글자 크기도 큼지막하게 시원한 화면에서 코딩할수 있다. 물론 이 화면은 프리젠테이션을 목적으로 하는 화면이다. 
여기서 메뉴를 볼려면 F10이나 마우스를 화면 최상단으로 옮기면 메뉴가 다시 보여지게된다.





adb shell을 이용한 안드로이드 앱 apk 삭제하기


DOS 창에서 안드로이드 SDK가 설치된 폴더의 

platform-tools 폴더로 이동해서 


adb uninstall 패키지명


ex)C:\....\platform-tools>adb uninstall com.test.myapp



패키지 명을 알려면 DOS 창에서

adb shell

# cd /data/data

# ls를 하면

설치된 패키지들이 죽~ 나온다.

여기서 삭제하기 원하는 패키지(앱)를 확인하면 된다.



안드로이드 스튜디오(Android Studio)에서 구글 샘플 소스 import 하기


※ Android Studio 1.5 버전을 기준


File - New - Import Sample... - Import Sample이라는 창이 뜨고 여기서 원하는 sample 소스를 import 하면된다.







Sample 소스가 import가 완료되면 위의 그림과 같이 선택할 수 있는 몇 가지 동작에 대한 간략한 소개 화면이 뜬다.

여기서 Alt+1(숫자 1)을 누르면 화면 좌측에 드디어 project 및 파일 구조를 보여주는 view가 보여진다. 

여기서부터 이제 샘플 소스를 주무를 수 있게 된다.





Android 카메라 영상이나 사진이 어두울 때 밝게 하는 방법


카메라로부터의 영상과 사진을 밝게 하는 방법으로는 노출(exposure) 값을 조정하는 방법을 통해서 원하는 기능을 구현할수 있다.

Camera.Parameters 클래스에는 카메라에 다양한 옵션들을 설정할수 있는 기능과 해당 값들이 마련되어 있다.

카메라의 zoom 조절, color에 대한 effect 설정, 초점 관련 설정, white balancing 설정, 그리고 노출정도에 대한 설정 등이다.

이 중에서 영상(사진)의 밝기를 결정하는 첫번째가 노출 값을 조정하는 것이고 또 다른 하나는 영상 밝기에 직접적인 영향을 주는 것은 아니나 

white balance를 통해서 일정부분 결과를 만들어낼수 있다. 

밝기 문제를 해결하는 핵심은 노출(exposure)의 값을 변경하는 것이다.

Camera.Parameters 클래스의 아래 메소드를 통해서 간단히 해결할수 있다.


public void setExposureCompensation (int value)


여기서 매개인자 value에 들어갈 값의 범위는 디바이스들 마다 각기 상이하다. 따라서 이 값을 결정하는 메소드가 2가지 제공이 된다.

getMinExposureCompensation()의 값에서 getMaxExposureCompensation()까지의 값이 value에 들어갈 값이다.

보틍은 -n ~ +n까지가 되는데 0이면 노출을 적용하지 않는 것이고 음수이면 기본 밝기보다 더 어두워지고 양수 값이면 기본 밝기 보다 더 밝아진다.


여기서 주의 할 것은 이렇게 설정된 각종 parameter 값들이 적용되는 시점은 android.hardware.Camera 클래스의 setParameters()가 실행될 때이다.


public void setParameters (Camera.Parameters params)


그런데 주의해야할 것은 이 메소드 실행 전에 각종 parameter 값들을 설정시 어떤 parameter 값들은 적용이 안되는 디바이스들이 있다. 이건 디바이스마다 상이하다.

만일 설정되어 있는 parameter 중에서 특정 디바이스에 적용이 안되는 값이 설정되어 있으면 setParameters (Camera.Parameters params) 메소드 실행시 앱이 강제 종료되는 수가 있다. 

예를 들어 아래의 parameter들은 갤S4에서는 강제종료된다. 


parameter.setSceneMode(Camera.Parameters.SCENE_MODE_PORTRAIT); 

parameter.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);


그런데 이렇게 강제 종료되지 않도록 Exception 처리를 해 두면 앱은 강제 종료가 되지 않지만 setParameters (Camera.Parameters params)가 정상 실행되지 않았기 때문에

위에서 설정했던 parameter들이 전혀 적용이 되지 않는 상황이 벌어진다. 왜 적용이 안되는가 혼란에 빠질수가 있다.

아래의 코드가 그 예이다.


try {

parameters.setPreviewFrameRate(10);

parameters.setSceneMode(Camera.Parameters.SCENE_MODE_PORTRAIT); 

parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);


//위의 두 parameter때문에 여기서 exception 발생하고 이 메소드 실행 안됨

//따라서 앞에서 설정했던 모든 parameter들이 하나도 적용이 안된다.

mCamera.setParameters(parameters); 

} catch (Exception ex) {

Toast.makeText(mContext, "setParameters failed~", 1).show();

}


이 문제에 대한 해법은 아래와 같이 처리해 줘야 된다.


//public List<String> getSupportedSceneModes ()

List<String> sceneModeList = parameters.getSupportedSceneModes();


//public List<String> getSupportedFocusModes ()

List<String> focusModeList = parameters.getSupportedFocusModes();


try {

parameters.setPreviewFrameRate(10); 

if (sceneModeList != null && 

                       sceneModeList.contains(Camera.Parameters.SCENE_MODE_PORTRAIT)) {

parameters.setSceneMode(Camera.Parameters.SCENE_MODE_PORTRAIT); 

}

if (focusModeList != null && 

                      focusModeList.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {

parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);

}

mCamera.setParameters(parameters);

} catch (Exception ex) {

Toast.makeText(mContext, "setParameters failed~", 1).show();

}


아래는 코드 조각이다.


public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

if (mCamera == null) return;

Camera.Parameters parameters = mCamera.getParameters();


//WHITE_BALANCE_FLUORESCENT 

//WHITE_BALANCE_DAYLIGHT 

//WHITE_BALANCE_CLOUDY_DAYLIGHT 

//WHITE_BALANCE_INCANDESCENT 

//WHITE_BALANCE_SHADE : 약간 어둡다.

//WHITE_BALANCE_WARM_FLUORESCENT : 약간 어둡다.

parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); 

int maxExpo = parameters.getMaxExposureCompensation();

parameters.setExposureCompensation(maxExpo); 


List<String> sceneModeList = parameters.getSupportedSceneModes();

List<String> focusModeList = parameters.getSupportedFocusModes();

try {

parameters.setPreviewFrameRate(10); 

if (sceneModeList != null &&  

                              sceneModeList.contains(Camera.Parameters.SCENE_MODE_PORTRAIT)) {

parameters.setSceneMode(Camera.Parameters.SCENE_MODE_PORTRAIT); 

}

if (focusModeList != null && 

                            focusModeList.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)){

 parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);

}

mCamera.setParameters(parameters);

} catch (Exception ex) {

Toast.makeText(mContext, "setParameters failed~", 1).show();

}

mCamera.startPreview();






Intent에 primitive 데이터들(int, float, String...)을 실어서 다른 Activity로 전송하는 것은 쉽게 구현이 가능하다. 그러나 객체 자체를 넘기는 것은 그냥은 안된다.

이를 위해 필요한 클래스가 ParcelParcelable이다.


  • Parcel

⇒ Container for a message (data and object references) that can be sent through an IBinder. 

Parcel은 객체를 IBinder를 통해서 다른 Process, 다른 Activity에 실어나르기 위한 컨테이너 역할을 한다.

한 마디로 Parcel에 객체(클래스)를 담아서 이 Parcel을 Intent에 실어서 보내면 된다.

예를 들어 SomeObject라는 클래스가 Parcel에 담겨 있다면 다음과 같은 코드가 가능하다.


SomeObject so = new SomeObject(arg1, arg2);

Intent it = new Intent(packageName, packageName.호출할ClassName);

it.putExtra("SOME_KEY", so);

startActivity(it);


이렇게 특정 Process나 Activity로 객체 so를 전송할 수 있다.

이렇게 전송하기 위해서는 SomeObject를 직렬화 해야 되는데 객체 직렬화 처리를 해 주는 interface가 Parcelable이다.

Intent에 담아 전송하고자 하는 클래스가 Parcelable를 implements해 주면 해당 클래스의 직렬화는 Parcelable이 담당해 준다.


  • Parcelable

⇒ Interface for classes whose instances can be written to and restored from a Parcel. 

직렬화해서 보낼 객체를 Parcel에 담거나 Parcel에 담겨진 직렬화된 객체를 복원하는 역할을 해주는 interface이다.


객체를 Intent에 담아서 전송하기 위해 처리해 주어야 할 가장 중심 작업은 2가지이다.

(1) 객체(class)의 멤버 변수를 Parcel에 담는 행위(전송하는 쪽에서 필요한 기능)

 -. public void writeToParcel(Parcel dest, int flags)에서 객체의 데이터를 Parcel에 담는 행위


public class FacialFeatures implements Parcelable{

private int pinNumber; //직렬화 할 실제 데이터

private byte[] faceFeatures; //직렬화 할 실제 데이터

public FacialFeatures(int pin_num, byte[] facialFT){

pinNumber = pin_num;

faceFeatures = facialFT;

}

@Override

public int describeContents() {

return 0;

}

//아래 메소드에서 직렬화할 객체의 데이터를 Parcel에 담는다.

//송신할 때 플랫폼의 IPC에서 이 메소드 자동 호출

@Override

public void writeToParcel(Parcel dest, int flags) {

dest.writeInt(pinNumber); //Parcel에 객체의 데이터 담기

dest.writeByteArray(faceFeatures); //Parcel에 객체의 데이터 담기

}



(2) Parcel에 담겨진 객체를 읽어내는 행위(수신쪽에서 필요한 기능)

-. Intent에 담아 다른 Process나 다른 Activity로 넘길 클래스에 CREATRO라는 static 멤버 변수를 선언해 주어야 한다. CREATOR의 데이터 type은 Parcelable.Creator 타입인데 Parcelable.Creator는 interface이므로 2개의 추상 메소드를 재정의 해 주어야 한다.

수신쪽에서 CREATOR라는 변수를 이용해서 Parcel에 담겨진 객체를 읽어낸다. 


public static Parcelable.Creator<FacialFeatures> CREATOR = new Parcelable.Creator(){

//아래 메소드가 Parcel에 담겨져 있는 객체 데이터를 읽는 기능 수행

//수신시 플랫폼의 IPC에서 이 메소드 자동 호출

@Override

public FacialFeatures createFromParcel(Parcel source) {

int _pinNum = source.readInt();

byte[] _faceFeatures = source.createByteArray();

return new FacialFeatures(_pinNum, _faceFeatures);

}

@Override

public FacialFeatures[] newArray(int size){

return new FacialFeatures[size];

}

};


정리하면 객체를 Intent에 담아서 다른 Process나 다른 Activity에 전송할려면 해당 객체를 직렬화 해 주어야 하는데 이 기능을 담당해 주는 Interface가 Parcelable이다.

객체를 직렬화 하기 위해서는 2가지를 처리해 주어야 하는데

(1) public void writeToParcel(Parcel dest, int flags)에서 해당 객체의 데이터를 Parcel에 담기

(2) Parcelable.Creator 타입의 static 변수 CREATOR를 재 정의하는 메소드 public T createFromParcel (Parcel source)에서 Parcel에 담겨져 있는 객체의 데이터를 읽는 작업을 처리해 주면 객체를 Intent에 담아서 전송하는 것이 가능하다.


그런데 이러한 실행 중 다음과 같은 에러를 종종 만나게 된다. 


java.lang.RuntimeException: bad array lengths


이 에러의 원인은 객체를 직렬화하면 객체의 데이터(멤버 변수)가 순서대로 일렬로 정렬되는데 해당 데이터의 범위를 벗어났을 경우 통상적으로 발생하는 에러이다.

특히 직렬화 할 데이터 중에 배열이 포함되어 있을 때 다음과 같은 실수를 범하기 쉽고 이때 발생하는 문제이다.


public static Parcelable.Creator<FacialFeatures> CREATOR = new Parcelable.Creator(){

@Override

public FacialFeatures createFromParcel(Parcel source) {

int _pinNum = source.readInt();

//public final byte[] createByteArray ()

//==>Read and return a byte[] object from the parcel.

//createByteArray()가 그냥 크기만 Parcel에 있는 byte 배열과 동일한 사이즈를 가진

//비어 있는 array를 만드는 것이 아니라 다음 2가지를 동시에 처리한다.

// (1) Parcel에 있는 배열의 크기와 동일한 크기의 배열을 만들고

// (2) Parcel로부터 배열의 내용까지 read해서 = 좌측에 대입시키는 것까지를 처리한다.

//따라서 createByteArray()를 하고 나면 직렬화 순서상 그 다음 순서로 position이 

//넘어 간다. 따라서 createByteArray() 후에 readByteArray(byte[] b)를 수행하면

//readByteArray()에서 아래와 같은 에러가 발생하고 앱이 crash된다.

//Caused by: java.lang.RuntimeException: bad array lengths

byte[] _faceFeatures = source.createByteArray();

//위의 createByteArray()에서 이미 배열을 읽었기 때문에 data position이

//이미 배열의 위치를 지나가 버렸다.

//따라서 readByteArray()를 하면 bad array lengths가 발생하는 것이다.

//따라서 readByteArray()로 읽든지 아니면 createByteArray()읽든지 둘 중 어느 

//하나만 있으면 된다.

// source.readByteArray(_faceFeatures);

return new FacialFeatures(_pinNum, _faceFeatures);

}

@Override

public FacialFeatures[] newArray(int size){

return new FacialFeatures[size];

}

};




구글 제공 API 데모(샘플) 소스 이클립스에서 import 하는 법


File - New - Project - Build Target의 API 버전을 선택하면 해당 API 데모 소스들이 전부 목록으로 뜬다.

여기서 원하는 샘플 소스를 선택하면 된다.



구글의 안드로이드 개발자 사이트에서 제공해주는 샘플 소스가 있는데 이런 것들은 완전한 프로젝트 형태로 주어지는 것이 아니다. 

따라서 일반적인 프로젝트 import식으로는 안된다.


File - New - Project - Android 하위 항목의 Android Project from Existing Code - Root Directory 항목에 샘플 소스가 있는 위치(디렉토리) 지정, Copy projects into workspace 항목 체크 - Finish 클릭


※ 일반적인 완성된 프로젝트 import하는 법

File - import - General 하위 항목의 Existing Projects into Workspace - Next - Select root directory 항목에 import할 프로젝트 위치 지정 - Copy projects into workspace 항목 체크 - Finish




지역변수에 final이 붙을 때의 의미에 대해


만일 아래 코드에서 mText에 final이 붙지 않은 상태에서  

layout.setOnTouchListener()에서 mText를 사용하고자 하면

컴파일 단계에서 아래와 같은 에러가 발생한다.


Cannot refer to a non-final variable mText inside an inner class defined in a different method


이유가 뭘까? 같은 onCreate() 메소드 안에 있는 변수인데...?

그 이유는...


public class AAAActivity extends Activity  {

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        

        final TextView mText = (TextView)findViewById(R.id.textV);

        

        LinearLayout layout = (LinearLayout)findViewById(R.id.linear);

        layout.setOnTouchListener(new View.OnTouchListener() {

          public boolean onTouch(View v, MotionEvent e) {

              mText.setText("Touched"); //에러 발생

                return true;

          }

        });

    }


  • mText는 전역변수도 아니고, onTouch()의 지역 변수도 아니며 onCreate()의 지역 변수로 선언되어 있다.
  • 리스너인 onTouch()는 onCreate()에 속한 지역 메서드가 아니라는 점을 주의해야 한다. onCreate()가 실행시에 onTouch()가 같이 실행되는 것이 아니라 터치 이벤트 발생시 실행할 메스드로 등록만 할 뿐이다.
  • 에러가 발생하는 이유는 mText 변수는 onCreate()가 리턴되면 사라지는 지역 변수이다.  반면 onTouch()는 터치 리스너로 등록되며 onCreate()가  리턴된 후에라도 이벤트가 발생하면 언제든지 호출될 수 있다. onTouch()가 호출되었을 때는 mText 변수가 존재하지 않으므로 mText의 실제값을 참조할 길이 없다. 미래에 호출될 리스너에게 현재의 지역 변수값을 전달하는 것은 불가능하다.
  • 그런데 여기서 지역 변수에 final을 붙이면 더 이상 변경할 수 없는 상수가 되므로 onTouch()를 등록하는 시점에 그 값을 분명히 전달할 수 있다. 



+ Recent posts