@Override

    public void onLowMemory() {

        super.onLowMemory();

        Glide.get(this).clearMemory();

    }


    @Override

    public void onTrimMemory(int level) {

        super.onTrimMemory(level);

        Glide.get(this).trimMemory(level);

    }


을 넣어주는 센스~

Image파일 vectorDrawable 대응하기

http://pluu.github.io/blog/android/2016/04/11/android-vector/


http://pngtosvg.com/ 여기서 png -> svg 파일로 변경


http://inloop.github.io/svg2android/ 여기서 svg -> drawable파일로 변경


적용해서 사용하기

Android 베타 테스트시 여러가지 방법이 있다. 

1. 구글 배타에 등록해서 테스트 한다

2. zenkis등을 이용한 CI 방식으로 서버쪽에서 git을 푸시받아 자동으로 apk 배포 한다. 

3. deploygate방식으로 스튜디오에서 쉽게 올릴수 있다.  (링크 . QR 코드 제공)

4. 직접 서버에 올리고 링크 보낸다.


이중에 3번째 방법을 소개하고자 한다. 


1. https://deploygate.com/ 에 가서 가입 진행

2. https://github.com/DeployGate/gradle-deploygate-plugin/blob/master/README.md 에서 그래들 방식 하나씩 적용.

3. http://qiita.com/henteko/items/7ffc8f15223c463683f4 마지막으로 설정해서 런해주면 된다. 

4. clean assembleDebug uploadDeployGate

주의 사항은 gradle 위치 참조시 해당 프로젝트 app/bundle.gradle 을 지정해주면 된다. 


studio 에서 설정시 최소 내용은 다음과 같이 해도 된다. 




그럼 완료된다. 


출처 : http://stackoverflow.com/questions/27963410/cant-create-backup-to-sd-card


메뉴 및 버튼을 둬서 sqliteExport 메소드가 실행되도록 하면 SDcard에 데이터베이스명.sqlite로 저장된다.

이 파일을 FireFox 의 database manager 이용해서 열어보면 쿼리 도 가능하고 csv 저장도 가능하고 기타 등등이 가능하다.

permission은 아래와 같다.

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



public void sqliteExport(){
try {
File sd = Environment.getExternalStorageDirectory();
File data = Environment.getDataDirectory();

if (sd.canWrite()) {
String currentDBPath = "//data//패키지명/databases//데이터베이스명";
String backupDBPath = "데이터베이스명.sqlite";
File currentDB = new File(data, currentDBPath);
File backupDB = new File(sd, backupDBPath);

if (currentDB.exists()) {
FileChannel src = new FileInputStream(currentDB).getChannel();
FileChannel dst = new FileOutputStream(backupDB).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
}
if(backupDB.exists()){
Toast.makeText(mContext, "DB Export Complete!!", Toast.LENGTH_SHORT).show();
}
}
} catch (Exception e) {
}
}


출처 : http://dktfrmaster.blogspot.kr/2016/09/glide.html

Glide란 무엇인가??

  • 구글에서 공개한 이미지 라이브러리
  • 기존의 Bump앱이 만들어 사용하던 라이브러리였는데 구글이 Bump앱을 인수하여 라이브러리를 공개
  • 웹 상의 이미지를 로드하여 보려주기 위해 고려해야 할 사항들을 미리 구현하여, 사용자가 이용하기 쉽게 만든 라이브러리

Glide 추가하기

Dependency 추가

  • build.gradle의 dependencies에 다음을 추가한다.
compile 'com.github.bumptech.glide:glide:3.7.0'
  • 혹시 maven을 이용한다면 다음을 추가한다.
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>glide</artifactId>
<version>3.7.0</version>
<type>aar</type>
</dependency>

기본 이미지 로딩

  • Glide 클래스는 빌더 패턴으로 구현되어 있고, 3개의 필수 파라미터를 요구한다.
    • with(Context context) : 안드로이드의 많은 API를 이용하기 위해 필요
    • load(String imageUrl) : 웹 상에서의 이미지 경로 URL or 안드로이드 리소스 ID or 로컬 파일 or URI
    • into(ImageView targetImageView) : 다운로드 받은 이미지를 보여줄 이미지 뷰
// 웹 URL
ImageView target = (ImageView) findViewById(R.id.imageview);
String url = "http://www.example.com/icon.png";

Glide.with(context)
.load(url)
.into(target);


// 리소스 ID
int resourceId = R.mipmap.ic_launcher;

Glide.with(context)
.load(resourceId)
.into(target);


// 로컬 파일
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Example.jpg");

Glide.with(context)
.load(file)
.into(target);


// URI
Uri uri = Uri.parse("android.resource://com.example.test/resource");

Glide.with(context)
.load(uri)
.into(target);
  • 만일 해당 경로에 이미지가 없다면, Glide는 error 콜백을 리턴할 것이다.

PlaceHolder 이미지

PlaceHolder

  • PlaceHolder : 원본이미지를 보여주기 전에 잠깐 보여주는 이미지
  • 네트워크 로드 등, 이미지 로드에 시간이 오래 걸릴 때 빈화면 대신 PlaceHolder 이미지를 보여준다.
  • Glide는 PlaceHolder 이미지를 리소스 영역에서 불러온다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.placeholder(R.mipmap.ic_launcher)
.into(target);

Error PlaceHolder

  • 이미지 로드에 실패했을 때 등, 예상하지 못한 상황으로 원본이미지를 로드할 수 없을 때 보여주는 이미지이다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.placeholder(R.mipmap.ic_launcher)
.erro(R.mipmap.ic_error) //Error상황에서 보여진다.
.into(target);

Animation

  • 원본 이미지가 다 로드되고 나면 PlaceHolder 이미지가 원본 이미지로 교체되는데, 이 때 애니메이션 처리를 할 수 있다.
  • 3.7.0 현재 버전을 기준으로, 이미지 교체 애니메이션은 기본 동작한다.
  • 애니메이션을 수동으로 On / Off 하려면 .crossFade() / dontAnimate 를 호출한다.
// Animation On
Glide.with(context)
.load("http://www.example.com/icon.png")
.placeholder(R.mipmap.ic_launcher)
.erro(R.mipmap.ic_error) //Error상황에서 보여진다.
.crossFade()
.into(target);


// Animation Off
Glide.with(context)
.load("http://www.example.com/icon.png")
.placeholder(R.mipmap.ic_launcher)
.erro(R.mipmap.ic_error) //Error상황에서 보여진다.
.dontAnimate()
.into(target);

이미지 리사이징

리사이징

  • Glide는 기본적으로 이미지뷰의 사이즈에 맞게 이미지가 다운로드되고 캐싱된다.
  • 명시적으로 이미지 사이즈를 변경하려면 override(x,y) 메서드를 호출한다.
  • 타켓 이미지뷰가 없을때, 미리 이미지를 특정 사이즈로 로드하는 용도로 사용된다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.override(600,200)
.into(target);

스케일링

  • Glide는 실제 이미지의 사이즈와 화면에 보이는 크기가 다를 때, 스케일링할 수 있는 옵션을 제공한다.

CenterCrop

  • 실제 이미지가 이미지뷰의 사이즈보다 클 때, 이미지뷰의 크기에 맞춰 이미지 중간부분을 잘라서 스케일링한다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.override(600,200)
.centerCrop()
.into(target);

fitCenter

  • 실제 이미지가 이미지뷰의 사이즈와 다를 때, 이미지와 이미지뷰의 중간을 맞춰서 이미지 크기를 스케일링한다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.override(600,200)
.fitCenter()
.into(target);

이미지 캐싱

캐싱 기본정책

  • Glide는 기본적으로 메모리 & 디스크에 이미지를 캐싱하여 불필요한 네트워크 연결을 줄인다.

메모리 캐시

  • 기본적으로 메모리 캐싱을 하기때문에, 메모리 캐싱을 위해 추가적으로 할 일은 없다.
  • 메모리 캐싱을 끄려면 skipMemoryCache(true)를 호출한다.
  • 처음 메모리 캐싱을 한 후에, skipMemoryCache(true)로 캐싱을 중지하더라도, 그 전에 저장된 캐시는 그대로 남아있다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.skipMemoryCache(true)
.into(target);

디스크 캐시

  • 기본적인 개념은 메모리 캐시와 같다. Glide는 기본적으로 디스크 캐싱을 수행한다.
  • 디스크 캐싱을 끄려면 diskCacheStrategy(DiskCacheStrategy.NONE) 메서드를 호출한다.
  • diskCacheStrategy 메서드는 DiskCacheStrategy enum을 인수로 받는다.
    • DiskCacheStrategy.NONE : 디스크 캐싱을 하지 않는다.
    • DiskCacheStrategy.SOURCE : 원본 이미지만 캐싱
    • DiskCacheStrategy.RESULT : 변형된 이미지만 캐싱
    • DiskCacheStrategy.ALL : 모든 이미지를 캐싱(기본)
  • 메모리 캐싱과는 별개이므로, 둘다 사용하지 않을 경우 다음과 같이 둘다 꺼주어야 한다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(target);

이미지 로드 우선순위

Priority

  • Glide 라이브러리는 동시에 이미지 로드 명령을 받았을 때, 지정한 우선순위에 따라 이미지를 로드하도록 지원한다.
  • priority()메서드에 Priority열거형 타입을 인수로 지정하여 우선순위를 변경한다.
    • Priority.LOW
    • Priority.NORMAL
    • Priority.HIGH
    • Priority.IMMEDIATE
  • Glide 라이브러리는 이 우선순위대로 이미지 로드를 수행하지만, 반드시 우선순위 순서대로 진행된다는 보장은 없다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.priority(Priority.HIGH)
.into(target);

썸네일

원본 썸네일

  • 원본 이미지를 썸네일로 사용한다.
  • thumbnail()메서드를 이용한다. 이 때, 크기의 배수값을 줌으로써 썸네일의 크기를 지정할 수 있다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.thumbnail(0.1f)
.into(target);
  • 원본 이미지를 사용하는 방법이기 때문에 원본이미지를 변경하면 썸네일에도 변경이 적용된다.

별도 썸네일

  • 위의 방법과는 다르게, 원본과 썸네일 이미지를 각각 로드하는 방법이다.
  • 이때도 thumbnail()메서드를 이용한다. 대신 썸네일을 위한 새로운 Request를 생성하여 인자로 주어야 한다.
// into() 메서드를 뺀 Glide Request를 생성한다.
DrawableRequestBuilder<String> thumbnailRequest = Glide
.with(context)
.load("http://www.example.com/thumbnail.png");

// 생성한 Request를 thumbnail() 메서드의 인자로 넣어준다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.thumbnail(thumbnailRequest)
.into(target);
  • 원본 이미지와는 별개의 리소스이므로 원본 이미지를 변경해도 썸네일은 변화가 없다.

Target

  • Glide는 기본적으로 이미지를 비동기로 로드하여 이미지뷰에 보여준다.
  • 하지만, Target을 이용하면 이미지뷰에 보여주는 동작이 아닌 Bitmap 자체를 얻어오는 등, 여러 동작을 수행할 수 있다.
  • Glide 입장에서는 일종의 콜백이라고 볼 수 있다.
  • Target을 구현하는 방법은 BaseTarget 추상클래스를 상속받거나, SimpleTarget을 이용한다.

SimpleTarget

  • SimpleTarget은 BaseTarget 클래스를 상속받은 클래스로 기본 동작이 구현되어 있고, onResourceReady메서드만 추가로 구현하면 된다.
  • 리소스 로드가 완료되면 onResourceReady 메서드가 호출되므로, 이 메서드 내부에서 수행할 동작을 구성하면 된다.
// 로드된 이미지를 받을 Target을 생성한다.
private SimpleTarget target = new SimpleTarget<Bitmap>() {
@Override
public void onRersourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
//TODO:: 리소스 로드가 끝난 후 수행할 작업
}
}

// 생성한 Target을 into() 메서드의 인자로 넣어준다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.asBitmap() // 리소스를 Btimap으로 강제하기 위해
.into(target);

Target을 구현할 때 고려사항

  • Target 으로 쓸 인스턴스가 가비지컬렉션의 대상이 되지 않도록 주의해야 한다. Target 인스턴스가 가비지컬렉션 되면 콜백을 받을 수 없다.
  • 엑티비티의 생명주기와는 무관한 Target일 경우, 항상 Application Context를 이용한다.

특정 크기를 지닌 Target

  • Glide는 .into()에 이미지뷰를 넘기면, 이미지뷰의 크기를 고려하여, 그 크기에 맞게 이미지를 로드한다.
  • 이와 유사하게, Target을 생성할 때, 로드될 이미지의 크기를 지정하면, Glide는 이미지를 로드할 때, 그 크기를 참조하여 이미지를 해당 크기에 맞게 로드한다.
// 로드된 이미지를 받을 Target을 생성한다. 생성할 때, 크기를 지정해준다,
private SimpleTarget target = new SimpleTarget<Bitmap>(250, 250) {
@Override
public void onRersourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
//TODO:: 리소스 로드가 끝난 후 수행할 작업
}
}

// 생성한 Target을 into() 메서드의 인자로 넣어준다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.asBitmap() // 리소스를 Btimap으로 강제하기 위해
.into(target);

RequestListener

  • Glide에서 Callback 리스너를 제공한다. 리스너는 다음의 2가지 콜백을 받는다.
    • onException : 이미지 로드 중, 예외가 생겼을 때
    • onResourceReady : 이미지 로드가 완료됬을 때
  • 각 콜백 메서드는 boolean 타입 반환인자를 가지고 있다. true일 경우, 각 콜백을 핸들링 했다는 의미이므로, Glide가 기본 후처리를 하지 않는다. 반면에, false일 경우, Glide가 기본 후처리를 진행한다.
private RequestListener<String, GlideDrawable> requestListener = new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
// 예외사항 처리
return false;
}

@Override
public boolean onResourceReady(GlideDrawable resouorce, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
// 이미지 로드 완료됬을 때 처리
return false;
}
}

// 생성한 Listener를 Glide 이미지 로드시에 추가해준다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.listener(requestListener) //리스너 추가
.into(target);

애니메이션

  • Glide는 이미지 전환시에 애니메이션을 지정할 수 있다.
  • 기본으로 제공하는 애니메이션으로는 crossFade 애니메이션이 있다.
  • 그 외에, animate 메서드를 이용해 커스텀 애니메이션을 지정할 수 있다.

xml로 애니메이션 주기

  • xml로 원하는 애니메이션을 정의한다.
  • 정의한 애니메이션 리소스를 animate 메서드의 인자로 넘겨준다.
// 애니메이션 리소스 준비 (anim.xml 이라 가정)
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-5%p" android:toXDelta="0" android duration="500" />
<alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="500" />
</set>
// 생성한 애니메이션 리소스를 이미지 로드시에 추가한다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.animate(R.anim.anim) // 리소스를 Btimap으로 강제하기 위해
.into(target);

커스텀 클래스를 이용한 애니메이션

  • ViewPropertyAnimation.Animator인터페이스를 구현한 커스텀 클래스를 만든다.
  • 만든 클래스의 인스턴스를 animate 메서드의 인자로 넘겨준다.
// 커스텀 애니메이션 클래스 준비
ViewPropertyAnimation.Animator animationObject = new ViewPropertyAnimation.Animator() {
@Override
public void animate(View view) {
// if it's a custom view class, cast it here
// then find subviews and do the animations
// here, we just use the entire view for the fade animation
view.setAlpha( 0f );

ObjectAnimator fadeAnim = ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
fadeAnim.setDuration( 2500 );
fadeAnim.start();
}
};

// 커스텀 클래스의 인스턴스를 이미지 로드시에 추가한다.
Glide.with(context)
.load("http://www.example.com/icon.png")
.animate(animationObject) // 리소스를 Btimap으로 강제하기 위해
.into(target);

참고 사이트


FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP


하단에 이미 스택에 있는 activity 일 경우 onCreate 를 방지하기 위해선 필요..

Retrofit 사용시 NumberformatException 발생시..


간혹 숫자로 변환시 빈값으로 들어오는 경우 뱉는 오류 중 하나가 NumberformatException 이다.


이럴경우 Adapter를 하나 등록하면 된다.


public class EmptyStringToNumberTypeAdapter extends TypeAdapter<Number> {
@Override
public void write(JsonWriter jsonWriter, Number number) throws IOException {
if (number == null) {
jsonWriter.nullValue();
return;
}
jsonWriter.value(number);
}

@Override
public Number read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return null;
}

try {
String value = jsonReader.nextString();
if ("".equals(value)) {
return 0;
}
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
}



만드는 쪽에서 


private static Gson getGson(){
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Integer.class , new EmptyStringToNumberTypeAdapter())
.registerTypeAdapter(int.class, new EmptyStringToNumberTypeAdapter())
.registerTypeAdapter(double.class, new EmptyStringToNumberTypeAdapter())
.registerTypeAdapter(Double.class, new EmptyStringToNumberTypeAdapter());

Gson gson = gsonBuilder.create();
return gson;
}


public static Retrofit initRetrofit() {

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Variable._SERVER_HOST)
.client(Common.getClient())
.addConverterFactory(GsonConverterFactory.create(getGson()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit;
}

이상입니다.. 


Do these modifications to your animation files:

enter.xml:

<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <translate
        android:duration="500"
        android:fromXDelta="100%"
        android:fromYDelta="0%"
        android:toXDelta="0%"
        android:toYDelta="0%" />
</set>

exit.xml:

<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <translate
        android:duration="500"
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toXDelta="-100%"
        android:toYDelta="0%" />
</set>

You'll have your second activity sliding in from right to the left.

For a better understadnig on how to play around with the fromXDelta and toXDelta values for the animations, here is a very basic illustration on the values: Activity transition values on X axis

This way you can easily understand why you add android:fromXDelta="0%" and android:toXDelta="-100%" for your current activity. And this is because you want it to go from 0% to the -100% position.

[EDIT]

So if you want to open ActivityB from ActivityA you do the following(let's say you have a button):

button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startActivity(new Intent(ActivityA.this, ActivityB.class));
            overridePendingTransition(R.anim.enter, R.anim.exit);
        }
    });

Now, if you want to have the "backwards" animation of the first one, when you leave Activity B, you'll need 2 new animation files and some code in the ActivityB's onBackPressed method, like this:

First the animation files: left_to_right.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <translate
        android:duration="500"
        android:fromXDelta="-100%"
        android:fromYDelta="0%"
        android:toXDelta="0%"
        android:toYDelta="0%" />
</set>

right_to_left.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false">
    <translate
        android:duration="500"
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toXDelta="100%"
        android:toYDelta="0%" />
</set>

And in ActivityB do the following:

@Override
public void onBackPressed() {
    super.onBackPressed();
    overridePendingTransition(R.anim.left_to_right, R.anim.right_to_left);
}

Also if you have up navigation enabled, you'll have to add the animation in this case as well:

You enable UP navigation like this:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getActionBar().setDisplayHomeAsUpEnabled(true);
}

And this is how you handle the animation in this case too:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    // Respond to the action bar's Up/Home button
    case android.R.id.home:
       //NavUtils.navigateUpFromSameTask(this);
       finish();
       overridePendingTransition(R.anim.left_to_right, R.anim.right_to_left);
       return true;
    }
    return super.onOptionsItemSelected(item);
}




adb shell "run-as [패키지명] ls -l"


adb shell "run-as [패키지명] ls -l databases"


adb shell "run-as [패키지명] cat databases/[디비파일] > [복사할 디렉토리와 파일명]"



출처 : http://www.programkr.com/blog/MYDNzADMwYTy.html


애니메이션 효과 프로그래밍 기초--AnimationAndroid    
  
애니메이션 스타일    
  
Android의 animation by 네 종류의 구성    
  
XML 중    
alpha    
그라디언트 투명도 애니메이션 효과    
scale    
그라디언트 사이즈 신축 애니메이션 효과    
translate    
화면 전환 애니메이션 효과 자리 이동    
rotate    
화면 회전 애니메이션 효과 이동    
  
  
JavaCode중    
AlphaAnimation    
그라디언트 투명도 애니메이션 효과    
ScaleAnimation    
그라디언트 사이즈 신축 애니메이션 효과    
TranslateAnimation    
화면 전환 애니메이션 효과 자리 이동    
RotateAnimation    
화면 회전 애니메이션 효과 이동    
  
Android애니메이션 모드    
  
Animation두 가지 주요 애니메이션 모드:     
  
한 가지가 tweened (그라디언트 애니메이션 animation)    
XML중    
JavaCode    
alpha    
AlphaAnimation    
scale    
ScaleAnimation    
  
  
한 가지가 frame by frame (화면 전환 애니메이션)    
XML중    
JavaCode    
translate    
TranslateAnimation    
rotate    
RotateAnimation    
  
  
  
어떻게 XML 파일 중 정의 애니메이션    
  
① 열기 Eclipse, 새 Android 공사    
② 지금 res 디렉터리에 새 anim 폴더    
③ 지금 anim 디렉터리에 새 한 myanim.xml (파일 이름 소문자 주의)    
④ 가입 XML 애니메이션 코드

1 <?xml version="1.0" encoding="utf-8"?>    
2  <set xmlns:android="http://schemas.android.com/apk/res/android">    
3    <alpha/>    
4    <scale/>    
5    <translate/>    
6    <rotate/>    
7  </set>    

Android 애니메이션 해석--XML

<alpha>

 1 <?xml version="1.0" encoding="utf-8"?>    
 2  <set xmlns:android="http://schemas.android.com/apk/res/android" >    
 3  <alpha    
 4  android:fromAlpha="0.1"    
 5  android:toAlpha="1.0"    
 6  android:duration="3000"    
 7    
 8  />    
 9  <!-- 투명도 제어 애니메이션 효과 알파    
10          부동 소수점 형식 값:     
11              fromAlpha 속성 애니메이션 시작 때 투명도 위해    
12              toAlpha 속성 을 애니메이션 끝날 때 투명도    
13              설명:    
14                  0.0 대해 완전히 투명    
15                  1.0 대해 완전히 불투명    
16              이상 값 찾다 0.0-1.0 사이의 float 데이터 형식 숫자    
17             
18          긴 정수 값:     
19              duration 속성 을 애니메이션 지속 시간    
20              설명:        
21                  시간 은 밀리초 단위로    
22 -->    
23  </set>


<scale> 

 1 <?xml version="1.0" encoding="utf-8"?>    
 2  <set xmlns:android="http://schemas.android.com/apk/res/android">    
 3     <scale     
 4            android:interpolator=    
 5                       "@android:anim/accelerate_decelerate_interpolator"    
 6            android:fromXScale="0.0"    
 7            android:toXScale="1.4"    
 8            android:fromYScale="0.0"    
 9            android:toYScale="1.4"    
10            android:pivotX="50%"    
11            android:pivotY="50%"    
12            android:fillAfter="false"    
13            android:startOffset="700"    
14            android:duration="700" />    
15  </set>    
16  <!-- 사이즈 신축 애니메이션 효과 scale    
17         속성: interpolator 지정하지 애니메이션 삽입 그릇    
18          내가 실험 과정에서 사용 android.res.anim 중 자원 때 발견    
19          세 가지 애니메이션 삽입 그릇:    
20              accelerate_decelerate_interpolator 가속 - 감속 애니메이션 삽입 그릇    
21              accelerate_interpolator 가속 - 애니메이션 삽입 그릇    
22              decelerate_interpolator 감속 - 애니메이션 삽입 그릇    
23          다른 은 특정한 애니메이션 효과    
24        부동 소수점 형식 값:     
25              
26              fromXScale 속성 애니메이션 시작 때 X 좌표 위의 신축 사이즈 위해       
27              toXScale 속성 을 애니메이션 끝날 때 X 좌표 위의 신축 사이즈        
28             
29              fromYScale 속성 애니메이션 시작 때 Y 좌표 위의 신축 사이즈 위해       
30              toYScale 속성 을 애니메이션 끝날 때 Y 좌표 위의 신축 사이즈      
31              startOffset 속성 을 지난번부터 애니메이션 계속 몇 시간 시작한 다음 애니메이션 실행    
32             
33              설명:    
34                   이상 네 가지 속성 값       
35         
36                      0.0 표시 수축 없다    
37                      1.0 것은 정상적인 신축 없다.        
38                      값 < 1.0 표시 수축     
39                      가치가 크다 1.0 표시 확대    
40             
41              pivotX 속성 을 애니메이션 건가요, 물건 X 좌표 시작 위치    
42              pivotY 속성 을 애니메이션 건가요, 물건의 Y 좌표 시작 위치    
43             
44              설명:    
45                      두 개 이상의 속성 값 이 0%-100% 중 순위    
46                      50% 물건 X 또는 Y축 좌표 에서 중점 위치 위해    
47             
48          긴 정수 값:     
49              duration 속성 을 애니메이션 지속 시간    
50              설명: 시간을 밀리초 단위로 위해    
51    
52          불 형 값:    
53              fillAfter 속성 이 설정을 위해 true 이 애니메이션 전환 애니메이션 끝나면 여기서 다른 응용    
54 -->

<translate>

 1 <?xml version="1.0" encoding="utf-8"?>    
 2  <set xmlns:android="http://schemas.android.com/apk/res/android">    
 3  <translate    
 4  android:fromXDelta="30"    
 5  android:toXDelta="-80"    
 6  android:fromYDelta="30"    
 7  android:toYDelta="300"    
 8  android:duration="2000"    
 9  />    
10  <!-- translate 위치 전송 애니메이션 효과    
11          정수 값:    
12              fromXDelta 속성 애니메이션 시작 때 X 좌표 위의 위치 위해       
13              toXDelta 속성 을 애니메이션 끝날 때 X 좌표 위의 위치    
14              fromYDelta 속성 애니메이션 시작 때 Y 좌표 위의 위치 위해    
15              toYDelta 속성 을 애니메이션 끝날 때 Y 좌표 위의 위치    
16              주의:    
17                       지정되지 fromXType toXType fromYType toYType 때,     
18                       기본 은 자신을 위해 상대 참조 물건                
19          긴 정수 값:     
20              duration 속성 을 애니메이션 지속 시간    
21              설명: 시간을 밀리초 단위로 위해    
22 -->    
23  </set>

<rotate>

 1 <?xml version="1.0" encoding="utf-8"?>    
 2  <set xmlns:android="http://schemas.android.com/apk/res/android">    
 3  <rotate    
 4          android:interpolator="@android:anim/accelerate_decelerate_interpolator"   
 5          android:fromDegrees="0"    
 6          android:toDegrees="+350"            
 7          android:pivotX="50%"    
 8          android:pivotY="50%"        
 9          android:duration="3000" />     
10  <!-- rotate 회전 애니메이션 효과    
11         속성: interpolator 지정하지 애니메이션 삽입 그릇    
12               내가 실험 과정에서 사용 android.res.anim 중 자원 때 발견    
13               세 가지 애니메이션 삽입 그릇:    
14                  accelerate_decelerate_interpolator 가속 - 감속 애니메이션 삽입 그릇    
15                  accelerate_interpolator 가속 - 애니메이션 삽입 그릇    
16                  decelerate_interpolator 감속 - 애니메이션 삽입 그릇    
17               다른 은 특정한 애니메이션 효과    
18                                
19         부동 소수점 숫자 형식 값:    
20              fromDegrees 속성 애니메이션 시작 때 개체 각도에서 위해       
21              toDegrees 속성 을 애니메이션 끝날 때 개체 회전 각도를 크다 360 도 할 수 있다      
22             
23              설명:    
24                       이 각도에서 위해 음수 — — 기 반시계 방향으로 회전    
25                       이 각도에서 위해 플러스 — — 표시 시계 방향으로 회전                 
26                       (부 from— — to 플러스: 시계 방향으로 회전)      
27                       (부 from— — to 음수: 반시계방향으로)    
28                       (양수 from— — to 플러스: 시계 방향으로 회전)    
29                       (양수 from— — to 음수: 반시계방향으로)          
30    
31              pivotX 속성 을 애니메이션 건가요, 물건 X 좌표 시작 위치    
32              pivotY 속성 을 애니메이션 건가요, 물건의 Y 좌표 시작 위치    
33                     
34              설명: 이상 두 속성 값 이 0%-100% 중 순위    
35                           50% 물건 X 또는 Y축 좌표 에서 중점 위치 위해    
36    
37          긴 정수 값:     
38              duration 속성 을 애니메이션 지속 시간    
39              설명: 시간을 밀리초 단위로 위해    
40 -->    
41  </set>


XML 중 애니메이션 효과 어떻게 사용

1 public static Animation loadAnimation (Context context, int id)    
2  //첫 번째 인자 Context 프로그램 컨텍스트 위해       
3  //두 번째 인자 id 위해 애니메이션 XML 파일 참조    
4  //예:     
5 myAnimation= AnimationUtils.loadAnimation(this,R.anim.my_action);    
6  //사용 AnimationUtils 같은 정적 방법 loadAnimation () 와 XML 불러오기 중 애니메이션 XML 파일

어떻게 중에 자바 코드 정의 애니메이션

 1 //코드 정의 애니메이션 대상 은 중 인스턴스    
 2 private Animation myAnimation_Alpha;    
 3  private Animation myAnimation_Scale;    
 4  private Animation myAnimation_Translate;    
 5  private Animation myAnimation_Rotate;    
 6         
 7      //각자의 구조 방법에 근거해서 하나의 인스턴스를 대상 초기화할 수 없습니다.    
 8 myAnimation_Alpha=new AlphaAnimation(0.1f, 1.0f);    
 9    
10  myAnimation_Scale =new ScaleAnimation(0.0f, 1.4f, 0.0f, 1.4f,    
11               Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);   
12    
13  myAnimation_Translate=new TranslateAnimation(30.0f, -80.0f, 30.0f, 300.0f);    
14    
15  myAnimation_Rotate=new RotateAnimation(0.0f, +350.0f,    
16                 Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f); 

Android 애니메이션 해석--JavaCode

AlphaAnimation    
  
① AlphaAnimation 클래스 대상 정의   

1 private AlphaAnimation myAnimation_Alpha;


② AlphaAnimation 클래스 대상 구조   

1 AlphaAnimation(float fromAlpha, float toAlpha)    
2  //첫 번째 매개 변수 fromAlpha 위해 애니메이션 시작 때 투명도    
3  //두 번째 매개 변수 toAlpha 위해 애니메이션 끝날 때 투명도    
4 myAnimation_Alpha=new AlphaAnimation(0.1f, 1.0f);    
5  //설명:    
6  //                0.0 대해 완전히 투명    
7  //                1.0 대해 완전히 불투명

③ 설정 애니메이션 지속 시간

1 myAnimation_Alpha.setDuration(5000);    
2  //설정 시간 지속 시간 을 5천 초 


ScaleAnimation

① ScaleAnimation 클래스 대상 정의

1 private AlphaAnimation myAnimation_Alpha;

② ScaleAnimation 클래스 대상 구조

 1 ScaleAnimation(float fromX, float toX, float fromY, float toY,    
 2             int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)   
 3  //첫 번째 매개 변수 fromX 애니메이션 시작 때 X 좌표 위의 신축 사이즈 위해       
 4  //두 번째 매개 변수 toX 위해 애니메이션 끝날 때 X 좌표 위의 신축 사이즈        
 5  //세 번째 매개 변수 fromY 애니메이션 시작 때 Y 좌표 위의 신축 사이즈 위해       
 6  //네 번째 매개 변수 toY 위해 애니메이션 끝날 때 Y 좌표 위의 신축 사이즈     
 7      
 8  //다섯 개의 인자를 pivotXType 위해 애니메이션 X 축 건가요, 물건 위치 형식     
 9  //여섯 개의 인자를 pivotXValue 위해 애니메이션 건가요, 물건 X 좌표 시작 위치    
10  //일곱 개의 인자를 pivotXType 위해 애니메이션 지금 Y축 건가요, 물건 위치 형식      
11  //여덟 개의 인자를 pivotYValue 위해 애니메이션 건가요, 물건의 Y 좌표 시작 위치    
12 myAnimation_Scale =new ScaleAnimation(0.0f, 1.4f, 0.0f, 1.4f,    
13               Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);   

③ 설정 애니메이션 지속 시간

1 myAnimation_Scale.setDuration(700);    
2  //설정 시간 시간 (밀리초 위해 700


TranslateAnimation

① TranslateAnimation 클래스 대상 정의

1 private AlphaAnimation myAnimation_Alpha;

② TranslateAnimation 클래스 대상 구조

1 TranslateAnimation(float fromXDelta, float toXDelta,    
2                         float fromYDelta, float toYDelta)    
3  //첫 번째 매개 변수 fromXDelta 애니메이션 시작 때 X 좌표 위로 이동 위치 위해       
4  //두 번째 매개 변수 toXDelta 위해 애니메이션 끝날 때 X 좌표 위로 이동 위치         
5  //세 번째 매개 변수 fromYDelta 애니메이션 시작 때 Y 좌표 위로 이동 위치 위해        
6  //네 번째 매개 변수 toYDelta 위해 애니메이션 끝날 때 Y 좌표 위로 이동 위치

③ 설정 애니메이션 지속 시간

1 myAnimation_Translate.setDuration(2000);    
2  //설정 시간 지속 시간 은 2000년 초


RotateAnimation

① RotateAnimation 클래스 대상 정의

1 private AlphaAnimation myAnimation_Alpha;

② RotateAnimation 클래스 대상 구조

 1 RotateAnimation(float fromDegrees, float toDegrees,    
 2              int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)   
 3  //첫 번째 매개 변수 fromDegrees 애니메이션 시작 때 회전 각도 위해       
 4  //두 번째 매개 변수 toDegrees 위해 애니메이션 회전 각도 까지      
 5  //세 번째 매개 변수 pivotXType 위해 애니메이션 X 축 건가요, 물건 위치 형식     
 6  //네 번째 매개 변수 pivotXValue 위해 애니메이션 건가요, 물건 X 좌표 시작 위치    
 7  //다섯 개의 인자를 pivotXType 위해 애니메이션 지금 Y축 건가요, 물건 위치 형식      
 8  //여섯 개의 인자를 pivotYValue 위해 애니메이션 건가요, 물건의 Y 좌표 시작 위치    
 9 myAnimation_Rotate=new RotateAnimation(0.0f, +350.0f,    
10                 Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f);   

③ 설정 애니메이션 지속 시간

1 myAnimation_Rotate.setDuration(3000);    
2  //설정 시간 지속 시간 3천 초


자바 코드 중 애니메이션 효과 어떻게 사용    
  
에서 View 부 클래스 상속 온 사용 방법 startAnimation () 와 위해 View 또는 하위 클래스 View, 한 애니메이션 효과 추가   
  
  애니메이션 효과 순서

 1 android:animationOrder="random"  //임의
 2 
 3 
 4 android:animationOrder="reverse"//부정
 5 
 6 
 7 
 8 
 9 gridLayoutAnimation
10 
11 
12 android:directionPriority="row"
13 
14 android:directionPriority="column"
15 
16 android:direction="right_to_left|bottom_to_top"


'Android > General' 카테고리의 다른 글

화면 이동 애니메이션 기본  (0) 2017.02.17
안드로이드에서 루팅없이 DB 파일 추출하기  (0) 2016.12.15
이니 페이 코드 표  (0) 2015.03.04
Google Analytics for Android 활용  (1) 2015.03.02
Android TAB Host  (0) 2015.03.02
[펌]이니 페이 코드 표
거래요청 페이지 작성시 참고필드 

결제 항목 작성시 아래의 입력 항목에 적합한 값을 이니시스 결제창으로 전송 
필드명설명사이즈
acceptmethod[hidden] 플러그인이 참조하는 기타 설정

플러그인 스킨 칼라 설정 변경 (6가지 색으로 변경 가능)
디폴트 : ORIGINAL , 
녹색 : GREEN , 보라색 : PURPLE , 빨강 : RED , 노랑 : YELLOE 
<input type="hidden" name="acceptmethod" value="...:SKIN(RED):..." /> 

무통장입금 서비스 입금 예정일 설정
무통장입금 결제수단에서 입금예정일을 고객이 아닌 상점에서 설정
날짜지정 2009-04-30 까지 
<input type="hidden" name="acceptmethod" value="...:Vbank(20090430)" /> 
시간까지 2009-04-30 오후 1시 30분까지 
<input type="hidden" name="acceptmethod" value="...:Vbank(200904301330)" />

신용카드 포인트 결제
신용카드 포인트 결제에 대해 이니시스와 계약을 맺은 상점에서만 적용.
이 옵션이 적용되지 않은 기존 상점의 경우에는 포인트 사용 여부에 대한 체크박스가 체크된 상태에서 고정되어 플러그인이 표시
이 옵션을 아래와 같이 적용하면 체크박스가 활성화 됨
<input type="hidden" name="acceptmethod" value="...:CardPoint:..." /> 

현금영수증 발급차단
상점에서 현금영수증 발급을 하지 않도록 설정을 희망할 경우
<input type="hidden" name="acceptmethod" value="...:no_receipt:..." />
 
admin키패스워드 , 키파일을 발급받을때 통보받은 키패스워드 
buyername구매자 이름30byte
buyeremail구매자 이메일주소 . 결제결과를 이메일로 전송하기 위해 필요
이메일주소가 없는경우 결제시스템에서 값이 NULL인지 체크하여, 결제실패되므로 NULL 이 되지 않도록 주의
60byte
buyertel구매자 핸드폰번호 , 결제결과를 SMS로 전송하기 위해 필요
휴대폰번호가 없는 경우 결제시스템에서 값이 NULL인지 체크하여, 결제실패되므로, NULL 이 되지 않도록 주의
20byte
checkoptBase64 설정 여부 , "false" 고정정보 
currency[hidden] 금액 단위 기본 설정 . 원화 "WON" , 미화 "cent"
미화결제시 $100.95를 결제하는 경우 price value에 "."을 제외한 숫자 10095를 입력해야 하며, $310를 결제하는 경우 price value에 31000으로 Cent 자리까지 입력해야 한다.
 
debug로그 기록 모드 , 기본설정 "true" 
enctype암호화 타입 서정 , "asym" 고정정보 
goodname상품명80byte
gopaymethod결제수단 강제 지정 , value 가 "" 이면 결제방법을 사용자가 선택 
ini_logoimage_url[hidden] 플러그인 좌측 상단 상점 로고 이미지 추가 필드

1. 신용카드 : card
2. 은행계좌이체 : bank
3. 가상계좌 : vbank
4. 핸드폰 : hpp
5. 기타 
신용카드, 휴대폰, 문화상품권만 계약되어 있는 경우 
<input type="hidden" name="gopaymethod" value="Card:HPP:Culture" /> 

플러그인에 해당 결제수단만 보이는 옵션
1. 신용카드+ISP : onlycard
2. 은행계좌이체 : onlybank
3. 가상계좌 : onlyvbank
4. 핸드폰 : onlyhpp
5. 전화결제 : onlyphone
5.1 폰빌전용 : pnlyphoebill
5.2 ARS1588전용 : onlyars1588bill
6. 기타 
<input type="hidden" name="gopaymethod" value="onlycard">
 
ini_menuarea_url[hidden] 플러그인 좌측 결제메뉴 위치에 이미지 추가 필드 
ini_encfield[hidden] 앞 페이지에서 설명한 상품가격 , 위변조 체크 정보 암호화된 값 
ini_certid[hidden] 앞 페이지에서 설명한 상품가격 , 위변조 체크 정보 
ini_logoimage_url[hidden] 플러그인 좌측 상단 상덤 로고 이미지 사용설정
이미지의 크기 : 90 x 34 pixels 
<input type="hidden" name="ini_logoimage_url" value="http://test.com/1.gif" alt="" />
 
inipayhomeINIpay 설치 디렉토리 절대경로 설정 , log, key 디렉토리 위치정보 설정 
ini_menuarea_url[hidden] 플러그인 좌측 결제메뉴 위치에 이미지 사용 설정 
이미지의 크기 : 78 x 78 pixels
<input type="hidden" name="ini_menuarea_url" value="http://test.com/2.gif" alt="" />
 
mid상점아아디
최조 설치시 테스트용 아이디인 "INIpayTest"로 설정, 실제 서비스 전환시 이니시스로부터 발급받은 아이디로 교체
 
nointerest무이자 설정 여부 ( no / yes )

무이자할부 판매를 시행하려면 이니시스와의 별도 계약후 가능
 
oid[hidden] 상품주문번호 (각 결제거래를 구분할 수 있는 상점 주문번호로서 상점측에서 각 결제거래를 구분할 수 있는 값을 만든다) 
주의 : 절대 한글값을 입력하면 안된다.

은행계좌이체 상품주문번호 설정
상품주문번호 또한 아래와 같이 필드에 추가
<input type="hidden" name="oid" value="uniq_order_number" />
40byte
parentemail보호자 이메일주소 (소액결제시에 14세미만 미성년이 휴대폰 결제와 전화결제 사용시 보호자에게 결제 내용을 같이 통 - 정통부 지시사항)60byte
price결제 금액 정보 , 최종 결제 금액으로 설정 
quotabase할부기간 지정

할부 허용안함 
<input type="hidden" name="quotabase" value="일시불" /> 

6개월까지 할부 허용
<input type="hidden" name="quotabase" value="선택:일시불:2개월:3개월:4개월:5개월:6개월" />
 
type지불요청 타입 설정 , "chkfake" 고정정보 



결제결과 정보

아래항목은 결제완료후 리턴되는 항목입니다. 디비에 아래 항목이 업데이트 되도록 작업시 참고 
필드명설명사이즈
공통필드, 모든 지불수단에 공통
TID각 거래를 나타내는 40자리 고유번호40
ResultCode결제코드, 지불성고시 "00" , 지불실패시 "01" 반환2
ResultMsg결과내용 , 결과코드에 대한 설명200
MOID상점주문번호 . 결제 요청시 "oid" 필드에 설정된 값64
ApplDate결제승인 날짜 . 값 형식 : YYYYMMDD4
AppiTime결제승인 시각 . 값 형식 : HH24MMDD8
ApplNum결제승인 번호. OCB Point/VBank를 제외한 지불수단에 모두 존재20
PayMetod결제방법 . (gopaymethod 와 값이 틀릴 수 있음)10
TotPrice결제결과 금액20
지불수단 : 카드결제 ( Card, VCard 공통)
CARD_Num신용카드번호16
CARD_Interest카드 할부여부, ( "1" 이면 무이자할부 )1
CARD_Quota카드 할부기간2
CARD_Code카드사 코드2
CARD_BankCode카드발급사(은행) 코드2
OrgCurrency달러결제 정보, 통화코드3
ExchangeRate달러결제 정보, 환율12
OCB_NumOK CASHBAG 적립 및 사용내역, 카드번호20
OCB_SaveApplNumOK CASHBAG 적립 및 사용내역, 적립 승인번호12
OCB_PayApplNumOK CASHBAG 적립 및 사용내역 , 사용 승인번호12
OCB_ApplDateOK CASHBAG 적립 및 사용내역 , 승인일시8
OCB_PayPriceOK CASHBAG 적립 및 사용내역 , 포인트 지불금액12
지불수단 : 실시간계좌이체 (DIrectBank)
ACCT_BankCode은행코드2
CSHR_ResultCode현금영수증, 발급결과코드10
CSHR_Type현금영수증, 발급구분코드1
지불수단 : 무통장입금 or 가상계좌(VBank)
VACT_Num입금계좌번호20
VACT_BankCode입금은행코드2
VACT_Name예금주명20
VACT_InputName송금자명20
VACT_Date송금 일자8
VACT_Time송금 시각6
지불수단 : 핸드폰(HPP)
HPP_Num휴대폰 번호14
지불수단 : 거는 전화결제 (Ars1588Bill)
ARSB_Num전화번호12
지불수단 : 받는 전화결제 (PhoneBill)
PHNB_Num 12
  • 페이스북으로 보내기
  • 트위터로 보내기
  • 구글플러스로 보내기


'Android > General' 카테고리의 다른 글

안드로이드에서 루팅없이 DB 파일 추출하기  (0) 2016.12.15
android anim 애니메이션 효과  (0) 2016.01.16
Google Analytics for Android 활용  (1) 2015.03.02
Android TAB Host  (0) 2015.03.02
Preferences 완벽 설명  (0) 2015.02.07

출처 : http://googledevkr.blogspot.kr/2014/06/google-analytics-for-android.html

Google Analytics for Android를 이용한 근거기반 서비스 개발


안녕하세요. GDG SSU 전 운영자 김종찬(flashilver@gmail.com)입니다. 현재 Software Maestro에서 2단계 연수과정을 진행 중이고, 노인분들을 위한 안드로이드 런처를 개발 중입니다.


작년 IT 업계에서 인기있던 키워드 중 하나는 “Lean Start-Up”입니다. 이 글에서는 완벽한 Lean Start-up은 아니지만, 개발자의 입장에서 Google Analytics를 활용하여 그 맛을 조금 보고 흥미를 느낄 수 있을만한 Tutorial을 제공하려 합니다.


준비하실 내용은 다음과 같습니다.


0. 런칭을 목표로 하고있는 독자님의 서비스 혹은 이 글에서 다루고자 하는 예제(Github Repo)
2. Android 개발환경 - Android Studio OR Eclipse+Android SDK
**이 글에서는 Android Studio를 사용합니다.


시작해보시죠!




0. Google Analytics Console 세팅하기



Google Analytics는 런칭된 Application의 상황 통계를 웹페이지 콘솔로써 보여줍니다. 그러한 데이터들을 받고 살펴보기 위해 첫번째 관문으로 Project에 대응되는 Analytics를 세팅하여야 합니다.


> Action 0-1 : http://www.google.com/analytics/ 에 가셔서 Google 계정으로 로그인을 해주시고, 계정이 없다면 가입 절차를 밟아주세요.


스크린샷 2014-05-26 오후 12.47.19.png

Action 0-1까지 수행하셨으면 화면 상단에 위와 같은 탭이 나타납니다.


>Action 0-2 : “관리" 버튼을 눌러주세요.


스크린샷 2014-05-26 오후 12.48.29.png
Action 0-2를 수행하시면 위와 같은 Spinner가 나오는데 저는 이미 Anaytics를 사용하고 있어서 여러가지가 나타나네요.


> Action 0-3 : “새 계정 만들기"를 눌러주세요.


스크린샷 2014-05-26 오후 12.56.28.png

> Action 0-4 :
우리는 이번에 웹사이트에 Anaytics를 거는 것이 아닌, 모바일 앱에 거는 예제를 다룰 것이니 모바일 앱을 클릭하시고 계정 이름, 앱 이름, 업종 카테고리, 보고서 시간대를 세팅해주세요.


계정 이름은 본인이 주로 사용하시는 ID나 프로젝트 명을 적어주시면 되며, 앱 이름과 업종 카테고리는 상황에 맞게 잘 적어주시면 됩니다. 보고서 시간대는 대한민국을 선택하시면 됩니다.


약관이 나오면 빠르게 동의를 눌러주세요.


스크린샷 2014-05-26 오후 12.58.16.png

위와 같은 화면이 나왔으며 “추적 ID”라는 항목이 등장한 것을 살펴 보실 수 있습니다. 이것은 차후에 안드로이드 애플리케이션과 Anlaytics를 연결하는 고리이니 다시 살펴보실 준비를 하셔야 합니다.




1. Android Project에 Google Analytics 세팅하기



다음으로는 Android Project에 Google Anaytics SDK를 추가할 차례입니다.


> Action 1-1 : 이 링크를 참조하여 SDK를 세팅하시면 되겠습니다. 위에서 알려드린 링크의 경우에는 Android Studio에서 세팅하는 법, Eclipse에서 세팅하는 법 둘 다 서술하고 있습니다. SDK 설정이 끝나면 이제는 직접 Android Project 에다가 SDK를 붙여보는 작업을 해보도록 하겠습니다.


> Action 1-2 : 이 링크를 참조하여 AndroidManifest.xml에 두 개의 퍼미션을 등록해주세요. <manifest 아래에 아래의 스크린 샷과 같이 등록하면 됩니다.

스크린샷 2014-05-26 오후 3.31.06.png


Google Analytics에는 Tracker라는 개념이 있습니다. 이 Tracker는 Application을 상속한 클래스에서 커스텀 클래스에서 정의되며, Tracker를 사용 할 때 해당 커스텀 Application 클래스를 통하여 사용하게 됩니다.


> Action 1-3 : 예제의 GuGuAnalyticsV4Example.java와 같이 각자의 커스텀 Application 클래스를 세팅하려합니다.


일단은 이 글에서 서술하는 내용을 따라하는 정도를 위해 클래스명은 프로젝트에 맞게 설정해주시고, PROPERTY_IDGoogle Analytics Console 세팅하기 챕터의 “Action 0-4”에서 확인한 키를 넣어주시고 나머지 부분은 예제의 소스코드와 다르지 않게 세팅해주세요. 그리고 AndroidManifest.xml에서 <application 안에 android:name을 생성하신 클래스명에 맞게 수정해주시면 됩니다.

스크린샷 2014-05-26 오후 4.15.32.png
<AndroidManifest.xml에서 android:name을 설정한 모습>


위의 처리를 해주지 않으면 차후에 getTracker()를 사용하실 때 ClassCastException에러가 유발 됩니다.


스크린샷 2014-05-26 오후 4.03.59.png
<Custom Application 소스의 모습>


위의 소스에서 R.xml 리소스의 global_tracker와 ecommerce_tracker가 요구되는 것이 보이시나요? 이제부터는 위에서 요구하는 두 리소스 파일을 만들어 주어야 합니다.


> Action 1-4 : xml이라는 리소스 폴더를 res폴더의 아래에 생성해주세요. xml 폴더 안에는 ecommerce_tracker.xmlglobal_tracker.xml 두 개를 생성 시켜주시면 됩니다.
두 개의 코드는 다음과 같습니다.

스크린샷 2014-05-26 오후 4.12.07.png
<global_tracker.xml>


스크린샷 2014-05-26 오후 4.12.50.png
<ecommerce_tracker.xml>


> Action 1-5 : global_tracker.xml 에서 screenName 태그에서 프로젝트에서 Analytics를 수행 하고자 하는 Activity 혹은 Fragment를 설정해주시면 됩니다. 이 예제에서는 Activity를 설정하고 있습니다.


Google Analytics를 기본적으로 설정하는 이 챕터는 거의 마무리 되어가고 있습니다.


이번엔 위에서 ScreenName을 통하여 지정한 Activity에서 Google Analytics로 Tracker를 보내도록 하겠습니다.


> Action 1-6 : 예제에 있는 MainActivity1 클래스와 같이 Tracking 하고자하는 액티비티 소스를 열고 onStart(), onStop()을 오버라이딩 해주세요.


그리고 onCreate(), onStart(), onStop()에 아래의 스크린샷과 같이 소스를 추가해주세요.


스크린샷 2014-05-26 오후 4.25.39.png


위 소스를 추가하기전에


import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;


위와 같이 import를 처리 해주셔야 합니다. <


위 소스에 대해서 간단히 설명하자면


GoogleAnalytics.getInstance(this).reportActivityStart(this); 는 Activity의 사용자가 어떤 액션을 진행하는지 추적을 시작하는 코드이고


GoogleAnalytics.getInstance(this).reportActivityStop(this); 는 Activity의 추적을 끝내는 코드입니다.


그리고 Tracker t 인스턴스를 초기화 시켜주고나서 행하는 setScreenName()안에 들어갈 인자를 String값으로 지정하고, send()를 이용하여 전송하면 Anaytics Console에서 유저가 액티비티를 살펴보는 것을 추적 가능합니다.


위와 같은 방법을 반복수행하여 모든 Activity에 적용해주면 됩니다.이렇게 하면 얼마나 많은 유저가 어떤 화면을 보고 있는지, 어떤 경로로 앱을 사용하는지 아래의 화면처럼 결과를 나타낼 준비가 된 것 입니다.

스크린샷 2014-06-03 오전 4.40.18.png


스크린샷 2014-06-03 오전 4.39.18.png



2. Event Tracking 설정하기



Event Tracking은 개발자가 어떤 특정한 상황을 Log로 남겨 유저가 어떤 액션을 일으켰는지, 그 액션이 해당 서비스에서 총 몇 번 수행 되었는지를 알게해주는 매우 바람직한 기능입니다.


이것을 이용하여 애플리케이션에서 유저들이 가장 많이 손이 가는 기능이 어디인지, 어디가 인기가 없는지를 감지해내는 것이 가능합니다. 이런 매력적인 기능을 0.Android Project에 Analytics 세팅하기 를 마친 상태라면 어렵지 않게 세팅 가능합니다.


아래의 스크린샷을 보고 따라해 보세요.


스크린샷 2014-05-26 오후 4.50.23.png

> Action 2-1 :
Tracker 인스턴스를 초기화하고,
t.send() 메소드 이후로 Category, Action, Label을 설정하고 build() 메소드를 이용하여 Analytics에 전달합니다.


저는 Category에 이벤트가 일어난 액티비티를 넣었고, Action에는 어떤 종류의 이벤트가 일어났는지, Label에는 어떤 버튼이 눌렸는지를 String을 이용하여 표현 하였습니다.
이 부분은 각자의 서비스에 따라서 어떻게 인자를 넣을지가 많이 다를 수 있습니다.


이게 끝입니다. 간단하지요? 결과는 아래와 같이 Google Analytics 콘솔에서 보여집니다.


스크린샷 2014-06-03 오전 4.38.44.png




3. Campaign Measurement 설정하기



Campaign Measurement란 외부에서 애플리케이션을 설치하게되는 경로를 알아내주게 하는 기능입니다. 이 Campaign Measurement를 잘 사용하면 좋은 예시로는 다음과 같습니다.


0. 우리의 애플리케이션을 유저들이 받을 때 어떤 경로로 받는지를 알고 싶을 때 :


가장 기본적인 아이디어입니다. 유저들이 검색엔진의 검색을 통해서 접근하는지, 카페나 블로그의 홍보들을 통하여 애플리케이션을 설치하는지, 가장 일반적으로 Play Store를 통해서 앱을 검색하여 설치하게되는지를 알 수 있습니다.


1. 외부에서 애플리케이션을 홍보 할 때 :


애플리케이션의 특징을 다양하게 하여 홍보를 했을 경우를 예로 들어보겠습니다. A라는 애플리케이션의 특징을 아름다운 앱, 사용성이 좋은 앱, 독특하고 인기가 좋은 앱 등 다양한 특징을 어필하며 애플리케이션을 홍보하게 될 경우가 있는데. 우리는 여기에 Campaign Measurement를 이용하여 유저들이 우리 앱을 받을 때 어떤 단어나 문구에 매력을 느끼고 접근하는지를 가늠하는 것이 가능합니다. 위의 상황말고도 Campaign Measurement를 어떻게 사용하느냐에 따라서 제품을 발전시켜나갈 때 정말 큰 인사이트를 줄 수있는 사용방법이 많으리라 생각합니다.


> Action 3-1 : AndroidManifest.xml에 가셔서 아래와같이 <service><receiver>를 등록해주세요. <intent-filter> 역시 빠뜨리시면 안됩니다!

스크린샷 2014-06-03 오전 4.52.09.png


위의 세팅까지 끝내면 Campaign Measurement를 사용하는데에 애플리케이션에서 해줘야하는 일은 끝난 상태입니다.


이제는 Referrer 인자를 지정하여 Google Play Store를 통해 애플리케이션에 접근하게 만드는 URL을 만드는 일만 남았습니다.


> Action 3-2 :
스크린샷 2014-06-03 오전 4.55.02.png


위의 링크를 타고 들어가셔서 Google Play URL Builder를 만들어주셔야 합니다. 각 요소에 맞게 빈칸을 채워주시고 Generate URL을 만들어주면 해당 Referrer 인자가 담긴 Google Play Store를 통해 앱으로 접근가능 하게하는 특수한 URL이 주어지게 됩니다. 해당 URL을 주소창에 넣고 페이지를 이동해보시면 아마 생각하시는 애플리케이션의 Play Store로 접근이 가능하게 됩니다. 그 Play Store에서 유저들이 애플리케이션을 받으면 Google Analytics가 어떤 주소로 유저가 앱을 받게되었는지를 인식하고 그 통계를 Analytics Console에 전달합니다.


제가 운영 중인 앱의 Console에서는 다음과 같이 데이터가 들어옵니다.

스크린샷 2014-06-03 오전 5.00.28.png


이곳에서 두 번째 측정기준의 선택요소에 따라서 얼마나 많은 사람들이 어떤 Referrer가 담긴 URL을 통해 앱을 받게 되었는지를 알 수 있습니다.




아직 많이 부족한 대학생이 프로젝트 개발경험을 하면서 얻은 작은 지식을 공유하였습니다. 이 글을 쓰니 좀 더 Google Analytics를 제대로 사용해보고 더 많은 것을 배워야 겠다는 생각이 드네요. 유저들에 대한 아무런 조사 없이 앱을 개발하고 마켓에 내놓는 것보다, 유저들이 나의 앱의 어떤 부분을 유용하게 쓰는지, 어떤 기능을 기대하는지 미리 아는 것은 더 많은 기회를 가져옵니다.



Google Analytics를 통해서 바람직한 고객중심 개발을 진행하는 개발자가 많아졌으면 좋겠습니다. 감사합니다.


'Android > General' 카테고리의 다른 글

android anim 애니메이션 효과  (0) 2016.01.16
이니 페이 코드 표  (0) 2015.03.04
Android TAB Host  (0) 2015.03.02
Preferences 완벽 설명  (0) 2015.02.07
꺼진화면에서 알림창 띄우기  (0) 2015.02.04

Screenshot-FragmentTabHost

I wrote a post about how to create and style the TabHost component in Android. Not surprising they have recently depreciated this widget in favor FragmentTabHost so I figured I’d run through the same examples as I did with TabHost just to document it. Most of this is taken from the SDK examples.

 

UPDATE: Since FragmentTabHost does not offer an icon option, I have set the icon in setIndicator(CharSequence label, Drawable icon) to ‘null’ in MainActivity.class.

ANDROID STUDIO: Android Studio does not provide dependencies so you will have to import them manually. File > Project Structure > app (under “Modules) > Dependenciestab. Click the “+” at the bottom to add a Library Dependency; in this case “support-v4“.

layout/activity_main.xml

<android.support.v4.app.FragmentTabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"/>

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
    </LinearLayout>

</android.support.v4.app.FragmentTabHost>

layout/fragment_layout.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:background="#eaecee">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical|center_horizontal"
        android:text="@string/hello_world"
        android:textAppearance="?android:attr/textAppearanceMedium" />

</LinearLayout>

MainActivity.java

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;

public class MainActivity extends FragmentActivity {
    private FragmentTabHost mTabHost;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);

        mTabHost.addTab(
                mTabHost.newTabSpec("tab1").setIndicator("Tab 1", null),
                FragmentTab.class, null);
        mTabHost.addTab(
                mTabHost.newTabSpec("tab2").setIndicator("Tab 2", null),
                FragmentTab.class, null);
        mTabHost.addTab(
                mTabHost.newTabSpec("tab3").setIndicator("Tab 3", null),
                FragmentTab.class, null);
    }
}

FragmentTab.java

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class FragmentTab extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_layout, container, false);
        TextView tv = (TextView) v.findViewById(R.id.text);
        tv.setText(this.getTag() + " Content");
        return v;
    }
}

 

UPDATE: Since I’ve been asked to give an example with the tabs on the bottom:

layout/activity_main.xml

<!--TABS WILL APPEAR ON THE BOTTOM-->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <FrameLayout
        android:id="@android:id/tabcontent"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"/>
    <android.support.v4.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>


'Android > General' 카테고리의 다른 글

이니 페이 코드 표  (0) 2015.03.04
Google Analytics for Android 활용  (1) 2015.03.02
Preferences 완벽 설명  (0) 2015.02.07
꺼진화면에서 알림창 띄우기  (0) 2015.02.04
[펌]Android V3 인앱결제 완벽 설명  (0) 2015.01.27


출처 ㅣ  http://tigerwoods.tistory.com/31

  



예제 프로젝트 다운로드



 

  

1. 환경설정 개요 (Preferences)

 

안드로이드 플랫폼은 Data를 저장하는 방법으로 환경설정(이하 Preferences), 파일, Local DB, 네트워크를 제공한다.

 

그 중 Preferences는 가장 간단하게 정보를 저장하는 방법(mechanism)을 제공하며, App이나 그 컴포넌트 (Activity, Service 등)의 환경 설정 정보를 저장/복원하는 용도로 사용된다.

 

 

▌Preferences의 형태▐

안드로이드에서 Preferences는 ListView의 형태로 표현되며 쉬운 Preferences의 구현을 위해 PreferenceActivity 클래스를 제공한다. PreferenceActivity 클래스는 XML 기반의 Preference 정의 문서를 통해 App 파일 저장소에 Preferences 파일을 생성하고 사용하는 방식으로 작동한다. (참고: 일반 Activity를 사용해 ListView형태의 Preference Activity가 아닌 커스텀 Preference Activity를 구현 할 수도 있지만 (Container + 각종 UI 위젯 사용) 이 글에서는 그 방법에 대해 다루지 않으려고 합니다)

 

 

▌Preferences 데이터 저장▐

Preferences를 위한 데이터 저장 시 사용되는 자료구조는 Map 방식이다. 즉 키(key)-값(value) 한 쌍으로 이루어진 1~n개의 항목으로 구성된다. 키(key) 는 String 타입으로, 말 그대로 각 데이터에 접근할 수 있게 하는 유일한 구분자며, 값(Value)은 기본 자료형 (int, boolean, string 등) 기반의 데이터로 구성된다. 다음은 Map 데이터 구조의 한가지 예이다.

 키(Key) - String 형
 값(Value) - 기본자료형
 Font
 "Noraml"
 FontSize 20
 FontColor FFFFFFFF
 ... ...
 ... ...


 

위와 같은 Preferences데이터는 안드로이드 디바이스의

data/data/App 패키지 이름/shared_prefs/

경로에 XML형태의 파일로 생성된다. xml 파일 이름은, 파일 생성/접근을 위해 어떤 메서드를 사용하느냐에 따라 시스템이 지정 할 수도 있고, 개발자가 임의의 파일 이름을 지정할 수도 있다.

 

 

참고로, 안드로이드에서 Preferences 데이터 파일을 다수의 App간에 공유하는 것은 기본적으로 불가능 하게 디자인 되어 있다. (참고: 여기서 불가능하다는 뜻은 Preferences Framework에서는 타 App의 환경 설정 파일에 접근할 수 있는 메서드를 제공하지 않는다는 뜻이다. 그럴 필요가 있을지 모르겠지만, 굳이 접근해야 한다면 Preferences 파일 생성 모드를 MODE_WORLD_READABLE로 설정해 guest가 파일을 쓰고 읽을 수 있도록 하고(위에서 MyCustomePref.xml 처럼) Preferences 데이터 파일을 파일 스트림을 통해 읽어와 직접 파싱하고 사용할 수 있는 편법이 있긴 하다)

 

XML파일을 디바이스로부터 추출해 열어보면 Preference 데이터가 다음과 같은 형식으로 저장되어 있음을 볼 수 있다.

1<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
2<map>
3    <boolean name="GreetingMsg" value="true" />
4    <boolean name="sub_checkbox" value="true" />
5    <string name="AdditionalMsg">tigerwoods.tistory.com</string>
6    <string name="TextColor">FF00FF00</string>
7    <string name="Ringtone">content://settings/system/ringtone</string>
8</map>

 

 

Preferences 구현 시 위와 같은 XML 기반 Preferences 데이터 파일을 File I/O API를 사용해 직접 생성/사용 하는 것은 아니다. 안드로이드는 Preferences구현/운용을 위해 android.preference 패키지 및 몇 가지 유용한 클래스/인터페이스(PreferenceActivity, SharedPreferences 등)를 제공하는데, 그것들의 도움을 받으면 손쉽게 Preferences를 구현/운용 할 수 있다.

 

 

▌Preferences 구현▐

안드로이드에서는 Preference 구현을 위해 많은 관련 클래스를 제공하기 때문에 그 클래스들을 적절히 이용하여 다음과 같이 몇 가지 단계만 추가하기만 하면 쉽게 프로젝트에 Preferences 기능을 추가 할 수 있다.

  • 프로젝트 내 어떤 환경정보를 Preferences로 관리 할지 설계
  • 위 설계에 따라 Preferences XML 문서를 작성하고 프로젝트 리소스로 공급 (\res\xml\ 밑)
  • 작성된 Preferences XML문서를 사용하는 PreferenceActivity 구현
  • Preferences가 필요한 Activity로부터 PreferenceActivity를 호출 (Intent 이용)
  • App이 다시 실행될 때 이미 저장된 Preference데이터 파일이 있다면 onResume()과 같은 callback내부에서 환경 설정 값 설정을 불러와 사용

 

위에 나열된 사항 이외에 Preference 데이터 파일의 생성, 데이터 변경 이벤트 listener, 데이터의 저장과 같은 부분은 PreferenceActivity 클래스가 자동으로 관리해 줌으로 개발자는 별로 신경 쓸 필요가 없다. (참고: PreferenceActivity를 사용하지 않고 일반 Activity로 구현한다면 안드로이드가 제공하는 여러 Preferences 관련 클래스 등을 이용해 파일 생성, 이벤트 listener, 저장 등을 직접 구현 해야 한다.)

 

그럼 위 Preference 구현 5단계 중 두 번째인 Preference XML 문서 작성부터 한 번 살펴보자.

 

 

 

 

2. Preference XML 문서 작성하기

 

Preference XML 문서를 작성하기 전에 문서를 구성할 Preferences 관련 클래스들에 관해 약간의 지식이 필요함. 이 글 끝 부분에 "참고. Preference XML 문서 작성시 사용되는 클래스" 단락에 간단한 클래스 설명과 중요 XML 속성, 메서드 등이 설명 되어 있음으로 참고.

 

Preferences XML 문서는 프로젝트 폴더\res\xml\파일이름.xml의 위치에 생성 한다. xml 파일의 이름은 개발자 임의로 지정할 수 있으며 추 후 code 상에서 R.xml.파일이름으로 접근 할 수 있다.

 

이렇게 생성된 XML 문서 내부를 여러 Preference 관련 클래스를 사용해 정의 해야 하는데, 이 글에 포함된 예제 프로젝트에서 사용된 Preference XML 문서를 살펴보면 다음과 같다.

소스 펼치기

 

 

그럼 settings.xml에서 사용된 요소들을 하나씩 분석해 보자

 

 

<PreferenceScreen>

Preferences XML 문서의 root 요소는 항상 <PreferenceScreen>이며 root 요소 내부에는 반드시 xmlns 속성 (xmlns:android=http://......)을 정의해 주어야 한다.

 

root로 사용된 <PreferenceScreen>는 child를 포함하는 container 역할을 하며, PreferenceActivity에게 Preferences 계층 구조를 전달하는 역할을 한다. root로 사용된 <PreferenceScreen>은 container역할 만 하고 자기 자신은 화면에 직접 표현이 안됨으로 title, summary 속성을 지정할 필요가 없다.

 

key 속성에 지정된 문자열은 추 후 code에서 이 preference 계층 구조 내부의 특정 요소를 찾을 수 있는 키 값이 된다.


Preferencescreen root = (PreferenceScreen)getPreference("preference_root");

CheckBoxPreference cb = (CheckBoxPreference)getPreference("checkbox");

 

 

<CheckBoxPreference> & <EditTextPreference>

Root <PreferenceScreen> 밑에 처음 나오는 두 개의 child이며, XML에 추가된 순서대로 Preference view에 표시된다.

각 아이템은 key, title, summary 속성이 지정되어 있으며, checkbox의 경우 초기 설정이 check 상태로 지정되어 있다.

<EditTextPreference> 같은 경우 DialogPreference로부터 상속하며, 클릭 시 별도의 다이얼로그 창을 popup 한다.

 

 

<PreferenceCategory>

예제는 총 2개의 <PreferenceCategory>가 있으며 첫 번째는 <ListPreference>와 <RingtonePreference>를 하나의 Category로 묶는다. 예제 XML의 34번째 라인에 보면 두 번째 Category도 지정되어 있으며 <PreferenceScreen>을 Catefory 멤버로 가지고 있다.

 

<PreferenceCategory>에서 title속성은 다른 Preference 객체와 같이 제목을 표시하지만, summary는 지정되어도 화면에 표시 되지 않는다.

 

또, 첫 번째 category의 속성 중 android:enabled 속성이 false로 지정되어 있음으로 Preference초기 실행 시 child인 <ListPreference>와 <RingtonePreference>는 비활성화 상태로 표현된다. 이와 같이 여러 Preference 아이템을 하나의 category로 묶으면 그룹 속성을 지정하는 것이 가능하다. (Preference의 마지막 항목, checkbox를 check하면 첫 번째 category를 활성화 함)

 

 

<ListPreference>

다른 항목들과 마찬가지로 key, title, summary가 설정되어 있으며, entries 속성에는 사용자에게 보일 ListView 아이템을 지정하며, entryValues속성에는 컴퓨터가 처리할 처리 할 정보를 제공한다. 예를 들면 entries – "Red", "Green", "Blue" 등 사람이 읽을 수 있는 값을 제공하고, entryValues에는 "FFFF0000", "FF00FF00", "FF0000FF" 등 컴퓨터가 사용할 정보를 제공한다.

 

또, android:defaultValue 속성이 "FFFFFFFF"으로 지정되어 있어 사용자가 설정 값을 바꾸기 전까지는 "FFFFFFFF" (흰색)이 기본 값으로 사용된다.

 

 

<RingtonePreference>

key, title, summary가 지정되어 있으며, showDefault와 showSilent가 true로 설정되어 Default 항목과 Slient 항목이 리스트에 표시된다. (<RingtonePreference>는 사용자가 지정한 정보를 바탕으로 Ringtone 설정 기능을 제공하는 Activity들을 찾는 Intent를 발생 시킨다. 디바이스에 여러 종류의 ringtone과 ringtone 관련 activity를 제공하는 app이 설치되어 있다면 더 많은 ringtone 옵션이 화면에 표시 될 수도 있다)

 

 

<PreferenceCategory>

두 번째 그룹으로 CheckBox를 별도의 화면에 표현하는 <PreferenceScreen> child를 포함.

 

 

<PreferenceScreen>

<PreferenceScreen>가 child 요소로 사용된 경우. Preferences에서 하나의 아이템으로 표현되기 때문에 title, summary가 지정되어 있다. 이 아이템을 선택하면 별도의 Preference 창 (dialog 기반)이 Popup한다.

 

 

<CheckBoxPreference>

key, title이 지정되어 있으며, summayOn과 summaryOff에는 각각 check상태의 도움말과 uncheck상태의 도움말이 설정 되어 있다.

 

 

 

 

3. XML + PreferenceActivity이용해 Preference 구현하기

 

작성된 Preference XML 파일을 화면에 표현 가능한 Preference로 만드는데 핵심적인 역할을 하는 것이 바로 PreferenceActivity이다. 전에 살펴본 적이 있는 ListActivity, TabActivity와 마찬가지로 Activity로 상속하며, Preference를 화면에 표현하고 운영하는데 특화된 클래스이다.

 

PreferenceActivity는 Preference 생성/운영에 도움이 되는 다음과 같은 편리한 기능이 구현되어 있다.

  • XML부터 Preference 계층 구조를 전달 받는 메서드 제공
  • 타 Activity의 Preference 계층 구조를 받아오는 메서드 제공
  • 제공된 Preference 계층 구조를 ListView 형태로 자동 구성/표현
  • 제공된 Preference 계층 구조에 따라 디바이스에 Preference 데이터 파일 자동 생성
  • 사용자가 변경한 사항들을 Preference 데이터 파일에 자동 저장

 

 

PreferenceActivity를 이용해 Preference를 구현하려면 크게 두 가지 단계만 거치면 되는데 다음과 같다. (물론 PreferenceActivity도 Activity 생명 주기를 따르기 때문에 onPause(), onResume() 메서드 같은 생명 주기 관련 callback의 구현도 신경 써야 하지만 여기서는 Preference를 구현하는 최소한의 기능만 설명한다)

  • PreferenceActivity를 상속하는 커스텀 클래스 정의.
  • onCreate()에 내부에서 Preference 계층 구조를 가져오는 메서드 호출.

 

위에서 두 번째 구현 순서로 설명된 Preference 계층 구조를 가져오는 메서드는 2개가 제공된다.

우선, \res\xml 밑의 Preference XML로부터 Preference 계층 구조를 얻어올 수 있는 메서드는 다음과 같다.

void addPreferencesFromResource(int)

Parameter:

  • int: Preference XML Resource. (예. R.xml.preferences)

 

두 번째로 타 Activity가 사용하는 Preference 계층 구조를 가져와 적용 시킬 수도 있는데, 이 때 사용되는 메서드는 다음과 같다.

(이 부분은 해결이 안 되는 중요한 버그가 있습니다. main preference까지는 잘 가져와 지는데 별도의 dialog를 띄우는 Preference 아이템을 클릭하면 WindowsManager가 BadTokenException을 발생합니다. 자세한 증상은 첨부된 예제 프로젝트에서 확인하시고, 혹시 해결 방법을 아시는 분은 꼭 댓글 부탁 드립니다)

void addPreferencesFromIntent(Intent)

Parameter:

  • Intent: Preference 계층 구조를 제공할 Activity를 지정하는 Intent

 

구현이 완료된 PreferenceActivity는 타 Activity로 부터 호출 되면 ListView 형태의 Preference 화면을 사용자에게 제공하고, 사용자가 정보를 수정하면 자동으로 Preference 데이터에 저장 하게 된다.

 

PreferenceActivity가 많은 편의 기능을 제공하지만 저장된 설정 복원은 개발자의 몫이다. 즉, 포함된 App이 다시 실행될 때, 기존에 저장된 Preferences설정 값을 Preference 데이터 파일로부터 읽어와 App에 적용 시키는 작업 (ex. App시작 시 Preference 파일에 저장된 폰트 색깔 등을 로드 해 App에 적용시키는)은 개발자가 직접 구현해 주어야 한다. 이를 위해 다음 단락에는 저장된 Preference 데이터 파일에 접근하여 저장된 정보를 가져오는 방법에 대해 알아본다.

 

 

 

 

4. Preference 데이터 파일로부터 환경 복원하기

 

안드로이드에서는 SharedPreferences 인터페이스를 이용해 저장된 Preferences 데이터 파일에 접근한다.

 

 

▌SharedPreferences 인터페이스 얻기▐

 

안드로이드 코드 내에서 SharedPreferences 인터페이스를 얻는 방법은 다음 나열된 세 개의 메서드를 통해서 가능하다. 



SharedPreferences Activity.getPreferences(int)

Parameter:

  • int: Preference 데이터 파일의 생성 시 접근 권한을 설정. Context클래스에 상수로 선언된 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITABLE을 설정 가능. MODE_PRIVATE는 지금 App 만 접근 가능하도록 파일을 생성하며 (_rw_rw____), MODE_WORLD_READABLE/WRITABLE은 타 App에서도 파일을 읽고/쓸 수 있도록 생성한다 (_rw_rw_r__ 또는 _rw_rw__w_ 또는 |를 이용해 두 속성을 모두 지정하면 _rw_rw_rw_)

Return:

  • SharedPreferences: 메서드가 호출되는 Activity 클래스의 이름으로 저장된 Preference 데이터 파일의 SharedPreferences 인터페이스를 리턴. 같은 이름의 Preference 데이터 파일이 없을 경우 새로 생성

 

예를 들면 Activity를 상속하는 A, B라는 클래스 이름의 커스텀 Activity가 정의 되어 있다. A 내부에서 getPreference를 호출할 경우 디바이스의 Preference 저장 위치(data/data/패키지이름/shared_prefs/)에 A.xml 이라는 Preference 데이터 파일을 검색하고 파일이 존재 한다면 해당 파일에 대한 SharedPreferences 인터페이스를 리턴 하며, 파일이 존재하지 않을 경우에는 A.xml 파일을 생성한 후 생성한 파일에 대한 SharedPreferences 인터페이스를 리턴한다. 마찬가지로, B 내부에서 getPreference를 호출할 경우 디바이스에서 B.xml 파일을 검색 후 해당 파일에 대한 SharedPreferences 인터페이스를 리턴 한다 (없으면 파일 생성 후 인터페이스 리턴).


 

SharedPreferences Context.getSharedPreferences(String, int)

Parameter:

  • String: 사용(생성)할 Preference 데이터 파일의 이름을 지정
  • int: 생성될 Preference 데이터 파일의 생성 시 접근 권한을 설정

Return:

  • SharedPreferences: 첫 번째 인자로 전달되는 문자열과 일치하는 이름의 Preference 데이터 파일의 SharedPreferences 인터페이스를 리턴. 만약 같은 이름의 파일이 없다면 파일을 새로 생성하고 생성된 파일에 대한 SharedPreferences 인터페이스를 리턴

 

한가지 참고할 사항은 이 메서드를 통해 개발자가 생성/사용될 Preference 데이터 파일의 이름을 임의로 설정할 수 있다는 것인데, 예를 들어, A라는 Activity에서 getSharedPreferences("initial_setting", MODE_PRIVATE)라는 메서드를 실행한다면, preference framework는 /data/data/패키지/shared_pref/ 밑에서 initial_setting.xml이라는 파일을 검색하고 없다면 이를 새로 생성 후 initial_setting.xml에 대한 SharedPreferences 인스턴스를 리턴 하게 된다..

 

제일 처음에 설명한 Activity.getPreferences(int)도 실제로는 자기자신을 호출한 Activity(또는 컴포넌트)의 이름과 입력된 int 형 인자를 가지고 본 메서드를 호출하는 형태로 구현되어 있다.




Static SharedPreferences PreferenceManager.getDefaultSharedPreferences(Context)

Parameter:

  • Context: 사용하기 원하는 SharedPreferences 인터페이스를 포함하는 Context 지정

Return:

  • SharedPreferences: 인자로 지정된 Context (app 구동 환경)가 기본으로 생성하는 Preference 데이터 파일에 대한 SharedPreferences를 리턴

 

이 메서드는 App level의 기본 Preference 데이터 파일을 생성/사용하는데 사용된다. 예를 들어 com.holim.test.aaa 라는 패키지에 포함되는 A Activity에서 본 메소드를 호출 하면 Preference 프레임웍은 /data/data/패키지이름/shared_pref/com.holim.test.aaa_preferences.xml 이라는 Preference 데이터 파일을 생성한다. 전달된 Context 인자에 따라패키지이름_preferences.xml 형태로 생성/사용할 Preference 데이터 파일의 이름이 시스템에 의해 자동으로 지정되게 되기 때문에, 호출되는 위치가 어디이든 같은 Context를 인자로 전달해 호출한 getDefaultSharedPreferences(…)메서드는 항상 같은 SharedPreferences 인스턴스를 리턴한다.

 

한가지 참고할 사항은 본 메서드는 static 메서드이기 때문에 메서드를 제공하는 PreferenceManager 클래스를 따로 생성할 필요 없이 다음과 같이 사용하면 된다.

SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);

 

 

이와 같이 여러 메서드를 사용해 얻어온 SharedPreferences 인터페이스로 무엇을 할 수 있는 지 알아 보자. 다음은 SharedPreferences 인터페이스가 제공하는 중요 메서드들이다.

 

 

▌SharedPreferences 인터페이스▐

 

SharedPreferences 인터페이스가 제공하는 기능은 다음과 같다.

  • 디바이스에 저장되어 있는 Preference 데이터 파일로부터 데이터를 추출하는 여러 메서드
  • Preference 데이터를 수정하는 방법을 제공하는 Editor 내부 인터페이스
  • Preference 데이터가 변경되었을 때 호출되는 callback 인터페이스

 

중요 메서드

SharedPreferences.Editor edit() – SharedPreferences가 연결된 Preference 데이터 파일을 수정 할 수 있게 여러 메서드를 제공하는 Editor 인터페이스 리턴. Editor 인터페이스는 자료 수정을 완료 한 후 Editor.commit() 메소드를 사용해야 파일에 변경내용이 저장됨.

void registerOnSharedpreferenceChangeListener(…) – Preference 변경 이벤트 Listener 등록

void unregisterOnSharedPreferenceChangeListener(…) – 위에 등록한 이벤트 Listener 등록 해지

Map <String, ?> getAll() – 모든 Preferences 값을 Map 자료형으로 리턴

boolean contains(String) – 인자로 제공되는 문자열과 같은 key의 Preference 항목이 있으면 true 리턴

타입 get타입(String, 타입) – Boolean, Float, Int, Long, String 형 메서드가 있으며 첫 인자는 데이터를 가져올 Key 값이며, 두 번째 인자는 찾는 preference 데이터가 존재하지 않을 때 리턴 할 값이다.

Boolean isMarried = sharedPref.getBoolean("결혼여부", false);

int fontSize = sharedPref.getInt("폰트사이즈", 20);

 

중요 인터페이스

  • SharedPreferences.Editor – Preference 파일에 저장된 항목을 수정/제거 할 수 있는 여러 메서드를 제공하는 인터페이스
  • SharedPreferences.OnSharedPreferenceChangeListener – Preference 데이터가 변경되었을 때 호출되는 callback 인터페이스

 

 

예제 프로젝트 중 MainActivity.java의 onCreate(…)와 onPause(…)메서드에서 보면 지금까지 설명한 SharedPreferences 인터페이스 얻기와 인터페이스가 제공하는 여러 메서드를 사용해 Preference 데이터 파일로부터 저장된 정보를 가져와 사용하는 것을 볼 수 있다.


MainActivity.onCreate()

소스 펼치기

 

MainActivity.onResume() 

소스 펼치기

 

 

 

 

참고. Preference XML 문서 작성시 사용되는 클래스

 

전 글들에서 다루었던 XML을 이용한 UI 및 메뉴 구성에서도 봤듯이

XML을 이용하면 디자인과 Logic을 분리 시킬 수 있어 간결한 코드의 구성이 가능할 뿐 아니라 현지화/유지보수 등 작업을 하는데 훨씬 편리 할 수 있음으로 권장되는 구현 방식 이라고 설명 한 적이 있다.

 

Preference에서도 마찬가지로 XML을 이용해 Preferences 구조를 쉽게 정의할 수 있다.

 

Preference XML 문서 작성시 android.preference 패키지 밑의 여러 클래스가 사용되는데 패키지 내부의 중요한 클래스의 상속 구조는 다음과 같다.

 

 

붉은 색 사각형 내부의 클래스가 Preferences 과 직접적으로 연관이 있는 클래스이며, 그 중 Preference 클래스를 비롯한 하위 클래스들이 XML Preference 문서 작성에 사용된다.

 

그럼 Preference 클래스부터 한번 살펴보자

  


 

▌Preference 클래스▐

여러 Preference 관련 클래스의 최상위 부모 클래스 이므로, 제공하는 XML 속성, 메서드는 자식 Preferences 관련 클래스에서도 모두 사용할 수 있다. XML 내부에서 직접적으로 사용되지는 않는다.

 

중요 XML 속성

  • android:key – Preferences 데이터 저장/불러올 때 사용되는 키(key) 지정
  • android:title – Preference 항목의 제목을 지정
  • android:summary – Preference 항목을 자세히 설명하는 문자열 지정
  • android:enabled – Preference 항목의 활성화 비활성화 여부 지정 (true/false)
  • android:selectable – Preference 항목의 선택 가능 여부 결정 (true/false)
  • android:order – Preference 항목이 표시되는 순서를 결정 (0-based 정수 사용: 낮은 값 먼저 보임). 순서가 명시적으로 지정되지 않는다면 XML에 정의된 순서대로 표시됨

 

중요 메서드

  • void setKey/Title/Summary(…) – Preference 항목의 키/제목/설명을 지정
  • void setEnabled(boolean) - Preference항목의 활성화/비활성화 여부 지정
  • void setSelectable(boolean) – Preference 항목의 선택 가능 여부 결정
  • void setLayoutResource(int) – Preference 항목에 표시할 View를 R 클래스로부터 지정
  • void setOnPreferenceChangedListener(…) – Preference 항목에 변화가 있으면 호출될 callback을 지정
  • void setOnPreferenceClickListener(…) – Preference 항목에 click 이벤트가 발생하면 호출될 callback을 지정

 

중요 인터페이스

  • Preference.OnPreferenceChangeListener - 사용자가 Preference를 변경하였을 때 호출되는 callback에 대한 인트페이스
  • Preference.OnPreferenceClickListener - 사용자가 Preference를 클릭하였을 때 호출되는 callback에 대한 인터페이스

 

  


▌PreferenceGroup 클래스▐

 

Preferences를 구성하는 객체들을 담을 수 있는 Container 클래스. PreferenceCategory와 PreferenceScreeen을 파생. XML 내부에서 직접적으로 사용되지는 않음.

 

중요 메서드

  • boolean addPreference(Preference) – 새로운 Preference 항목을 그룹에 추가. 추가 성공/실패 리턴.
  • Preference findPreference(String) – 인자로 전달되는 문자열과 같은 key값을 갖는 group 내 (자기자신포함) Preference 항목 리턴
  • Preference findPreference(int) – 인자(0-based정수)에 명시된 위치의 Preference 항목 리턴. 기본적으로 가장 처음 추가된 항목이 index 0
  • void removeAll() – 이 그룹 내 모든 Preference 항목을 삭제함
  • void removePreference(Preference) - 인자로 전달된Preference 항목을 삭제함
  • void setEnabled(boolean) – 그룹 전체의 활성화/비활성화 여부 지정
  • void setOrderingAsAdded(boolean) – true일 경우 그룹에 추가된 순서로 표현. false일 경우 Preference아이템 내 순서 정보가 있으면 그걸 따르고 순서 정보가 없으면 Preference 아이템의 title을 이용해 알파벳순 정렬.

  


 

▌PreferenceScreen 클래스▐

 

PreferenceGroup에서 파생하며, 실제로 Preference XML 문서 내부에 사용 되는 클래스이다. 여러 Preference 구성 요소를 담을 수 있는 root container역할을 하며 Activity에 표현될 Preference의 계층구조를 나타내는 클래스이다. PreferenceActivity에 전달되어 이 클래스의 인스턴스가 포함하는 것들을 화면에 표현한다.

 

이 클래스가 Preference XML 문서에서 사용될 때는 다음과 같이 두 가지 용도로 사용된다.

  • Preference XML 문서의 root 요소 사용 시 - 경우에는 PreferenceActivity에 전달되어 preference의 전체 구조를 전달하는 용도로 사용. 이 경우 자기 자신은 화면에 표현 안되고 child 요소를 포함하는 Container의 용도로 사용됨 (XML UI에서 Containter는 화면에 표현 안되고 것과 같은 의미)
  • Preference XML 문서에서root가 아닌 child 요소로 사용 시 - Preference 아이템 중 하나로 취급. Preference 아이템 중 하나로 화면에 표시되며, 클릭 시 별도의 preference 화면으로 이동

 

!!! 그림 (main pref. -> sub pref)

 


 

▌PreferenceCategory 클래스▐

 

Preferences를 구성하는 객체들을 특정 category별로 분류 할 수 있게 하는 클래스. 중요한 XML 속성이나 메서드 없음

!!! 그림 (category 분류)

 

  


▌DialogPreference 클래스▐

 

모든 dialog 기반 Preference 객체의 부모 클래스. Preference 화면에는 링크만 표시되고, 링크를 클릭했을 때 실제 Preference를 컨트롤 할 수 있는 Dialog가 popup한다.

!!! 다이얼로그 그림

 

중요 XML 속성

  • android:dialogIcon – Dialog의 Icon 지정
  • android:dialogTitle – Dialog의 제목 지정
  • android:dialogMessage – Dialog 내에 표시될 문자열 내용 지정
  • android:negativeButton – 부정 버튼에 표시될 Text설정 (취소, cancel 등)
  • android:positiveButton – 긍정 버튼에 표시될 Text 설정 (적용, OK 등)

 

중요 메서드

  • void setDialogIcon(Drawable) – Dialog의 Icon 지정
  • void setDialogTitle(…) – Dialog의 제목 지정. 문자열 직접 지정 또는 문자열 리소스 사용
  • void setDialogMessage(…) – Dialog 내에 표시될 문자열 내용 지정
  • void setPositiveButtonText(…) – 부정 버튼에 표시될 Text설정 (취소, cancel 등)
  • void setNegativeButtonText(…) – 긍정 버튼에 표시될 Text 설정 (적용, OK 등)

  


 

▌EditeTextPreference 클래스▐

 

DialogPreference로부터 상속하며, 사용자로부터 문자열을 입력 받아 저장 하기 위한 Preference 아이템을 구현한 클래스.

 

중요 메서드

  • EditText getEditText() – Dialog가 포함하는 EditBox인스턴스를 리턴
  • String getText() – SharedPreferences 객체로부터 저장된 문자열 리턴
  • void setText() – SharedPreferences에 문자열 저장

  


 

▌ListPreference 클래스▐

 

DialogPreference로부터 상속하며, Dialog기반의 ListView 위젯에 미리 제공되는 문자열들을 표현하고 사용자가 그 문자열 중 하나를 선택하여 설정 값을 지정할 수 있도록 구현한 클래스.

 

중요 XML 속성

  • android:entries – ListView의 각 raw에 표현될 문자열을 array 리소스를 통해 공급. 사용자(human)를 위한 정보.
  • android:entryValues – ListView의 특정 raw가 사용자에 의해 선택되었을 때 프로그램 내부적으로 처리 할 문자열 data를 array 리소스 형식으로 제공. 컴퓨터가 처리 하기 위한 정보. (ex. 남/여: 사람을 위한 정보 -> 0/1: 컴퓨터를 위한 정보)
  • android:defaultValue – 초기 선택 항목 지정. (0-based 정수)

 

중요 메서드

  • CharSequence[] getEntries() – ListView의 각 row에 표현될 문자열들을 리턴 (사용자 위한 정보)
  • CharSequence[] getEntryValues() – Entry(사람을 위한 정보)와 연계된 컴퓨터가 처리할 문자열들을 리턴
  • void setEntries(…) – ListView의 각 row에 표현할 문자열 지정. Array리소스 또는 CharSequence[] 전달
  • void setEntryValues(…) – 컴퓨터가 처리할 문자열 지정. Array또는 CharSequence[] 전달

  


 

▌CheckBoxPreference 클래스▐

 

CheckBox 기능의 Preference 아이템을 구현한 클래스. SharedPreferences에 boolean 값으로 정보 저장

 

중요 XML 속성

  • android:summayOn – CheckBox가 check상태일 때 사용자에게 보일 안내문
  • android:summaryOff – CheckBox가 uncheck 상태일 때 사용자에게 보일 안내문

 

중요 메서드

  • boolean isChecked() – 현재 check/uncheck 상태 리턴
  • void setChecked(boolean) – CheckBox를 program 상에서 check/uncheck 함
  • void setSummaryOn(…) – CheckBox가 check상태일 때 사용자에게 보일 안내문 설정. String 리소스나 CharSequence 인스턴스 전달
  • void setSummaryOff(…) – CheckBox가 uncheck상태일 때 사용자에게 보일 안내문 설정

  


 

▌RingtonPreference▐

 

사용자가 디바이스에서 제공하는 전화 벨 소리를 지정할 수 있게 구현된 클래스. Intent를 이용해 어떤 벨 소리 Picker를 화면에 표시할 지 결정함.

 

중요 XML 속성

  • android:ringtoneType – 어떤 종류의 벨 소리 종류를 선택 가능하게 할지 지정. ringtone, notification, alarm, all 중에 하나 또는 복수(| 사용)개 지정 가능
  • android:showDefault – Default 벨소리 항목 표시 여부 결정 (true/false)
  • android:showSilent – 무음(Silent) 항목 표시 여부 결정 (true/false)

 

중요 메서드

  • setRingtoneType(int type) – XML 속성 중 ringtoneType에 대응. RingtoneManager 클래스에 선언되어 있는 상수 TYPE_RINGTONE, TYPE_NOTIFICATION, TYPE_ALARM, TYPE_ALL 중 하나 또는 복수(|사용) 전달
  • setShowDefault(boolean) – XML showDefault 속성에 대응
  • setShowSlient(boolean) – XML showSilent 속성에 대응

  


 

 PreferenceManager 클래스 

 

Activity나 XML로부터 Preferences 계층구조의 생성을 돕는 helper 클래스이지만 이 클래스를 이용하여 직접 Preferences를 구성하기보다는 PreferenceActivity.addPreferenceFromResource(int) 또는 PreferenceActivity.addPreferenceFromIntent(Intent)를 사용하는 것이 일반적이다.

 

혹시, PreferenceActivity를 사용하지 않고 Activity를 이용해 직접 Preferences 화면을 구성해야 한다면 필요한 클래스이다.

 

중요 메서드

  • Preference findPreference(CharSequence) – 인자로 전달되는 Key값을 가지는 Preference 항목의 인스턴스를 가져옴
  • SharedPreferences getDefaultSharedPreferences(Context) – 제공되는 context가 기본으로 사용하는 Preference파일로부터 SharedPreferences인스턴스 생성해 리턴
  • SharedPreferences getSharedPreferences() – this객체가 사용하는 context와 연결될 Preference파일로부터 SharedPreferences 인스턴스 생성해 리턴
  • void setDefaultValues(Context, int resId, boolean) – 제공되는 context가 사용하는 SharedPreferences를 두 번째 인자로 제공되는 XML 리소스에 정의된 기본 속성값들로 설정 함. 세 번째 인자가 false 일 경우엔 이 메서드가 전에 실행된 적이 있다면 무시하고, true일 경우 전에 실행 여부화 상관없이 재 실행된다. 참고: 마지막 인자를 true로 지정 했다고 해서 이 메서드를 이용해 Preferences를 바로 초기화 할 수 있는 것은 아니고, SharedPrefernces.clear() 메서드를 사용해 Preferences 항목을 모두 삭제 후 이 메서드를 사용해 XML에 지정된 본래의 값으로 Preferences를 초기화 할 수 잇다.

 

 


카톡 스타일의, 화면 꺼진 상태에서도 푸시가 오면 알림창을 띄우는 기능을 구현해야 했다. 

처리 절차는 다음과 같다.

1. 푸시를 받았는데 화면이 꺼져있다면
2. 락 스크린 위로(!) 특정 액티비티를 실행한다.
3. 이 액티비티에서 원하는 액션을 수행한다. (대게 '보러가기' / '닫기' 수준이겠지)

1번에선 화면이 꺼져있는지 여부를 확인해야 하고, 이는 다음과 같이 확인할 수 있다.


public static boolean isScreenOn(Context context) {
return ((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isScreenOn();
}

2. 락 스크린 위로 액티비티를 보여주기 위해선 보여줄 액티비티가 full screen 이어야 하며, 다음과 같은 window 세팅을 해 줘야 한다. onCreate() 메서드에 넣어두면 된다.

getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

맨 위의 dismiss keyguard 는 락 까지 해제하겠다는 것인데, 몇 번 테스트 해 보니 일반적인 상황에선 락 화면을 없애지 못했다. 굳이 제거 안되는 락 스크린까지 걷어낼 필요가 없어 그냥 넘어가고.

3번은 case by case 이니 딱히 언급할 필요가 없고.

그런데 내 경우에 화면 표시까진 잘 되었는데, 몇 가지 요상한 문제가 발생했다.

1. recent app 목록에서 앱을 선택하면 원래 앱 화면이 아닌, 저 팝업 화면이 튀어나왔다.
2. 그래서 저 팝업 화면에다 android:excludeFromRecents="true" 를 설정했더니 아얘 recent app 목록에서 사라졌다.

앱을 만들면서 task를 새로 만들어본 적이 한번도 없었는데, push를 받아 activity를 생성할 때 newTask 를 붙여놨다지만 이 pushPopup 과 원래 앱의 taskAffinity 가 동일하다면 (별도로 설정을 하지 않는 이상 동일하다) 실제론 task가 새로 생기지 않으며, 아마도 처음 task를 실행한 pushPopup에서 android:excludeFromRecents 가 true 로 되어 있어 recent 목록에서도 날아간 듯 하다.

해결은

1. pushPopup 액티비티에 별도의 taskAffinity 를 주고,
2. pushPopup 액티비티는 android:excludeFromRecents="true" 를 명시했으며
3. pushPopup 을 띄울 때나, 이 액티비티가 원래의 앱을 호출할 때 모두 Intent.FLAG_ACTIVITY_NEW_TASK 플래그를 달아줬다.

이렇게 하니
1. recent 목록에서 원래 앱이 날아가는 문제도 없고
2. recent 목록의 항목을 선택하면 원래 앱의 원래 화면으로 잘 찾아들어갔다.

* activity 정의

<activity 
android:name=".activity.PushPopup_"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:excludeFromRecents="true"
android:taskAffinity="{my_pkg_name}.popup"/>* 화면 꺼졌을 때 activity 호출


if (!Util.isScreenOn(context)) {
context.startActivity( new Intent(context, PushPopup_.class).putExtra( blah blah).setFlags( Intent.FLAG_ACTIVITY_NEW_TASK));
}

* popup activity 의 oncreate에서 flag 설정

getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
1
김윤중님의 프로필 사진이줭님의 프로필 사진김미혜님의 프로필 사진Sewon Ann님의 프로필 사진
댓글 16
김윤중
2013. 8. 13.
 
 
안녕하세요. 안드로이드 공부하는 학생입니다. 중복으로 푸쉬를 보내면 맨처음 푸쉬창을 사용자가 없애지 않으면 다음 푸쉬가 안보내지는데, 중복푸쉬를 가능하게 하는 방법이 있나요?
Sewon Ann
2013. 8. 13.
 
 
+김윤중  안녕하세요. 제가 이 글을 쓴 지 너무 오래되어 저도 기억이 잘 안나는데, 푸시창이라고 하신게 화면 꺼짐상태의 창을 말씀하신거지요? 이 경우 제일 좋은 방법은 이미 열려있는 화면 꺼짐 창의 내용을 갱신하는 것입니다. 메서드가 잘 기억이 나진 않는데 현재 알림 창이 떠 있는지 확인하고, 떠 있다면 액티비티의 내용을 갱신하기 위해 broadcast 를 날리던지 intent를 날리던지 하는 방식으로 처리할 수 있을거에요. 저도 그렇게 했던 것 같구요.
김윤중
2013. 8. 13.
 
 
빠른 답변 감사합니다!! 네 맞습니다. 화면이 꺼진상태에서 푸시가 날라오면 알림창을 보여주는 것이죠. 하지만 연속으로 푸시를 보내면 첫번째 푸시만 알림창으로 나오고 다음 푸시는 나오지 않는 문제가 있는데 그 문제를 해결 못하고 있습니다ㅠ
Sewon Ann
2013. 8. 13.
 
 
+김윤중 제 경우엔 푸시팝업 화면을 singleTop인가 singleInstance로 띄우고, 여기에 intent를 다시 보냈던 것 같아요. 이 때 푸시팝업 activity는 onNewIntent() 로 새 인텐트를 받게 되고, 이 메서드안에서 내용을 갱신하도록 구현했습니다.
김윤중
2013. 8. 13.
 
 
우와!! 감사합니다! +Sewon Ann 님 말씀대로 하니까 푸쉬팝업이 연속적으로 꺼진상태에서 작동하긴 하나 아쉽게도 출력되는 데이터 값이 이전 데이터가 찍히는 상황이 벌어지네요. 아무래도 onNewIntent()에서 값을 제대로 받아 오지 못하는 것 같습니다.ㅠㅠ;;
Sewon Ann
2013. 8. 13.
 
 
@김윤중아마도 getIntent() 로 데이터를 가져오신 것 같은데 onNewIntent() 의 parameter로 넘어온 intent 값을 사용하셔야 해요. getIntent() 를 쓰시고 싶다면 onNewIntent() 안에서 새로 넘어온 intent 객체를 setIntent() 로 설정해서 사용하시면 되지 싶습니다. 도움이 되시길 :-)
김윤중
2013. 8. 13.
+
1
2
1
 
 
+Sewon Ann  감사합니다. 도움되었습니다! 정말정말 감사합니다!!! 와 감동의 눈물이 ㅠㅠㅠㅠㅠ 삼일 해매던걸 여기와서 한퀴에 해결했네요 ㅠㅠ 너무 감사합니다.
김윤중
2013. 8. 13.
 
 
으아!! 너무 감사드립니다 ^_^ 앞으로 종종 찾아뵐께요 !!
이줭
2013. 10. 31.
 
 
안녕하세요~ 저는 폰이 sleep mode로 넘어간뒤 일정 지나면 푸쉬를 보내도 onReceive() 에 들어오지 않는데요... 혹시 방법 아시나요?ㅜㅜ , 폰이 활성->슬립모드로 들어가자마자 푸쉬를 보내면 onReceive()에 들어와서 화면을 깨우고 팝업을 보여줍니다.
Sewon Ann
2013. 10. 31.
 
 
+이주영  아마도 서버에서 푸시를 보낼 때 delay_while_idle 속성을 true로 해 두셨을거에요. 이걸 true로 해 두면 첫번째 푸시는  receive로 들어오지만, 이 때 바로 폰은 "아, 나는 이제 자는 상태니 푸시 보내지마" 상태로 접어들기 때문에  이후 푸시가 화면 켜야 들어옵니다. 그래서 일단 delay_while_idle 를 확인해보시는 게 좋을겁니다. false 여야 자고 있는 상태에서도 깨우는 처리가 될 거에요. 

http://developer.android.com/google/gcm/server.html
이줭
2013. 10. 31.
 
 
우와!! 감사합니다 ㅜㅜ 몇일째 헤매던건데 바로 해결해주셨어요!!댓글도 바로 달아주시구!!!! 두번 감사해요ㅎㅎㅎ 
Sewon Ann
2013. 10. 31.
 
 
+이졍 네, 저도 이것때문에 한참 고생을 했어서 마침 기억을 하고 있었네요.
김미혜
2014. 3. 15.
 
 
gcm 서버로부터 메시지를 받기는 합니다. 화면이 꺼져있을때 깨우면서 최상위로 activity를 띄우게 하고 싶은데요. 2번대로는 해봤는데 1번이 없어서그런지 안되더라구요.
Sewon Ann
2014. 3. 15.
 
 
+김미혜 깨우는 게 문제라면 wake lock 쪽을 살펴보시면 될 것 같네요


출처 : http://crowjdh.blogspot.kr/2013/11/v3.html

Ch1. 준비

0. Android SDK Manager -> Extras -> Google Play Billing Library 다운받고 설치한다.

1. Google Play Developer Console 여기로 가서 내 앱 추가

 - Prepare Store Listing 상태로 추가.

2. <SDK Path>/extras/google/play_billing/IInAppBillingService.aidl 파일을 프로젝트의 com.android.vending.billing 에 복사한다.

3. <SDK Path>/extras/google/play_billing/samples/

TrivialDrive/src/com/example/android/trivialdrivesample/util 의 클래스들을 내 프로젝트의 적절한 곳으로 복사한다.

*패키지명은 내 프로젝트 패키지 하위 패키지로 설정한다.

4. 메니페스트 파일의 <manifest>태그 아래에

<uses-permission android:name="com.android.vending.BILLING" />

퍼미션을 추가한다.

5. 인앱빌링을 사용하고자 하는 액티비티의 onCreate내에서 IabHelper 인스턴스를 생성한다. 이때 넘겨주는 license key는 아래와 같은 방법으로 구성, 보안을 높이는 것을 추천한다.

*license key는

Google Play Developer Console -> 위 1에서 등록한 내 어플리케이션 -> Services & APIs

여기에서 확인한다.

Security Recommendation: It is highly recommended that you do not hard-code the exact public license key string value as provided by Google Play. Instead, you can construct the whole public license key string at runtime from substrings, or retrieve it from an encrypted store, before passing it to the constructor. This approach makes it more difficult for malicious third-parties to modify the public license key string in your APK file.

6. IabHelper인스턴스에 startSetup메소드에 콜백 등록하자. (http://developer.android.com/training/in-app-billing/preparing-iab-app.html#Connect 참조)

6.1 해당 액티비티에서 인앱결제가 완료됐을 경우

@Override

public void onDestroy() {

   super.onDestroy();

   if (mHelper != null) mHelper.dispose();

   mHelper = null;

}

이처럼 연결을 해제한다.

* 연결이 실패할 경우 IabHelper 클래스의 dispose() 메서드 내에서 에러가 나는 경우가 있다.

연결이 실패할 경우 startSetup 메서드에서 mServiceConn 객체 인스턴스를 만들 때 onServiceDisconnected 콜백이 불리면 mService 을 null로 만들어준다.

if (mContext != null) mContext.unbindService(mServiceConn);

위의 줄을

if (mContext != null && mService != null) mContext.unbindService(mServiceConn);

위처럼 바꾸자.

7.1 릴리즈용 키스토어를 사용해 사인된 apk파일을 생성한다.(디버그용 키스토어어는 안됨)

(http://www.androidpub.com/35445 참조)

* conversion to dalvik format failed with error 1 에러 발생시

project properties -> java build path -> Libraries

에서 의심가는 라이브러리를 제거하고 다시 추가해보자.

android-support-v4.jar을 업데이트한 후에 이 에러가 발생할 수 있다.

* 자신의 프로젝트나 라이브러리로 임포트한 프로젝트의 values/strings.xml 파일에서 Missing Translation 이라는 린트 에러가 날 때는

1. 아직 번역이 덜 끝났을 경우 :

<resources

   xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">

위 처럼 우선 무시하고 번역이 끝났을 경우 번역을 추가하고 위의 속성을 제거한다.

2. 번역이 필요없는 문자열의 경우 :

<string name="hello" translatable="false">hello</string>

위 처럼 translatable=”false”라는 속성을 주자.

7.2 Google Play Developer Console -> 위 1에서 등록한 내 어플리케이션 -> APK탭

에 들어가서 APK 업로드하기기(퍼블리시 하지 않는다)

7.3 Google Play Developer Console -> 위 1에서 등록한 내 어플리케이션 -> In-app Products

에 들어가서 새 제품을 추가하고 continue를 누른다.

*Type : Managed product를 선택한다.(일반적인 소모성 아이템의 경우)

*Product Id : application에서 이 아이템을 특정하는 데에 필요한 아이디를 입력.

(네이밍 규칙은 https://support.google.com/googleplay/android-developer/answer/1072599?hl=ko 여기를 참조)

7.4 추가 정보 입력 후 위의 save버튼 옆 inactive 버튼을 눌러 active로 바꿔준다.

*Warning: It may take up to 2-3 hours after uploading the APK for Google Play to recognize your updated APK version. If you try to test your application before your uploaded APK is recognized by Google Play, your application will receive a ‘purchase cancelled’ response with an error message “This version of the application is not enabled for In-app Billing.”

Ch.2 구현

인앱 구매에서 일반적으로 필요한 시나리오는 다음과 같다.

(아래는 모두 셋업이 끝난 후, 즉 IabHelper.startSetup(OnIabSetupFinishedListener listener)의 콜백에서 result.isSuccess()가 true를 반환한 경우에 해야 한다.)

1. 내가 등록한 아이템의 정보(이름, 가격 등의 세부 정보)를 알아오기.

2. 구매 절차

일반적으로 OnIabSetupFinishedListener에서 위 1을 수행해 아이템의 이름과 가격을 가져오고 난 뒤 그 정보를 유저에게 보여준다. 유저가 구매 버튼을 누른 경우 위 2를 수행한다.

1. 아이템 확인

1. Ch.1 - 6의 startSetup메소드의 결과 연결이 수립된 후 불리는 OnIabSetupFinishedListener에서 아래와 같이 인앱제품의 세부사항을 요청할 수 있다.

인앱 제품의 sku(고유 아이디. 위 Ch.1 - 7.3 참고)를 리스트에 넣고 인벤토리의 정보를 요청한다.

List<String> additionalSkuList = new ArrayList<String>();

additionalSkuList.add(IAB.SKU_BG_RED);

additionalSkuList.add(IAB.SKU_BG_YELLOW);

mHelper.queryInventoryAsync(true, additionalSkuList,mQueryFinishedListener);

2. 세부사항 요청의 결과를 받아올 콜백 메소드를 선언하자. 위에서 요청한 인벤토리의 정보가 아래의 콜백으로 들어온다.

private IabHelper.QueryInventoryFinishedListener

           mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {

           public void onQueryInventoryFinished(IabResult result, Inventory inventory)  

           {

              if (result.isFailure()) {

                 // handle error

                 return;

               }

               String redPrice =

                  inventory.getSkuDetails(IAB.SKU_BG_RED).getPrice();

               String yellowPrice =

                  inventory.getSkuDetails(IAB.SKU_BG_YELLOW).getPrice();

               // update the UI

               TextView resultView = (TextView)findViewById(R.id.result);

               resultView.setText("red price : " + redPrice + ", yellow price : " + yellowPrice);

           }

        };

2. 아이템 구매

1. 아이템 구매 요청을 날리기 전 액티비티의 onActivityResult에서 아래와 같은 처리를 해야 한다.

빨간 색으로 하이라이트한 부분이 중요하다. 이 부분을 처리해주지 않으면 정상적으로 구매절차가 이뤄지지 않는다.

@Override

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        LogUtil.message(TAG, "requestCode : " + requestCode + ", resultCode : " + resultCode + ", data : " + data);

        if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {

            // not handled, so handle it ourselves (here's where you'd

            // perform any handling of activity results not related to in-app

            // billing...

            super.onActivityResult(requestCode, resultCode, data);

        }

        else {

            Log.d(TAG, "onActivityResult handled by IABUtil.");

        }

    }

2. 위에서 인벤토리의 정보를 가져온 후 유저가 구매버튼을 누르면 아래의 메서드를 실행, 구매절차를 수행한다.

mHelper.launchPurchaseFlow(MarketAct.this, IAB.SKU_BG_RED, IabHelper.ITEM_TYPE_INAPP, RC_REQUEST, mPurchaseFinishedListener, "test payload");

인자값들

1) 엑티비티 인스턴스

2) 구매할 인앱 아이템 ID(SKU)

3) 아이템의 종류.

1. 일반 인앱 아이템 : IabHelper.ITEM_TYPE_INAPP

2. 정기구독 : IabHelper.ITEM_TYPE_SUBS

4) 임의 정수. 구매과정이 끝나면 onActivityResult의 requestCode로 반환된다. 자세한 내용은 후에 적는다.

5) 구매 종료 후 불려질 콜백. 여기서 상황에 따라 데이터와 UI를 업데이트 해준다.

6) ***

https://play.google.com/apps/publish/

1. All applications에 들어가서 테스트할 어플리케이션이 등록되어 있는지 확인.

2. Setting -> Account details

에서 Gmail accounts with testing access 에 자신이 사용하는 디바이스의 기본 계정을 추가한다.

* 기본 계정을 변경하려면 디바이스를 공장초기화하고 등록해야 한다(고 한다).

3. ch.1 - 7.1 ~ 7.2의 과정을 거친 apk파일을 올려야 테스트 구매를 할 수 있다. 릴리즈용으로 사인된 apk를 업로드하자.

(서버측의 인증은 대중 없다. 새로 apk를 올린 후 정상적으로 인앱 확인, 구매를 하려면 15분 정도 소요된다고는 하지만 테스트결과 주말의 경우 하루가 지나야 인증되는 경우도 있었다.)

4. 마찬가지로 이클립스에서 실행한 앱으로는 테스트 구매를 할 수 없다. 위 3번 단계에서 사인한 apk를 디바이스에 직접 설치하자.

*명령어(Mac OS 기준) :

언인스톨 : <sdk path>/platform-tools/adb [-s 디바이스 번호] uninstall 패키지명

인스톨 : <sdk path>/platform-tools/adb [-s 디바이스 번호] install 파일이름.apk

*디바이스 번호는

<sdk path>/platform-tools/adb devices

로 확인할 수 있다. 여러 개의 디바이스가 연결된 상황이 아니라면 이 옵션은 무시해도 좋다.

3. 아이템 소모

소모성 아이템의 경우 아이템을 사용하거나 앱이 구동될 때 체크하는 것이 좋다.

앱이 구동될 때 체크하는 이유는 소모성 아이템은 구입 즉시 유저에게 제공해야 하기 때문이다. 만약 소모성 아이템이 구글 플레이 서버에 남아있다면 유저에게 제공되지 않았다는 것을 뜻한다.

또한 소모성 아이템은 구글 플레이 서버에 소모 요청을 하지 않으면 다시 구매할 수 없기도 하므로 꼭 소모해주자.

mHelper.consumeAsync(purchase, mConsumeFinishedListener);

1) 소모할 아이템의 Purchase객체

2) 콜백. 변경된 데이터 반영과 UI 업데이트.

부록 2. 참고 문서

API Document

http://developer.android.com/google/play/billing/index.html

Lesson

http://developer.android.com/training/in-app-billing/index.html

Managing application

https://play.google.com/apps/publish/

Handling In-app-purchase orders

https://wallet.google.com/merchant/



출처 : http://snowbora.tistory.com/421

그동안 안드로이드에서의 Bitmap 이미지 관련해서 많은 글을 남겼는데, 
거의 최종 버전에 가까운 글입니다.

관련글은

이며, 관련글도 차례로 보는게 더 도움이 되실겁니다. 

기본적인 뼈대는 구글 블로그에 공개되어 있는 멀티 쓰레드를 이용한 이미지 다운로드 소스입니다. 
여기에 URL을 이용한 파일 캐싱 기능과 이미지 크기가 너무 커서 BitmapFactory에서 decoding 도중에
bitmap size exceeds VM budget 오류를 발생하며 종료되는 부분을 처리했습니다.


그리고, Bitmap 이미지를 파일로 저장하는 과정에서

bitmap.compress(CompressFormat.JPEG, 100, out);
bitmap.compress(CompressFormat.PNG, 100, out); 

2가지 옵션을 사용할 수 있는데

이미지를 JPEG로 압축할 경우, 특정 사진들에 대해서 화질이 너무 안 좋다는 단점이 있었고
이미지를 PNG로 압축할 경우에는 화질은 JPEG보다 우수하지만 압축하는데 시간과 리소스가 
너무 오래걸린다는 단점이 있었습니다. (둘다 이미지를 압축하기 때문에 시간 및 리소스가 소비됩니다.)


그래서 그냥 네트워크로 받은 이미지를 원본 그대로 파일로 저장한다음, 그 파일에서 BitmapFactory.decodeFile을 
이용해서 불러오는 것으로 바꾸었습니다. 



다음은 파일에서 이미지를 bitmap size exceeds VM budget 오류를 발생시키지 않고 안전하게 불러오는
부분입니다. 

01.// File 에서 이미지를 불러올 때 안전하게 불러오기 위해서 만든 함수
02.// bitmap size exceeds VM budget 오류 방지용
03.Bitmap SafeDecodeBitmapFile(String strFilePath)
04.{
05.File file = new File(strFilePath);
06.if (file.exists() == false)
07.{
08.return null;
09.}
10. 
11.// 가로, 세로 최대 크기 (이보다 큰 이미지가 들어올 경우 크기를 줄인다.)
12.final int IMAGE_MAX_SIZE    = GlobalConstants.getMaxImagePixelSize();   
13.BitmapFactory.Options bfo   = new BitmapFactory.Options();
14.bfo.inJustDecodeBounds      = true;
15. 
16.BitmapFactory.decodeFile(strFilePath, bfo);
17. 
18.if(bfo.outHeight * bfo.outWidth >= IMAGE_MAX_SIZE * IMAGE_MAX_SIZE)
19.{
20.bfo.inSampleSize = (int)Math.pow(2, (int)Math.round(Math.log(IMAGE_MAX_SIZE
21./ (double) Math.max(bfo.outHeight, bfo.outWidth)) / Math.log(0.5)));
22.}
23.bfo.inJustDecodeBounds = false;
24.bfo.inPurgeable = true;
25.bfo.inDither = true;
26. 
27.final Bitmap bitmap = BitmapFactory.decodeFile(strFilePath, bfo);
28. 
29.return bitmap;
30.}




그리고 이미지를 네트워크에서 다운로드해서 파일로 저장하는 함수입니다.