728x90

Android Studio 샘플 예제를 받은 걸 가지고 패키지명 변경을 하는 방법이다.


먼저 폴더를 선택한 다음에 3번까지 하고 나면, 폴더가 펼쳐져서 보인다.



이제 변경하고 싶은 곳에 마우스로 선택한 다음, Shift + F6 키를 누르면 패키지명 변경할 수 있게 나온다.



패키지명을 입력하고 Refactor 를 해주면 Do Refactor 가 나온다. 눌러주면 변경된다.


다른 폴더명도 이렇게 변경해주면 된다.


만약 폴더가 모자라면 수동으로 직접 폴더명을 생성해주면 자동으로 인식된다.

그러고나서, 파일을 마우스로 Drag & Drop 으로 해당 폴더에 옮겨준다.


이렇게 하고 나면 AndroidManifest.xml 파일이 변경된 것을 확인할 수 있다.


그런데 자동으로 변경되지 않는 것이 있다.

앱 build.gradle 정보는 변경되지 않는다.

applicationId 값을 수작업으로 변경해줘야 한다.

이걸 안해주었더니 "Error: Activity class does not exist." 가 나와서 에러 메시지 찾아서 수정하느라고 생쑈를 조금 했다.


apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "25.0.0"

    defaultConfig {
        applicationId "com.tistory.link2me.searchview"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
} 



블로그 이미지

Link2Me

,
728x90

Android Studio 에서 모듈을 새로 추가하는 순서를 이미지로 적어본다.




Application Name 을 적는다.





여기까지 하고 나면 관련 파일이 생성된다.




여기까지 했으면 이제 build.gradle 파일을 열어서 수정한다.




위 그림처럼 dependencies 에 추가하고 싶은 라이브러리를 추가해준다.

여기서 compileSdkVersion 정보는 수정하지 않아도 되고 필요한 버전으로 수정하면 된다.



마지막으로

를 눌러준다.


혹시라도 이런 메시지가 나오면 ....

AndroidManifest.xml 파일을 수정해준다.

android:roundIcon="@mipmap/ic_launcher_round"

이 한줄을 삭제하고 저장하면 다시 Sync Now 를 눌러주면 메시지가 없어진다.


이렇게 하면 모듈 추가가 완성된 것이라고 보면 된다.


compile 'org.jsoup:jsoup:1.10.3' 를 추가한 이유는 Web Site 파싱 관련 라이브러리 이기 때문이다.

https://jsoup.org/download 에 가면 최신버전 정보가 나온다.

Web Site 파싱 연습을 해보고자 한다.

가장 먼저 내 홈페이지 로그인 기능을 해보려고 한다.

블로그 이미지

Link2Me

,
728x90

Eclipse res (resource) 폴더에 drawable 폴더가 있다.

Android Studio res (resource) 폴더에 drawable 폴더와 mipmap 폴더가 있다.


mipmap/ 폴더는 런쳐 아이콘 이미지 자원만을 위한 폴더
drawable/ 폴더는 이미지 자원을 위한 폴더
drawable-hdpi
drawable-ldpi
drawable-mdpi
drawable-xhdpi
mdpi, hdpi, xhdip 등과 같은 단말의 밀도(density)에 따라 가장 적합한 이미지를 보여준다


Eclipse 기반으로 개발된 소스를 수작업으로 변경할 경우 이미지 폴더는 같은 폴더로 복사하여 옮기면 된다.

그리고 java 코드는 수작업으로 변경하는 편이 편하다.

import 로 변경한 것이 제대로 동작이 안될 수도 있다.


블로그 이미지

Link2Me

,
728x90

Android Studio 상에서 코드 구현 작업을 하던 것을 집과 사무실에서 하다보면 코드를 변경한 것이 어떤 것이 최종인지 헷갈리는 경우가 생긴다.


이럴 경우에는 코드를 육안으로 일일이 비교한다는 건 정말 힘들다.

그렇다고 날짜로만 최신날짜로 덮어쓴다(?)는 것은 정말 위함한 생각이다.


내가 사용하는 방법은 AcroEdit 를 설치하면 포함되어 있는 AcroDiff.exe 파일을 이용한다.


더 좋은 파일 내용비교 프로그램이 있을 수도 있지만, 몇개의 프로그램을 사용해본 결과 이것이 최고로 편하더라.

AcroDiff.exe

첨부된 이 파일만 받아서 이걸 실행하고 파일을 마우스로 Drag & Drop 하여 비교하면 된다.



Total Commander 로 양쪽 파일을 선택하여 마우스로 끌어다가 AcroDiff.exe 파일에 Drop 한다.

화면상에 보면 노란색으로 표시된 부분이 보인다. 명칭을 변경하여 서로 다른 걸 알 수 있다.

달라진 부분을 Android Studio  상에서 Shift + F6 를 눌러서 명칭을 변경해준다.

이렇게 해야만 코드상에서 달라진 명칭을 한번에 모두 변경할 수 있다.

그런 다음 위 그림 1번을 다시 누르면 서로 100% 일치한다고 나오면 양쪽 코드는 동일하다는 결과다.


구글링이나 블로그에서 찾은 코드를 붙여넣기, 프로젝트 읽어오기 등을 하다보면 본인이 원하는 명칭과 다르면 코드 분석이나 작업하는데 불편해서 Android Studio 에서 Shift + F6 으로 명칭을 일괄 변경하면 매우 편하더라.


집과 사무실에서 작업한 코드가 다를 수도 있을 경우에는 이런 방법으로 하면 되지만...

집에서 작업하던 걸 사무실에 가서 작업을 한다고 할 경우에는

src 폴더 밑에 main 폴더만 복사해서 사무실 노트북에 덮어쓰기 또는 사무실 해당 main 폴더를 삭제하고 복사하기를 하면 바로 인식한다.


이렇게 하는 것은 Android Studio 환경이 서로 거의 유사하다는 조건에서 가능하다.

gradle 설정 정보 등은 동일하므로 굳이 Project 내 모든 폴더 파일을 다 옮길 필요가 없더라.

간단하게 핵심이 되는 main 폴더만 복사하면 된다.



블로그 이미지

Link2Me

,
728x90

Android Studio 에서 폴더를 정리하는 방법이다.

코드를 이것 저것 추가하다보니 한 폴더에 너무 많은 내용이 있어서 내용 열람이 깔끔하지 않다.

그래서 새로운 폴더 common 을 윈도우 폴더 만들기에서 생성하고 나서 java 코드를 옮기는 작업을 했다.


옮기고자 하는 파일을 선택한 다음, 마우스로 common 폴더로 Drag & Drop 을 한다.



4번을 누르면.....


파일이 옮겨진 것을 확인할 수 있다.

파일내 package 경로명은 자동으로 변경된다.


마우스 Drag & Drop 으로 옮길 수 있어서 너무 편하고 좋다.


블로그 이미지

Link2Me

,
728x90

Android Studio 에서 모듈 삭제 방법이다.




모듈 삭제해서 화면에서는 안보이지만 실제 폴더에 가면 파일명은 그대로 있다.

즉 연결된 정보만 삭제되었을 뿐이라는 거다.

불필요하면 위 과정후에 실제 파일 경로를 직접 삭제처리 한다.

블로그 이미지

Link2Me

,
728x90

C:/Users/(계정 이름) 폴더에 들어가보면 .AndroidStudio라는 폴더가 보인다.

이 폴더를 변경하는 방법이다.


EditPlus 를 이용해서 아래 그림 파일을 연다.


아래와 같이 되어 있는 곳을 수정한다.


옮기고자 하는 폴더 경로를 적어준다.


블로그 이미지

Link2Me

,
728x90

Android Studio gradle 경로 변경 방법이다.



변경하고 싶은 폴더명을 적어주고 OK를 누르면 된다.



블로그 이미지

Link2Me

,
728x90

AVD configuration folder (.android) 설치경로가 C 드라이브 사용자 환경에 저장된다.

이 폴더에 Emulator 가 설치되는데 C 드라이드 용량을 엄청 많이 점유한다.

따라서 이 폴더를 다른 하드디스크로 옮겨야 한다. C 드라이브 용량이 절대적으로 부족하기 때문이다.

노트북 SSD를 교체하려고 뜯어보니까 일반 SSD 가 아니더라. 그래서 포기하고 F 드라이브(용량 500GB HDD)로 옮기기로 했다. Android Studio 및 sdk 는 이미 옮겨서 설치한 상태인데 환경설정에 대한 용량도 커서 C드라이 공간 확보가 절대적으로 필요하다.


옮기는 방법은

C 드라이드 User 환경에 있는 .android folder를 옮기고자 하는 드라이드(ex, D 드라이드, F드라이브 등)로 복사하거나 이동시킨다.

이동을 시킬 경우에는 Android Studio 가 실행되지 않는 상태에서 하는게 좋다.

F:\Android\AndroidStudio_config 로 옮겼다.



다음에 할 일은 default 경로를 변경해야 한다.

윈도우 설정 아이콘을 누른다.


설정화면에서 system 을 입력한다.


고급 시스템 설정보기를 클릭한다.


고급 탭에서 환경변수를 클릭한다.



윈도우 폴더에서 .android 를 옮긴 폴더명이 있는 곳에서 마우스 우클릭하여 주소 복사를 한다.


변수이름에 ANDROID_SDK_HOME 을 입력하고 변수 값에는 복사한 경로를 붙여넣기 한다.

여기까지 하고 확인버튼 누르고 다시 확인 버튼을 누르면 끝이다.


이제 제대로 변경된 것인지 확인해보자.

AVD Manager.exe 파일을 실행하면...


2번과 같이 경로가 변경된 것을 확인할 수 있다.


블로그 이미지

Link2Me

,
728x90

모듈을 추가하는 것은 Contacts Demo 파일을 순수하게 Android Studio 버전에서 생성하는 과정이라고 볼 수 있다.

모듈 추가 과정을 상세하고 보고 싶다면 확인해라.


이제 build.gradle 부분이 http://link2me.tistory.com/1304 와 어떻게 다른지 살펴보자.


apply plugin: 'com.android.application'

android {
    compileSdkVersion 15
    buildToolsVersion "25.0.3"

    defaultConfig {
        applicationId "com.prince.contacts"
        minSdkVersion 8
        targetSdkVersion 15
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile 'com.android.support:support-v4:18.0.0'
}

 apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"

    defaultConfig {
        applicationId "com.tistory.link2me.contacts_demo_for_studio"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}


build.gradle 부분이 다른 걸 확인했으니 코드를 복사해서 붙여넣기 했더니 문제없이 잘 동작한다.

다만, AndroidMenifest.xml 에서 약간 수정할 부분이 좀 있었다.


첨부된 소스코드는 Android Studio 에서 완전히 작성된 코드라고 보면 된다.


Contact_Demo.zip



Eclipse 버전의 샘플 소스는 http://link2me.tistory.com/1301 에 올려져 있다.


블로그 이미지

Link2Me

,
728x90

Contacts Demo 앱을 Android Studio 용으로 변환을 시도한 결과를 적어둔다.


=== build.gradle (Module) ====

 apply plugin: 'com.android.application'

android {
    compileSdkVersion 15
    buildToolsVersion "25.0.3"

    defaultConfig {
        applicationId "com.prince.contacts"
        minSdkVersion 8
        targetSdkVersion 15
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile 'com.android.support:support-v4:18.0.0'
}

 === build.gradle (Project) ====

 buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}


=== AndroidManifest.xml ===

 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.prince.contacts"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="AddContact"
            android:label="@string/add"
            android:theme="@android:style/Theme.Dialog" >
        </activity>
        <activity
            android:name="DeleteContacts"
            android:label="@string/delete"
            android:theme="@android:style/Theme.Dialog" >
        </activity>
    </application>

</manifest>


Eclipse 에서 Android Studio 용으로 변환을 하면 주의해서 살펴볼 내용을 위 사항이라고 본다.


jave 소스코드와  Layout 파일은 build.gradle 이 달라짐에 따라서 에러,경고 등이 생길 수 있다.

높은 버전에서 문제없이 코드가 동작되도록 하는 것은 상황에 맞게 수정해야 한다.

블로그 이미지

Link2Me

,
728x90

Android Studio 3.1.4 버전에서는 아래 사항을 무시해도 된다.

이미 문제를 해결하여 반영한 거 같다.


Android Studio 3.0 으로 업그레이드/설치하고 나서 Preview Layout 이 보이지 않을 경우 설치된 Android Studio 에서 \plugins\android\lib\layoutlib\data\fonts\ 에 fonts.xml 파일을 찾아서 아래 파일을 받아서 덮어쓰기하면 된다.


fonts.xml




아래 사항은 Android Studio 2.X버전에서 Layout 미리보기를 하면 한글이 깨져보이는 증상을 해결하는 방법이다.


font.xml 파일을 EditPlus로 열어서 보면

<family lang="ko">
    <font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
</family>

라고 되어 있는 부분을 아래 그림과 같이 수정하면 된다.


NanumGothic.ttf 파일은 설치되어 있다.

Layout PreView 에 설정된 폰트 NotoSansCJK-Regular.ttc 가 MAC 용에서 인식하는 한글이라고 한다.

즉 Android Studio 개발을 MACBook 에서 개발할 것으로 보고 만들었나 보다.

윈도우 운영체제에서 인식이 잘 안되는 이유가 여기에 있었던가 보다.

실제 Macbook 을 사용하시는 분이 맥북에서는 Android Stuio 인식이 엄청 빠른데 윈도우에서는 상당히 느리다고 하더라.

폰트 변경을 해주었으면 바로 자동 인식은 안되니까 Android Stuio 를 종료했다가 재실행하면 인식이 잘 된다.




블로그 이미지

Link2Me

,
728x90

Eclipse 에서 workspace 개념에 해당되는 부분이 Android Studio 에서는 Project 라고 보면 된다.


그림에서 보는 바와 같이 app, connhandler, looper, thread_progressbar 는 모듈이다.

Eclipse 의 프로젝트 라고 볼 수 있다.



한 프로젝트에서 모듈만 새로 추가하면서 작성하면 위 그림처럼 나온다.

그럼 모듈을 새로 추가를 해보자.







모듈이 추가된 것을 확인할 수 있다.


비슷한 유형이라면 모듈을 추가해서 관리하면 기존 소스 복사도 편하고 좋다.




블로그 이미지

Link2Me

,
728x90

Android Studio 를 사용하면서 알아두면 좋을 자동완성 기능을 추가해둔다.

나중에 오래되면 또 잊어버리고 기억이 하나도 나지 않을 것이기 때문이다.


builder.setItems(items, new D 까지 입력하면 팝업창이 나온다. 그러면 엔터키를 치면 자동완성이 된다.

final String[] items ={"휴대폰 전화걸기","사무실전화 걸기", "연락처 저장"};
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("해당작업을 선택하세요");
builder.setItems(items, new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {

    }
});


btn_chatting.setOn 까지 입력하면 팝업창이 뜨는데 Enter키를 누른다.

btn_chatting.setOnClickListener(new On); 까지 입력하면 팝업창 뜨고 Enter키를 누른다.

그러면 아래와 같이 자동 완성된 코드가 만들어진다.

btn_chatting.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       
    }
});

btn_chatting = (Button) findViewById(R.id.chatting);
btn_chatting.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this,Chatting.class);
        intent.addFlags(intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(intent);
    }
});


블로그 이미지

Link2Me

,
728x90

Android Studio 에서 Alt + Enter 키를 누르면 Import 가 자동으로 추가되는데 좀 더 편하게 하는 방법이다.





Updated 2020.4.4

코틀린에서 자동 추가가 안되어 찾아보니 아래와 같이 해주어야 제대로 되는 걸 확인할 수 있었다.


블로그 이미지

Link2Me

,
728x90

Project name 을 변경 시도하다가 실패했다.

그래서 eclipse 에서 Project name 을 변경시도했다.

http://beansarcade.tistory.com/29 를 보고 따라 했더니 금방 해결되었다.


다시 Android Studio 에서 import 로 불러와서 확인해보니 프로젝트명이 정상적으로 잘 되었다.

왜 이름을 변경하느냐? 고 묻는다면 FCM 연동 때문이다.


이제 FCM 연동을 할 차례다.


FCM(Firebase Cloud Message) 를 활용하여 Push 알림 메시지를 구현하고자 한다.

- 서버 게시판에서 특정 글을 등록하면 지정된 회원에게 Push 메시지 자동 전송


Firebase 클라우드 메시징(FCM)은 메시지를 무료로 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션이다.


eclips 에서 4.0.3 버전으로 컴파일 하던 것을 Android Studio 로 import 하니까

build.gradle (Module:App) 에

dependencies {
    compile 'com.android.support:support-v4:18.0.0'

}
라고 표시되었다.


아무 생각없이 신규 프로젝트 생성시 만들어진 걸 복사해서 넣었더니 문제가 발생했다.

알고 보니까 기존 compile 정보를 그대로 유지하면 문제가 되지 않았다.


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:support-v4:18.0.0'
    //compile 'com.android.support:appcompat-v7:25.3.0'
    testCompile 'junit:junit:4.12'

    //추가한 라인
    compile 'com.google.firebase:firebase-messaging:9.6.1'

    //서버로 데이터를 보낼때 okhttp 를 사용한다면 추가
    compile 'com.squareup.okhttp3:okhttp:3.2.0'
}



정작 문제는 FCM 메시지를 전혀 받지 못한다.


요즈음 구글이 GCM 에서 FCM 으로 변경을 권고하면서 GCM 연동하는 방법이 나온 것을 따라서 해보려고 해도 쉽지가 않다.

게다가 eclipse 에서 FCM 연동하는 방법을 아직 잘 모르겠다.

eclipse 버전이 낮으면 FCM 연동도 쉽지 않은거 같다.


그래서 eclipse 를 Android Studio 로 변경하는 것은 완전히 포기하고 새로운 파일을 생성해서 하는 걸로 시도중이다.



블로그 이미지

Link2Me

,
728x90

안드로이드 Broadcast 를 테스트 해보고 정리해둔다.
개념도 제대로 모른채 구글링해서 나온 예제를 실행해보고 실행이 되는구나 하는 정도만으로는 응용이 불가능하다는 걸 테스트 해보면서 몸소 느끼고 있다. Android Studio 기반에서 테스트한 것을 적어둔다.

안드로이드 6.0 에서 SMS 권한이 없는데 허용할 것인지 문의하는 창도 뜨고 잘 동작된다.


BroadCast.zip


Android Studio 는 main 폴더에 핵심적인 내용이 들어가 있으므로 이것만 파일 첨부하면 이걸 이용해서 다시 작성하기가 쉽다.


BroadcastReceiver는 모든 어플에게 다 전해지는 것이다. (시스템 매니저가 알아서 보내는 것)


// IntentFilter 전달받는 여러 인텐트중 특정 인텐트만 고르겠다는 의도
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbAttachReceiver , filter); // 브로드캐스트리시버 등록


또는

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbAttachReceiver, filter);  // 브로드캐스트리시버 등록


Broadcast Receiver(방송수신자)는 BroadcastReceiver 클래스를 상속받아 작성한다.

Broadcast 메시지가 수신되면 onReceive 메서드가 자동 호출된다.

public class Broadcastreceiver extends BroadcastReceiver {

    private Bundle bundle;
    private SmsMessage currentSMS;
    private String message;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Log.d("onReceive()","부팅완료");
            //Intent i = new Intent(context, ScreenService.class);
            //context.startService(i);
        }


        if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
            Log.d("SMSBroadcastReceiver", "SMS 메시지가 수신되었습니다.");
            //Toast.makeText(context, "문자가 수신되었습니다", Toast.LENGTH_SHORT).show();
            bundle = intent.getExtras();
            if (bundle != null) {
                Object[] pdu_Objects = (Object[]) bundle.get("pdus");
                if (pdu_Objects != null) {
                    for (Object aObject : pdu_Objects) {
                        currentSMS = getIncomingMessage(aObject, bundle);
                        String senderNo = currentSMS.getDisplayOriginatingAddress();
                        message = currentSMS.getDisplayMessageBody();
                        Toast.makeText(context, "senderNum: " + senderNo + " :\n message: " + message, Toast.LENGTH_LONG).show();
                    }
                    this.abortBroadcast();
                    // End of loop
                }
            }
        } // bundle null



AndroidManifest.xml 을 사용하여 Broadcast Receiver 를 등록

- 앱이 실행중이 아니더라도 지정한 Broadcast 메시지를 수신(지정한 Action 발생)되면 Broadcast Receiver 가 자동 실행된다.


동적으로 Broadcast Receiver 를 등록하면, 앱이 실행중일 때만 Broadcast Receiver 가 수행되며, 동적으로 Broadcast Receiver 를 추가/제거가 가능하다.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.link2me.sms_receive">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
            android:name ="com.tistory.link2me.sms_receive.Broadcastreceiver"
            android:enabled="true">
            <intent-filter android:priority="10000">
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

    </application>

</manifest>


AndroidManifest.xml에 해당 리시버를 등록하고 intent-filter로 원하는 브로드캐스팅 받을 Action을 등록한다.

즉, intent-filter는 어떤 intent를 필터링해서 들을것인지 설정하는 것이다.

안드로이드 앱에서 알고 싶은 이벤트들이 있을수 있다. 가령
- 배터리 부족
- 네트워크 연결 / 중단
- SD카드 삽집 / 제거
- SMS 수신
- 카메라 버튼이 눌렸을때
- 폰의 날짜, 시간이 변동되었을때
- 비행기 모드 전환시
- 어플 설치/제거
- 재부팅/종료
- 매 분마다 수신


android:enabled="true" : 시스템이 알아서 Broadcastreceiver를 실행한다는 의미다. default 가 true 다.

android:exported="false" : 외부 어플리케이션에서는 사용할 수 없으며 같은 앱 또는 같은 UserId를 가진 놈만 호출할 수 있다는 뜻이다. (이걸 적어두면 메시지 수신이 제대로 안됨)
android:priority : 숫자가 높을수록 우선순위가 높으며, 우선순위가 높은 리시버부터 순차적으로 메세지가 전달된다.


 === Broadcastreceiver.java === 

 package com.tistory.link2me.sms_receive;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;

public class Broadcastreceiver extends BroadcastReceiver {
    private Bundle bundle;
    private SmsMessage currentSMS;
    private String message;

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            Log.d("onReceive()","부팅완료");
            //Intent i = new Intent(context, ScreenService.class);
            //context.startService(i);
        }

        if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
            Log.d("onReceive()","스크린 ON");
        }

        if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
            Log.d("onReceive()","스크린 OFF");
        }

        if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
            Log.d("SMSBroadcastReceiver", "SMS 메시지가 수신되었습니다.");
            Toast.makeText(context, "문자가 수신되었습니다", Toast.LENGTH_SHORT).show();
            bundle = intent.getExtras();
            if (bundle != null) {
                Object[] pdu_Objects = (Object[]) bundle.get("pdus");
                if (pdu_Objects != null) {
                    for (Object aObject : pdu_Objects) {
                        currentSMS = getIncomingMessage(aObject, bundle);
                        String senderNo = currentSMS.getDisplayOriginatingAddress();
                        message = currentSMS.getDisplayMessageBody();
                        Toast.makeText(context, "senderNum: " + senderNo + " :\n message: " + message, Toast.LENGTH_LONG).show();
                    }
                    this.abortBroadcast();
                    // End of loop
                }
            }
        } // bundle null
    }

    private SmsMessage getIncomingMessage(Object aObject, Bundle bundle) {
        SmsMessage currentSMS;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String format = bundle.getString("format");
            currentSMS = SmsMessage.createFromPdu((byte[]) aObject, format);
        } else {
            currentSMS = SmsMessage.createFromPdu((byte[]) aObject);
        }
        return currentSMS;
    }
}


문자수신시각 정보 추가시

Date curDate = new Date(currentSMS.getTimestampMillis());
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
String receivedDate = sdf.format( curDate );



=== MainActivity.java ===

 package com.tistory.link2me.sms_receive;

import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

        int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS);
        if(permissionCheck == PackageManager.PERMISSION_GRANTED){
            Toast.makeText(this, "SMS 수신권한 있음", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "SMS 수신권한 없음", Toast.LENGTH_SHORT).show();
            if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.RECEIVE_SMS)){
                Toast.makeText(this, "SMS 권한 설정이 필요함", Toast.LENGTH_SHORT).show();
            } else {
                // 권한이 할당되지 않았으면 해당 권한을 요청
                ActivityCompat.requestPermissions(this,new String[] {Manifest.permission.RECEIVE_SMS},1);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == 1) {
            for (int i = 0; i < permissions.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, permissions[i] + " 권한이 승인됨.", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(this, permissions[i] + " 권한이 거부됨.", Toast.LENGTH_LONG).show();
                }
            }
        }
    }

}



블로그 이미지

Link2Me

,
728x90

Android Studio 상에서 프로젝트 이름(package name)을 변경하고 싶을 때가 있다.


왼쪽 프로젝트 탐색기에서 Android로 변경되어 있는지 확인



java 폴더 선택하여 그 하위에 있는 변경할패키지명을 선택한 후
Shift + F6(Refactor → rename)를 누른다






androidMainfest.xml에서 변경되어 있는지 확인


build.gradle 파일에서 applicationId를 변경할 패키지명으로 변경되었는지 확인


이렇게 하고 컴파일을 하면 패키지명이 변경되어 컴파일 된다.

하지만 물리적인 이름까지 변경되지는 않는다.


물리적인 이름까지 변경해서 테스트를 해보니 에러가 발생하고 제대로 동작이 안되는 거 같다.

그래서 딱 위 그림까지면 변경하면 생성되는 apk 파일명도 변경된다.

블로그 이미지

Link2Me

,
728x90

Android Studio 에서 메뉴 만드는 방법이다.




메뉴를 추가한다.







일일이 입력하지 않고 특정 단축어만 입력하면 자동으로 팝업되면서 선택할 수 있게 나온다.

그러면 엔터키만 치면 그 다음에 입력할 사항만 입력하면 된다.



여기서 에러메시지를 보여주면서 자동으로 넘어가지 않는다.

Android Studio 버전 2.3 에서는 자동으로 완성시키더라.

계속 버전업을 하면서 편리하게 자동완성 기능을 추가하고 있는거 같다.


==== menu_item.xml ====

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
  <item
      android:id="@+id/item01"
      android:title="메뉴1"
      android:orderInCategory="2"
      android:icon="@android:drawable/ic_lock_idle_low_battery"
      app:showAsAction="always"
      />.

    <item
        android:id="@+id/item02"
        android:title="메뉴2"
        android:orderInCategory="3"
        android:icon="@android:drawable/btn_plus"
        app:showAsAction="always"
        app:actionLayout="@layout/custom_layout"
        />

    <item
        android:id="@+id/item03"
        android:title="메뉴3"
        android:orderInCategory="1"
        android:icon="@android:drawable/sym_def_app_icon"
        app:showAsAction="always"
        />
</menu>


==== custom_layout.xml ===

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

    <EditText
        android:id="@+id/custom_et01"
        android:inputType="text"
        android:imeOptions="actionDone"
        android:layout_width="100dp"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/custom_tv01"
        android:text="찾기"
        android:textSize="15dp"
        android:textColor="#22a"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>


==== activity_main.xml ===

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tistory.link2me.Menu.MainActivity">

    <TextView
        android:id="@+id/tv01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textSize="18dp"
        android:textColor="#a84"
        android:text="Hello World!" />

    <Button
        android:id="@+id/btn01"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/tv01"
        android:layout_marginTop="20dp"
        android:text="ActionBar 아이콘 변화"
        android:onClick="btnClicked"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>


==== MainActivity.java ====

package com.tistory.link2me.Menu;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    EditText editText;
    ActionBar actionBar;

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

        actionBar = getSupportActionBar();
        actionBar.setSubtitle("서브타이틀");

        textView = (TextView) findViewById(R.id.tv01);
    }

    public void btnClicked(View v){
        actionBar.setLogo(android.R.drawable.sym_action_call);
        actionBar.setDisplayOptions(android.app.ActionBar.DISPLAY_SHOW_HOME|android.app.ActionBar.DISPLAY_USE_LOGO);
    }

    // 시스템 상에서 자동으로 호출하는 메소드 및 custom_layout 객체화
    public boolean onCreateOptionsMenu(Menu menu){
        getMenuInflater().inflate(R.menu.menu_item,menu);

        // custom_Layout 객체를 참조
        View findView = menu.findItem(R.id.item02).getActionView();
        if(findView != null){
            editText = (EditText) findView.findViewById(R.id.custom_et01);
            if(editText != null){
                editText.setOnEditorActionListener(findListener);
            }
        } else {
            Toast.makeText(getApplicationContext(), "액션뷰가 없네요", Toast.LENGTH_SHORT).show();
        }
        return true;
    }

    public boolean onOptionsItemSelected(MenuItem item){
        switch (item.getItemId()){
            case R.id.item01:
                Toast.makeText(this, "메뉴1", Toast.LENGTH_SHORT).show();
                break;

            case R.id.item02:
                Toast.makeText(this, "메뉴2", Toast.LENGTH_SHORT).show();
                break;

            case R.id.item03:
                Toast.makeText(this, "메뉴3", Toast.LENGTH_SHORT).show();
                break;
        }

        textView.setText(item.getTitle().toString());
        return super.onOptionsItemSelected(item);
    }

    private TextView.OnEditorActionListener findListener = new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if(event == null || event.getAction() == KeyEvent.ACTION_UP){
                find();

                // 키패드 처리 객체 --> InputMethodManager 객체
                InputMethodManager inputMethodManger = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
                // 키패드 닫기
                inputMethodManger.hideSoftInputFromWindow(textView.getWindowToken(),0);
                textView.setText("메뉴2");
            }
            return true;
        }
    };

    private void find(){
      String findString = editText.getEditableText().toString();
        Toast.makeText(this, "검색어 : "+findString, Toast.LENGTH_SHORT).show();
    }
 
}





블로그 이미지

Link2Me

,
728x90

Android Studio 에서 xml 파일을 추가하는 방법

오랫만에 접속하면 이런 방법마저 잊어버리게 되더라. 그래서 적어둔다.




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

<ImageButton
android:id="@+id/ib01"
android:src="@android:drawable/sym_def_app_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:id="@+id/et01"
android:hint="예상 우승 후보팀을 입력하세요"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

이 XML 파일을 View 객체로 만들기 위해서는 LayoutInflator를 이용해야 한다.

안드로이드에서 inflate 를 사용하면 xml 에 씌여져 있는 view 의 정의를 실제 view 객체로 만드는 역할을 한다.

자바 프로그램 코드상에서는 레이아웃 인플레이터에 직접 접근하지 못하고

getLayoutInflater() 메소드를 이용하거나, getSystemService(String) 메소드를 호출해 반환값으로 LayoutInflater 객체를 받아야 한다.

사용자의 화면에 보여지는 것들은 Activity 위에 있는 View다.


Layout Inflater
- 레이아웃 인플레이터는 레이아웃 xml 파일에 상응하는 뷰 객체를 반환받는 데 사용한다
- 자바 프로그램 코드상에서는 레이아웃 인플레이터에 직접 접근하지 못하고 getLayoutInflater() 메소드를 이용하거나
getSystemService(String) 메소드를 호출해 반환값으로 LayoutInflater 객체를 받아야 한다.
- 보통 자바 코드에서 View, ViewGroup 을 사용하거나, Adapter의 getview() 또는 Dialog, Popup 구현시
배경화면이 될 Layout을 만들어 놓고 View의 형태로 반환 받아 Acitivity에서 실행 하게 된다.


inflate(int resource, ViewGroup root, boolean attachToRoot)
- 레이아웃 XML파일을 View객체로 만들기 위해서는 LayoutInflater내의 inflater 메서드를 사용
- resource: view를 만들고 싶은 레이아웃 파일의 id (
inflate할 대상의 xml 리소스)

ex) R.layout.custom

- 두 번째 인수는 생성된 뷰의 루트로 사용할 뷰 객체. 리소스 내에 루트가 따로 있다면 null.


inflate 를 사용하기 위해서는 우선 inflater 를 얻어와야 한다.
LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE );
LinearLayout linearLayout = (LinearLayout) inflater.inflate( R.layout.custom, null);
setContentView( linearLayout ); // 가져온 View 를 화면에 그린다.


/* We get the inflator in the constructor */
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

--> getSystemService(Context.LAYOUT_INFLATER_SERVICE) = LayoutInflater 객체를 반환받는다.


// 인플레이트를 얻어오는 다른 방법
LinearLayout linearLayout = (LinearLayout) View.inflate(MainActivity.this, R.layout.custom, null);
// Inflate된 View에서 Child인 EditText를 얻어 오기
EditText editText = (EditText) linearLayout.findViewById(R.id.et01);




블로그 이미지

Link2Me

,