728x90

안드로이드 코딩하면서 유용한 걸 적어둔다.


오늘날짜 구하기

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

SimpleDateFormat formater = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);
Date current = new Date();
String date = formater.format(current);
Toast.makeText(getApplicationContext(), "date ==="+date, Toast.LENGTH_SHORT).show();


소요시간 측정하기

걸리는 시간을 측정해서 대체 코드를 구현해야 할지 판단할 때 좋을 듯하다.

final SimpleDateFormat FORMATTER = new SimpleDateFormat("mm:ss.SSS");
long startTime = System.currentTimeMillis();
System.out.println("Time taken to insert " + FORMATTER.format(new Date(System.currentTimeMillis() - startTime)));


배열 선언

int[] apple = {20, 27, 24, 26, 32, 40};
위의 행은
int[] apple = new int[] {20, 27, 24, 26, 32, 40};
와 같다.


- apple.length는 배열의 길이를 의미

- 배열은 첨자(index, 인덱스)값으로 접근한다.
- 첨자값은 0부터 시작한다.


ArrayList 를 배열로 변환

toArray() 메소드를 이용한다.


ArrayList<Integer> pbook = new ArrayList<Integer>();
  pbook.add(1);
  pbook.add(2);
  pbook.add(3);
  pbook.add(4);

// List -> Integer 배열
Integer[] pArray = pbook.toArray(new Integer[pbook.size()]);

int i=0;
for(Integer temp : pbook){
 
pArray [i++] = temp;
}


ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Test1");
arrayList.add("Test2");
arrayList.add("Test3");

String[] array = new String[arrayList.size()];
int i=0;
for(String temp : arrayList){
  array[i++] = temp;
}


배열을 ArrayList 로 변환

// String[] -> List

String[] array = {"aaa", "bbb", "ccc"};

배열을 ArrayList 또는 List로 변환하는 것은 아래 3개중에서 한줄이면 된다.

List<String> list = Arrays.asList(array);
List<String> list = Arrays.<String>asList(array);
ArrayList<String> arrayList = new ArrayList<>(Arrays.asList(array));


List<String> list = Arrays.asList(str.split("\\s*,\\s*")); // 콤마로 구분해서 문자열을 넣어준다.

화면 출력하는 방법은

Iterator<String> ie = arrayList.iterator();
while (ie.hasNext()) {
    System.out.println(ie.next());
}


무식한 방법으로 for문을 사용하는 방법은

String[] array = new String[3];
array[0] = "Test1";
array[1] = "Test2";
array[2] = "Test3";
ArrayList<String> mList = new ArrayList<>();
for(String temp : array){
  mList.add(temp);
}



배열을 매개변수로 받는 메소드

public static int[] addNumArray(int[] pArray){
    for(int i=0; i < pArray.length; i++)
        pArray[i] += 10;
    return pArray;
}


ArrayList 검색

- ArrayList에 indexOf(Object o) 메소드를 사용하면 해당 값이 ArrayList에서 몇번째 위치해 있는지 찾아준다.

- indexOf 결과가 없으면 -1 을 반환한다.

- index 와 get 메소드를 이용하여 원하는 값을 찾아낼 수 있다.

- index 값이 -1 인 경우 get(indexOf(Object o)) 를 하니까 에러를 발생시킨다.

  이 경우에는 반드시 분리해서 처리를 해야 에러가 없이 동작된다.

String temp_key = "";
String temp_mNO = "";
String temp_oNO = "";
if(temp_mobileID.indexOf(ContactId) > -1){
    temp_key = temp_keyIDX.get(temp_mobileID.indexOf(ContactId));
    temp_mNO =temp_mobileNO.get(temp_mobileID.indexOf(ContactId));
}
if(temp_officeID.indexOf(ContactId)>-1){
    temp_oNO = temp_officeNO.get(temp_officeID.indexOf(ContactId));
}

- indexOf 는 동일한 값이 2개 이상 존재하면 가장 먼저 찾은 결과를 반환하기 때문에 결과가 부정확할 수도....


ArrayList 수정

ArrayList<String> arraylist = new ArrayList<String>();

arraylist.set(인덱스번호,"변경할 값");



문자열 비교

등위 연산자(==)

- 같은 자료형을 비교하면 같다(true)

- 기본 자료형과 참조형을 비교하면 다르다(false)


equalsIgnoreCase(), equals()

- 기본 자료형과 참조형을 비교하면 같다(true)

  주소 비교가 아니라 값을 비교한다.

- equals() 는 대소문자를 구분하여 비교한다.

- equalsIgnoreCase()는 대소문자를 구분하지 않고 비교한다.



728x90
블로그 이미지

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 에 올려져 있다.


728x90
블로그 이미지

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 이 달라짐에 따라서 에러,경고 등이 생길 수 있다.

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

728x90
블로그 이미지

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 를 종료했다가 재실행하면 인식이 잘 된다.




728x90
블로그 이미지

Link2Me

,
728x90

연락처(Contacts) ContactHelper Class  메소드를 세부적으로 추가했다.

첨부된 파일의 소스는 한명의 데이터가 분리되어 표기되기 때문에 실제 연락처 소스로 활용하기에는 부족하다. 테스트 용도로는 매우 좋다.

Cursor 쿼리 중심으로 이해하기 쉽게 코드를 수정 보완해서 작성했다.

SQLiteHelper 에서 사용하는 Cursor 와는 개념은 비슷한데 약간 다르다.

아래 코드 중에서 100% 완료 상태가 아닌 테스트 중인 것을 그대로 올린 것도 있으므로 필요하신 분은 수정해서 사용하면 될 것으로 본다.

구글링을 하고 본인의 생각을 담아서 코드를 추가하면 유용하게 사용할 수 있는 좋은 자료라고 본다.


import java.util.ArrayList;

import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
import android.widget.Toast;

public class ContactHelper {

    // Display Name 이 포함된 모든 Cursor 반환
    public static Cursor getContactCursorFromDisplayNameLIKE(ContentResolver contactHelper, String display_name) {

        // 검색할 칼럼 정하기, null 이면 모든 필드
        String[] projection = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
                ContactsContract.CommonDataKinds.Phone.NUMBER };
        // Cursor를 얻기 위한 쿼리 조건
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " like \'%" + display_name + "%\'";
        String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
        Cursor cursor = null;

        try {
            if (display_name != null && !display_name.equals("")) {
                cursor = contactHelper.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, where, null,
                        sortOrder);
            } else {
                cursor = contactHelper.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null,
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");
            }
            cursor.moveToFirst();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return cursor;
    }

    // Display Name 으로 모든 Cursor 반환
    public static Cursor getContactCursorFromDisplayName(ContentResolver contactHelper, String display_name) {

        // 검색할 칼럼 정하기, null 이면 모든 필드
        String[] projection = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
                ContactsContract.CommonDataKinds.Phone.NUMBER };
        // Cursor를 얻기 위한 쿼리 조건
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ='" + display_name + "'";
        String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
        Cursor cursor = null;

        try {
            if (display_name != null && !display_name.equals("")) {
                cursor = contactHelper.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, where, null,
                        sortOrder);
            }
            cursor.moveToFirst();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return cursor;
    }

    public static Cursor getDisplayNameFromNumber(ContentResolver contactHelper, String number) {

        // 참조 https://developer.android.com/reference/android/provider/ContactsContract.PhoneLookup.html
        // Android 2.0 and later
        Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));

        // 검색할 칼럼 정하기, null 이면 모든 필드
        String[] projection = { PhoneLookup._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.NUMBER };

        Cursor cursor = null;

        try {
            if (number != null && !number.equals("")) {
                cursor = contactHelper.query(contactUri, projection, null, null, null);
                if (cursor.getCount() > 0) {
                    while (cursor.moveToNext()) {
                        long rawContactId = cursor.getLong(cursor.getColumnIndex(PhoneLookup._ID));
                        System.out.println("Contact ID from number : " + rawContactId);
                        System.out.println("Contact DISPLAY_NAME : "
                                + cursor.getString(cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME)));
                        System.out.println("Contact NUMBER : " + cursor.getString(cursor.getColumnIndex(PhoneLookup.NUMBER)));
                    }
                    return cursor;
                }

            } else {
                System.out.println("전화번호 검색어가 입력되지 않았습니다 ");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static long getContactIDFromName(ContentResolver contactHelper, String display_name) {
        Uri contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;

        String[] projection = { ContactsContract.Contacts._ID };
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " = '" + display_name + "'";
        String[] whereParams = null; // new String[] {display_name};
        String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
        Cursor cursor = null;

        try {
            cursor = contactHelper.query(contactUri, null, where, whereParams, sortOrder);

            if (cursor.moveToFirst()) {
                System.out.println("ContactID from display_name : "
                        + cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID)));
                return cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
                cursor = null;
            }
        }

        return -1;
    }

    // 전화번호에서 ContactID 획득
    private static long getContactIDFromNumber(ContentResolver contactHelper, String number) {
        Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));

        String[] projection = { PhoneLookup._ID };
        Cursor cursor = null;

        try {
            cursor = contactHelper.query(contactUri, projection, null, null, null);

            if (cursor.moveToFirst()) {
                System.out.println("ContactID from number : " + cursor.getLong(cursor.getColumnIndex(PhoneLookup._ID)));
                return cursor.getLong(cursor.getColumnIndex(PhoneLookup._ID));
            }

            return -1;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
                cursor = null;
            }
        }

        return -1;
    }

    // 연락처 추가 (이름 + 전화번호), 기존 개발자 구현 완료사항
    public static boolean insertContact(ContentResolver contactHelper, String firstName, String mobileNumber) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

        ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, firstName).build());

        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, mobileNumber)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, Phone.TYPE_MOBILE).build());

        try {
            // 연락처 제공자는 applyBatch()에서의 모든 작업을 하나의 트랜잭션으로서 수행
            contactHelper.applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (Exception e) {
            return false;
        }

        return true;
    }

    // 구한 ID 기준으로 연락처 삭제
    // 성명 검색으로 구한 ContactID는 삭제가 안되고, Phone Number 로 구한 ContactID는 삭제가 정상처리됨
    public static void deleteContactFromgetContactID(ContentResolver contactHelper, long getContactId) {
        System.out.println("Contact ID : " + getContactId);
        String where = RawContacts.CONTACT_ID + " = " + getContactId;
        contactHelper.delete(RawContacts.CONTENT_URI, where, null);
    }

    // 전화번호 기준으로 연락처 삭제 (구현 완료)
    public static void deleteContactFromNumber(ContentResolver contactHelper, String number) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        String[] WhereArgs = new String[] { String.valueOf(getContactIDFromNumber(contactHelper, number)) };

        ops.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI)
                .withSelection(RawContacts.CONTACT_ID + "=?", WhereArgs).build());
        try {
            contactHelper.applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        }
    }

    // 성명 기준으로 동일한 성명을 가진 모든 연락처 삭제 (구현 완료)
    public static void deleteAllContactFromName(ContentResolver contactHelper, String display_name) {
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " = '" + display_name + "'";
        contactHelper.delete(RawContacts.CONTENT_URI, where, null);
    }

    // 성명 기준으로 동일한 성명을 가진 모든 연락처 삭제 -- 진행중
    public static void deleteAllContactFromName2(ContentResolver contactHelper, String display_name) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
       
        Cursor cursor = null;
        cursor = getContactCursorFromDisplayName(contactHelper, display_name);
        if (cursor != null && cursor.getCount() > 0) {
            cursor.moveToFirst();
            while (cursor.moveToNext()) {
                String number = cursor.getString(2); // 전화번호 구하기
                // 전화번호 기준으로 ContactID 구하기
                System.out.println("Contact Phone Number : " + number);
                long rawContactId = getContactIDFromNumber(contactHelper, number);
                if (rawContactId > 0) {
                    String where = RawContacts.CONTACT_ID + "=" + rawContactId;
                    //contactHelper.delete(RawContacts.CONTENT_URI, where, null);
                   
                    ops.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI)
                            .withSelection(where, null).build());
                    try {
                        contactHelper.applyBatch(ContactsContract.AUTHORITY, ops);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    } catch (OperationApplicationException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    // 성명 포함 연락처 삭제 (구현 완료)
    public static void deleteContactFromNameLIKE(ContentResolver contactHelper, String display_name) {
        System.out.println("Contact Name Search : " + display_name);
        Uri contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        String[] projection = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.NUMBER }; // 검색할 칼럼 정하기
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " like \'%" + display_name + "%\'";
        String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
        Cursor cursor = null;
        try {
            if (display_name != null && !display_name.equals("")) {
                cursor = contactHelper.query(contactUri, projection, where, null, sortOrder);
                if (cursor.getCount() > 0) {
                    // cursor.moveToFirst();
                    while (cursor.moveToNext()) {
                        long rawContactId = cursor.getLong(cursor.getColumnIndex(Phone._ID));
                        System.out.println("Contact ID from number : " + rawContactId);
                        String phoneNO = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
                        System.out.println("Contact Phone Number : " + phoneNO);
                        deleteContactFromNumber(contactHelper, phoneNO);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 성명 + 전화번호 기준으로 ContactId 구하기 -- 구현중
    public static long getContactIdFromNameAndNumber(ContentResolver contactHelper, String display_name, String number) {
        long rawContactId;
        // 먼저 이름기준으로 Cursor 를 구한 다음에 폰 번호 기준 ID 구한다.
        Uri contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        String[] projection = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.NUMBER }; // 검색할칼럼 정하기
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " = '" + display_name + "'";
        String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
        Cursor cursor = null;
        try {
            if (display_name != null && !display_name.equals("") && number != null && !number.equals("")) {
                cursor = contactHelper.query(contactUri, projection, where, null, sortOrder);
                if (cursor.getCount() > 0) {
                    while (cursor.moveToNext()) {
                        long getContactId = cursor.getLong(cursor.getColumnIndex(Phone._ID));
                        System.out.println("Contact ID from number : " + getContactId);
                        String phoneNO = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
                        System.out.println("Contact Phone Number : " + phoneNO);
                        rawContactId = getContactIDFromName(contactHelper, phoneNO);
                        System.out.println("Contact ID from Phone Number : " + rawContactId);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }

    // 성명 + 전화번호 기준으로 연락처 삭제 (동명이인 고려) -- 진행중
    public static void deleteContactFromNameAndNumber(ContentResolver contactHelper, Context context, String display_name,
            String number) {
        // 로직 개념 : 이름 검색으로 전화번호를 구한 다음에, 전화번호 기준으로 ContactID 를 구하는 순서로 로직 구현
        number = number.replaceAll("[^0-9]", ""); // 숫자만 추출
        // 이름 + 전화번호 조건으로 처리하는 로직이 없는 듯....
        // String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME +
        // " = '" + display_name + "' AND "
        // + ContactsContract.CommonDataKinds.Phone.NUMBER + " = '" + number +
        // "' AND "
        // + ContactsContract.CommonDataKinds.Phone.TYPE + "=" +
        // ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE + "";
        Uri contactUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        String[] projection = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.NUMBER }; // 검색할 칼럼 정하기
        String where = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " = '" + display_name + "'";
        String sortOrder = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC";
        Cursor cursor = null;
        try {
            if (display_name != null && !display_name.equals("") && number != null && !number.equals("")) {
                cursor = contactHelper.query(contactUri, projection, where, null, sortOrder);
                if (cursor.getCount() > 0) {
                    // cursor.moveToFirst();
                    while (cursor.moveToNext()) {
                        long getContactId = cursor.getLong(cursor.getColumnIndex(Phone._ID));
                        String phoneNO = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
                        System.out.println("Contact Phone Number : " + phoneNO);
                        long rawContactId = getContactIDFromNumber(contactHelper, phoneNO);
                        System.out.println("Contact ID from Number : " + rawContactId);
                        deleteContactFromNumber(contactHelper, phoneNO);
                        Toast.makeText(context, "getIDFromName : " + getContactId + " getIDFromNumber : " + rawContactId,
                                Toast.LENGTH_LONG).show();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 모든 연락처 삭제(구현완료)
    public static void deleteAllContact(ContentResolver contactHelper) {
        contactHelper.delete(RawContacts.CONTENT_URI, null, null);
    }

}



MainActivity.java 에서 사용법 예제

예제라고 할 순 없고 테스트 중인 것을 적었다.

public class MainActivity extends ListActivity {

    private ArrayList<String> contactID;
    private ArrayList<String> contactNames;
    private ArrayList<String> contactNumbers;
    ArrayAdapter<String> adapter;
    private Cursor cursor;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button btn_Test1 = (Button) findViewById(R.id.btn_CustomDelete1);
        Button btn_Test2 = (Button) findViewById(R.id.btn_CustomDelete2);

        contactID = new ArrayList<String>();
        contactNames = new ArrayList<String>();
        contactNumbers = new ArrayList<String>();

        cursor = ContactHelper.getContactCursorFromDisplayNameLIKE(getContentResolver(), "");
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            contactID.add(cursor.getString(0));
            contactNames.add(cursor.getString(1));
            contactNumbers.add(cursor.getString(2));
            cursor.moveToNext();
        }

        adapter = new MyAdapter(this, android.R.layout.simple_list_item_1, R.id.tvNameMain, contactNames);
        setListAdapter(adapter);

        // 추가 테스트
        // Cursor cursor1 =
        // ContactHelper.getDisplayNameFromNumber(getContentResolver(), "010-1234-0401");
        // System.out.println("Contact Cursor Return !!");
        // if (cursor1 != null) {
        // cursor1.moveToFirst();
        // while (!cursor1.isAfterLast()) {
        // System.out.println("ContactId =" + cursor1.getString(0) + " 성명: " +
        // cursor1.getString(1) + " 휴대폰번호:"
        // + cursor1.getString(2));
        // cursor1.moveToNext();
        // }
        // }

        btn_Test1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 성명 포함 삭제 테스트
                // 이름 검색으로 구한 ContactID 로는 삭제가 안되나, Number 로 구한 ContactID로는 삭제가
                // 됨
                // ContactHelper.deleteContactFromNameLIKE(getContentResolver(),
                // "개발");
                // ContactHelper.deleteContactFromgetContactID(getContentResolver(),53360);
                ContactHelper.deleteContactFromgetContactID(getContentResolver(), 10674);
                adapter.notifyDataSetChanged();
                // 현재 Activity 갱신처리
                finish();
                startActivity(getIntent());
            }
        });

        btn_Test2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // 전화번호 삭제 테스트
                        // ContactHelper.deleteContactFromNameAndNumber(getContentResolver(), MainActivity.this, "테스트", "010-1212-7710");
                        ContactHelper.deleteAllContactFromName(getContentResolver(), "개발자");
                    }
                });

                // 현재 Activity 갱신처리
                finish();
                startActivity(getIntent());

            }
        });

    }

    private class MyAdapter extends ArrayAdapter<String> {

        public MyAdapter(Context context, int resource, int textViewResourceId, ArrayList<String> conNames) {
            super(context, resource, textViewResourceId, conNames);

        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            View row = setList(position, parent);
            return row;
        }

        private View setList(int position, ViewGroup parent) {
            LayoutInflater inf = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            View row = inf.inflate(R.layout.liststyle, parent, false);

            TextView tvID = (TextView) row.findViewById(R.id.tvContactID);
            TextView tvName = (TextView) row.findViewById(R.id.tvNameMain);
            TextView tvNumber = (TextView) row.findViewById(R.id.tvNumberMain);

            Button btn_Add = (Button) row.findViewById(R.id.btn_AddContact);
            Button btn_Del = (Button) row.findViewById(R.id.btn_deleteContact);

            tvID.setText("ContactID : " + contactID.get(position));
            tvName.setText(contactNames.get(position));
            tvNumber.setText("No: " + contactNumbers.get(position));

            btn_Add.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, AddContact.class);
                    startActivity(intent);
                }
            });

            btn_Del.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, DeleteContacts.class);
                    startActivity(intent);
                }
            });

            return row;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater imf = getMenuInflater();
        imf.inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.item1) {
            Intent intent = new Intent(MainActivity.this, AddContact.class);
            startActivity(intent);
        } else if (item.getItemId() == R.id.item2) {
            Intent intent = new Intent(MainActivity.this, DeleteContacts.class);
            startActivity(intent);
        }
        return super.onOptionsItemSelected(item);
    }
}
 


테스트에 사용한 파일

본 파일은 구글링으로 구한 파일을 가지고 이해하기 쉽게 코드를 상당히 수정하고, 연락처 삭제 중심으로 코드를 추가 구현했다.

테스트 환경 : Eclipse

안드로이드 스튜디오에서 테스트하려면 코드가 복잡한 것이 없으므로 변환하면 될 것으로 본다.

Android-Contacts.zip


Android Studio 버전으로 수정한 코드는 http://link2me.tistory.com/1305 에 올려져 있다.

보완해서 테스트 하고 있는 중이라 약간 더 코드가 수정되었다.

728x90
블로그 이미지

Link2Me

,
728x90

Android Contacts 를 검색하고 동일 정보가 있으면 삭제하고 신규로 데이터를 저장하는 로직이 있어서 아무 생각없이 잘 될 것이라는 믿음(?)으로 소스를 사용하다가 주소록을 2,000 개 이상 동시 저장하는 걸 테스트 하는데 너무 느려서 구글링으로 예제 소스 하나 받아서 테스트를 해보니 동일한 자료가 엄청나게 저장되어 있는 걸 알았다.

삼성폰이 기본 제공하는 연락처 앱에서는 데이터가 딱 저장한 개수만큼만 보인다.

데이터 삭제가 제대로 되는지 확인해봤더니 삭제가 안되고 있더라.

계속 삽질을 하다가 Easy Contacts Delete 라는 어플을 받아서 전체를 선택하고 삭제를 했더니 모든 데이터가 삭제되었다.


연락처(Contacts) 에 대한 기본 개념이 부족해서 해결이 제대로 안되는 것이라 집중적으로 관련 자료를 검색하고 책을 보면서 정리하고 테스트 해보고 있다.

Display_Name 으로 검색한 ContactID 와 Phone Number 로 검색한 ContactID 를 정확하게 구현해주어야 한다는 걸 테스트를 통해서 알게되었고, Phone Number 기준으로 얻은 ContactID 로 삭제가 잘 된다는 걸 알았다.


도전의 절반은 스키마를 마스터하고, 각 테이블이 다른 테이블과 어떻게 상호 작용하는지를 이해하는데 있다.


주소록 DB는 /data/data/com.android.providers.contacts/databases/contacts2.db 에 있는데 접근이 불가능하다.

contacts database는 Provider URI 를 통해서 접근할 수 있다.

Stackoverflow 보면 https://developer.android.com/guide/topics/providers/contacts-provider.html 사이트 참조하라고 나온다.


Contacts API 2.0 에서는, 연락처(Contacts)은 다음 세 테이블로 구성되어 있다.


  ContactsContract.Contacts table

 - 고유한 사용자(people) 정보를 표시하는 테이블

 - https://developer.android.com/reference/android/provider/ContactsContract.Contacts.html 칼럼 확인


  ContactsContract.RawContacts table

 - user account and type 을 기준으로, 한 사용자 정보 요약을 포함하는 테이블

 - https://developer.android.com/reference/android/provider/ContactsContract.RawContacts.html 에서 칼럼 확인

 - ContactsContract.RawContacts.CONTACT_ID = Contacts._ID 값과 일치


  ContactsContract.Data table

 - E-Mail 주소나 휴대폰번호 같은 RawContacts 테이블의 세부 정보를 포함하는 테이블
 - ContactsContract.Data.RAW_CONTACT_ID = ContactsContract.RawContacts._ID 값과 일치

 - https://developer.android.com/reference/android/provider/ContactsContract.Data.html 에서 칼럼 확인

 - 15개의 일반 열 중에서 DATA1부터 DATA15까지는 일반적으로 이용할 수 있고 이외에 추가로 마련된 네 개의 일반 열, 즉 SYNC1부터 SYNC4까지는 동기화 어댑터 전용이다.

 - 표시 이름, 전화 번호, 이메일, 우편 주소, 사진 및 웹사이트 세부 정보 행은 모두 ContactsContract.Data 테이블에서 찾을 수 있다.


모든 테이블에는 테이블명._ID 가 있다.


위 도식도에 나온 ID 칼럼이 서로 어떻게 연관되어 있는지를 아는것이 가장 중요하다.

데이터를 수정(Update)하거나 삭제(Delete)할 때 정확하게 정보를 업데이트할 수 있다.

즉 rawContactId 값을 구하는 걸 잘못 구하면 데이터 수정/삭제가 제대로 안된다는 것이다.

이 개념 이해하느라고 정말 많은 시간을 할애하였다.

구글링해도 이런 내용은 나오지 않고 그냥 소스 코드 예제만 좀 나온다.

검색 키워드 찾는 방법을 잘못하니까 예제 찾기도 쉽지 않더라.


원시 연락처의 이름은 ContactsContract.RawContacts에 있는 자신의 행에 저장되지 않고,

ContactsContract.CommonDataKinds.StructuredName 행에 있는 ContactsContract.Data 테이블에 저장된다.
원시 연락처의 데이터는 원시 연락처의 _ID 값과 연결된ContactsContract.Data 행에 저장된다.
하나의 원시 연락처에 같은 유형 데이터의 인스턴스가 여러 개 있을 수 있다.
ㅇ MIMETYPE
  - 사용자 지정 MIME 유형으로 표현된다.
  - 연락처 제공자는 ContactsContract.CommonDataKinds의 서브클래스에서 정의된 MIME 유형을 사용한다.

ㅇ 연락처 제공자는 기존 연락처 어느 것과도 일치하지 않는 새로운 원시 연락처가 추가되면 새로운 연락처를 생성한다.
  애플리케이션이나 동기화 어댑터가 기존 연락처와 일치하는 새로운 원시 연락처를 생성하면, 새로운 원시 연락처는 기존 연락처에 통합된다.
ㅇ 연락처 제공자가 연락처를 자동으로 관리하므로, 통합이나 동기화에 응답하여 연락처 행의 _ID 값을 변경할 수도 있다.
ㅇ 사용자 프로필에 액세스하기 위한 상수는 ContactsContract.Profile 클래스에서 이용할 수 있다.
  - 사용자 프로필에 액세스하려면 특수 권한이 필요하다.
  - android.Manifest.permission#READ_PROFILE과 android.Manifest.permission#WRITE_PROFILE 권한이 필요
  - 권한 android.Manifest.permission#READ_PROFILE을 사용하면 개발자가 기기 사용자의 개인 식별 데이터에 액세스할 수 있게 해준다.



참고

특정 블로그에서만 찾은 자료가 아닌지라 출처는 생각나는 것만 남긴다.

http://www.javased.com/index.php?source_dir=android-client_1/src/com/googlecode/asmack/contacts/ContactDataMapper.java

안드로이드 연락처 초성검색 소스 : http://www.androidpub.com/45681

http://blog.mready.net/2012/09/android-contacts/ 에 나온 예제를 가지고 ContactHelper.java 클래스 파일을 만드는데 활용하면 좋을 거 같다.

728x90
블로그 이미지

Link2Me

,
728x90

반응이 너무 느려서 서버에서 데이터를 가져오는데 걸리는 시간을 측정하는 코드를 추가했다.

0.5 ~ 0.6초 정도 걸리는데 Local 에서 가져온 데이터를 처리하는 로직 문제인지 너무 오래 걸린다.


long start, end; // 서버에서 가져오는 시간 측정
start = System.currentTimeMillis();
end = System.currentTimeMillis();
System.out.println("get JSONData Time :" + (end - start)/1000.0);

728x90
블로그 이미지

Link2Me

,
728x90

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> //위치정보 확인함
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/> //위치정보 확인함
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> //wifi 연결을 확인함
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> //wifi 체인지를 확인함
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> //네트웍이 연결된것을 확인할수 있게함
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> //부팅완료를 확인할수있게함
<uses-permission android:name="android.permission.INTERNET"/> // 인터넷을 사용함
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> // 외장메모리 사용
<uses-permission android:name="android.permission.RECODER_AUDIO"/> //녹음이 가능하게 함
ACCESS_CHECKIN_PROPERTIES      체크인데이터베이스의_속성테이블로_액세스
ACCESS_COARSE_LOCATION         코스_로케이션_액세스_(Cell-ID/WiFi)
ACCESS_FINE_LOCATION           파인로케이션_액세스(GPS)
ACCESS_LOCATION_EXTRA_COMMANDS 로케이션_옵션_커맨드_액세스
ACCESS_MOCK_LOCATION           목_로케이션_프로바이더_생성_(테스트용)
ACCESS_NETWORK_STATE           네트워크_상태_접근
ACCESS_SURFACE_FLINGER         서피스_플링거_접근
ACCESS_WIFI_STATE              WiFi상태_접근
ADD_SYSTEM_SERVICE             시스템서비스_추가
BATTERY_STATS                  배터리_상태
BLUETOOTH                      블루투스
BLUETOOTH_ADMIN                블루투스_어드민
BRICK                          디바이스_실효성_지정
BROADCAST_PACKAGE_REMOVED      제거된_패키지에_대한_notification_브로드캐스트
BROADCAST_SMS                  SMS에_대한_브로드캐스트
BROADCAST_STICKY               인텐트_브로드캐스트
CALL_PHONE                     통화
CALL_PRIVILEGED                통화(긴급전화_포함)
CAMERA                         카메라
CHANGE_COMPONENT_ENABLED_STATE 컴포넌트의_실효성_변경
CHANGE_CONFIGURATION           컨피그_변경
CHANGE_NETWORK_STATE           통신상태_변경
CHANGE_WIFI_STATE              WiFi상태_변경
CLEAR_APP_CACHE                어플리케이션_캐시_클리어
CLEAR_APP_USER_DATA            어플리케이션의_유저데이터_클리어
CONTROL_LOCATION_UPDATES       위치정보_갱신
DELETE_CACHE_FILES             캐시파일_제거
DELETE_PACKAGES                패키지_제거
DEVICE_POWER                   전원상태에_대한_로우레벨_접근
DIAGNOSTIC                     진단리소스_읽고쓰기
DISABLE_KEYGUARD               키_가드_끄기_DUMP_덤?
EXPAND_STATUS_BAR              상태표시줄_확장
FACTORY_TEST                   팩토리_테스트
FLASHLIGHT                     플래시라이트
FORCE_BACK                     포스백
GET_ACCOUNTS                   어카운트_획득
GET_PACKAGE_SIZE               패키지_획득
GET_TASKS                      태스크_획득
HARDWARE_TEST                  하드웨어테스트
INJECT_EVENTS                  유저이벤트_키/트랙볼
INSTALL_PACKAGES               패키지_인스톨
INTERNAL_SYSTEM_WINDOW         내부_시스템윈도_활용
INTERNET                       인터넷
MANAGE_APP_TOKENS              어플리케이션_토큰관리
MASTER_CLEAR                   마스터_클리어
MODIFY_AUDIO_SETTINGS          오디오설정_편집
MODIFY_PHONE_STATE             전화상태_편집
MOUNT_UNMOUNT_FILESYSTEMS      파일시스템_편집
PERSISTENT_ACTIVITY            액티비티_지속
PROCESS_OUTGOING_CALLS         전화_발신처리_접근
READ_CALENDAR                  캘린더_읽어오기
READ_CONTACTS                  주소록_읽어오기
READ_FRAME_BUFFER              프레임버퍼_읽어오기
READ_INPUT_STATE               입력상태_읽어오기
READ_LOGS                      로그_읽어오기
READ_OWNER_DATA                owner_data읽어오기
READ_PHONE_STATE               통화상태_읽어오기_READ_SMS_SMS읽어오기
READ_SYNC_SETTINGS             동기설정_읽어오기
READ_SYNC_STATS                동기상태_읽어오기
REBOOT                         reboot
RECEIVE_BOOT_COMPLETED         boot완료
RECEIVE_MMS                    MMS수신
RECEIVE_SMS                    SMS수신
RECEIVE_WAP_PUSH               WAP수신
RECORD_AUDIO                   오디오_수신
REORDER_TASKS                  태스크_Z오더
RESTART_PACKAGES               패키지_리스타트
SEND_SMS                       SMS송신
SET_ACTIVITY_WATCHER           액티비티_왓쳐지정
SET_ALWAYS_FINISH              액티비티_전체_종료
SET_ANIMATION_SCALE            스케일_애니메이션_지정
SET_DEBUG_APP                  디버그어플리케이션_지정
SET_ORIENTATION                스크린_로테이션지정
SET_PREFERRED_APPLICATIONS     자주_사용하는_어플리케이션_지정
SET_PROCESS_FOREGROUND         포어그라운드_처리지정
SET_PROCESS_LIMIT              제한처리_지정
SET_TIME_ZONE                  타임존_지정
SET_WALLPAPER                  배경화면_지정
SET_WALLPAPER_HINTS            배경화면_힌트_지정
SIGNAL_PERSISTENT_PROCESSES    지속처리_시그널_지정
STATUS_BAR                     상태표시줄_지정
SUBSCRIBED_FEEDS_READ          서브스트립드_피즈_읽어오기
SUBSCRIBED_FEEDS_WRITE         서브스트립드_피즈_쓰기
SYSTEM_ALERT_WINDOW            알림_윈도우
VIBRATE                        진동
WAKE_LOCK                      알람
WRITE_APN_SETTINGS             APN설정_쓰기
WRITE_CALENDAR                 캘린더_쓰기
WRITE_CONTACTS                 주소록_쓰기
WRITE_GSERVICES                G서비스_쓰기
WRITE_OWNER_DATA               owner_data쓰기
WRITE_SETTINGS                 설정_쓰기
WRITE_SMS                      SMS쓰기
WRITE_SYNC_SETTINGS            동기설정_쓰기


출처 : http://iyeti.kr/673

728x90
블로그 이미지

Link2Me

,
728x90

안드로이드 SQLite DB 관련으로 알아두어야 할 사항이다.


rawQuery 를 이용하는 방법과 query를 이용하는 방법의 차이점이다.


rawQuery() Example

rawQuery() 메소드에 SELECT 구문을 넘겨 주기만 하면 된다.

인자로 idx 를 받아서 Where 조건문으로 일치하는 데이터 칼럼을 전부 가져오는 코드다.


 SQLiteDatabase db = getReadableDatabase(); // 읽기 가능한 데이터베이스

 Cursor cursor = db.rawQuery("select * from myTable where _idx = ?", new String[] { idx });

 SQLiteDatabase db = getReadableDatabase(); // 읽기 가능한 데이터베이스

 String sql = "SELECT idx, id, pw, name FROM member";

 Cursor cursor = db.rawQuery(sql, null);


query() Example

query() 메소드는 SELECT 구문의 각 부분을 쪼개 각 인자로 넘겨받고, 최종적으로는 SELECT문을 생성해 실행한다.

 Cursor cursor = db.query(DB_TABLE,
    new String[] { KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION },
    null, null, null, null, null);

 query() 가 받아서 처리하는 인자의 순서
 1. 대상 테이블 이름(DB_TABLE)
 2. 값을 가져올 컬럼 이름의 배열
    String[] columns ={
KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION};
 3. WHERE 구문. 물음표를 사용해 인자의 위치를 지정할 수 있다.

     "name=?"
 4. WHERE 구문에 들어가는 인자값

     String[] params ={"홍길동"};
 5. GROUP BY 구문
 6. ORDER BY 구문
 7. HAVING 구문
 테이블 이름을 제외한 각 값이 필요없는 경우라면 null을 지정한다.

 db.query(DBTable,columns,whereclause,whereargs,null,null,null);

 String query =
    "SELECT column1, (SELECT max(column1) FROM table1) AS max FROM table1 " +
    "WHERE column1 = ? OR column1 = ? ORDER BY column1";
 sqLiteDatabase.rawQuery(query, whereArgs);




SQLiteOpenHelper 클래스에서 제공하는 ContentVales 객체를 이용하여 DB를 update 하는 방법이다.

public void update(Integer idx, String name, String mobileNO, String officeNO) {
    SQLiteDatabase db = this.getWritableDatabase();
    ContentValues cv = new ContentValues();
    cv.put(Column_Name, name);
    cv.put(Column_mobileNO, mobileNO);
    cv.put(Column_officeNO, officeNO);
    db.update(TABLE_NAME, cv, "idx = ? ", new String[]{Integer.toString(idx)});
}


이 코드를 표준 SQL 문으로 입력한다면 아래와 같다.

public void update(Integer idx, String name, String mobileNO, String officeNO) {
    SQLiteDatabase db = this.getWritableDatabase();
    String sql ="Update "+TABLE_NAME+" SET name='"+name+"' mobileNO='"+mobileNO+"' officeNO='"+officeNO+"' where idx="+idx+"";
    db.execSQL(sql);
}



728x90
블로그 이미지

Link2Me

,
728x90

컴파일 하려고 했더니 에러가 발생한다.

Error running app:
Instant Run requires 'Tools | Android | Enable ADB integration' to be enabled.



해결방법

Enable your ADB Integration. Go to Tools --> Android --> set checked in Enable ADB Integration



728x90
블로그 이미지

Link2Me

,
728x90

Eclipse 에서 소스코드를 자동정렬 해주는 단축키는 Ctrl + Shift + F 다.

문제는 자동정렬의 width = 80 으로 고정시켜 코드를 자동정렬 시켜버린다.


width = 130 정도로 수정했더니 23인치 모니터 화면에 적정하게 보인다.

너비를 커스터마이징하는 방법이다.



이렇게 설정하고 나서, Ctrl + Shift + F 를 누르면 강제로 줄바꿈했던 코드를 한줄로 합쳐서 표시해준다.

728x90
블로그 이미지

Link2Me

,
728x90

Android Studio SQLite DB에 대해 다뤄보자.


안드로이드에서 데이터를 저장하는 방법은 다음과 같은 것들이 있다.

SharedPreference

 key - value 으로 저장하여 모든 Intent에서 활용 가능

 주로 서버 로그인후 idx, userID 등의 정보를 저장하여 활용한다.

 앱이 삭제되면 저장된 정보는 삭제된다.

 Internal Storage

 안드로이드는 리눅스 운영체제를 수정하여 만들었으며,

 /data/data/어플리케이션패키지명/files 디렉토리에 저장한다.

 앱이 삭제되면 어플리케이션패키지명이 삭제되므로 이 공간에 저장된 파일들도

 삭제된다.

 External Storage

 외부저장공간은 탈착이 가능한 SD카드다.

 안드로이드 장치는 외부 저장 공간을 지원하므로

 외부 저장공간에 파일을 저장할 수 있다.

 누구나 읽을 수 있고 사용자에의해 변경될 수 있다.

 SQLite 데이터베이스

 구조화된 데이터를 DB에 저장한다.

 SQLite는 초경량 데이터베이스로 데이터를 디스크 파일에 저장한다.

 데이터 정의 명령어는 테이블을 생성/변경/삭제한다.

 데이터 조작 명령어는 데이터를 조회/추가/삭제/수정한다.


Cursor 사용

Cursor 인터페이스는 데이터베이스 쿼리에 의해서 반환된 result 셋에 임시적으로 데이터를 읽고 쓰기 위해서 액세스할 수 있는 기능을 제공하는 인터페이스다.

인자

내용 설명

 moveToFirst

커서가 쿼리(질의) 결과 레코드들 중에서 가장 처음에 위치한 레코드를 가리키도록 한다.

 moveToLast

 마지막 행으로 이동

 moveToNext

 다음 레코드로 커서를 이동

 moveToPrevious

 이전 레코드로 커서를 이동

 moveToPosition(int position)

 파라미터로 주어진 절대 행 위치로 이동한다. 성공적이면 true 반환

 getCount

 질의 결과값(레코드)의 갯수를 반환, 조건이 없으면 총개수 반환

 getColumnCount

 총 칼럼 개수를 반환

 getColumnIndex

 해당 이름의 칼럼의 0 베이스 칼럼 인덱스 값을 반환해준다.

 해당 이름의 칼럼이 존재하지 않으면 -1을 반환한다.

 getColumnIndexOrThrow

 해당 이름을 가지고 있는 칼럼의 0 베이스 인덱스 번호를 반환한다.
 해당 이름의 칼럼이 존재하지 않으면 IllegalArgumentException 을 발생시킨다.

 getColumnName

 특정 인덱스값에 해당하는 필드 이름을 반환

 getColumnNames

 칼럼의 이름들을 String(문자열) 배열 형태로 반환

 moveToPosition

 커서를 특정 레코드로 이동시킨다

 getPosition

 커서가 현재 가리키고 있는 위치를 반환

 getDouble

 해당 인덱스의 칼럼 값을 double 형으로 반환한다.

 close()

 열려있는 커서를 닫는다.


SQLite 데이터베이스의 특성상 하나의 테이블의 레코드를 읽어 오기 위해서는 커서(cursor)라는 것이 필요하다.

조건에 맞는 레코드를 한꺼번에 모두 가져올 수 없기 때문에 커서(cursor)를 이용해서 조작을 한다.

커서(cursor)는 현재 레코드를 가리킨다. 하나씩 이 커서(cursor)를 이동하면서 레코드 하나하나씩을 접근해서 가져온다. 이 커서 객체를 이용하여 get을 하게 되면 컬럼 번호에 맞게 데이터를 가져올 수 있다.

커서는 정방향이나 역방향으로 움직일 수 있다.


SQLite 는 구글 안드로이드 운영체제에 기본 탑재된 데이터베이스로 비교적 가벼운 데이터베이스다.


SQL 기본 문법은 http://www.w3schools.com/sql/default.asp 에서 익히면 된다.

SQLite 는 완전히 독립적이고 서버 기능을 제공하지 않으며, 트랜잭션을 지원하며 쿼리를 수행할 수 있는 표준 SQL 언어를 사용한다.

SQLite 만의 문법사항을 알아야만 편하다. 간단하게 SQLite 의 문법 사항을 알아보자.

ㅇ Data Type : Null, Integer(정수), Real(실수), Text(문자열), Blob

    - Integer : 부호있는 정수, 실제 값에 따라 1byte ~ 8bytes까지 가변적으로 저장됨

    - Boolean values are stored as integers 0 (false) and 1 (true).

    - Date and Time 데이터 타입은 없다. datetime은 입력되는 값에 따라 Text, Real, Integer 타입으로 저장됨

ㅇ JOIN : Left Outer Join, INNER Join 지원

ㅇ Rename Table, Add Column 지원 (Drop Column, Alter Column, Add Constraint 미지원)

    - 한번에 여러 Column 추가 안된다.

    - Join Update 를 지원하지 않는다.

ㅇ Grant and Revoke : 별도로 권한 부여 기능이 없고, OS 파일 시스템 권한을 사용


SQLite 에 대한 자세한 설명은 http://overoid.tistory.com/19 에 잘 설명되어 있다.

영문 원본을 읽어보려면 http://www.sqlite.org/datatype3.html 를 보면 된다.


http://www.androidhive.info/2011/11/android-sqlite-database-tutorial/ 에 좋은 예제가 있다.



초기 데이터가 많은 경우 DB 파일을 assets 에 저장해두고 copy 해서 SQLite 저장하는 것은 해보지 않았다.

서버 데이터를 가져오는 케이스에 대한 로직을 그려본다면 ...

서버에 있는 자료를 읽어서 SQLite DB에 저장하고, SQLite DB에 저장된 데이터를 읽어서 ArrayList에 저장하고 ListView 또는 RecyclerView 를 통해 보여주거나, 폰에 주소록을 저장할 수도 있다.

안드로이드에서 데이터베이스를 사용하려면

- SQLiteOpenHelper 를 사용하는 방법

- openOnCreateDatabase() 메소드로 데이터베이스 객체를 직접 생성하는 방법

둘 중 하나를 선택해야 한다.


안드로이드에 내장되어 있는 SQLite을 사용하기 위해서는 SQLiteOpenHelper 클래스를 이용해야 한다.
SQLiteOpenHelper 클래스는 데이터베이스를 생성하고 버전을 관리 할 수 있도록 도와주는 클래스이다.

SQLiteOpenHelper 클래스를 이용하면 SQL 기본 사용법을 알면 쉽게 처리할 수가 있다.


SQLiteOpenHelper 를 사용하는 방법으로 코드를 구현해 보자.

 === SQLiteDBHandler ===

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class SQLiteDBHandler extends SQLiteOpenHelper {
    public static final String TAG = "DBHelper";

    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "mCRM.db";
    public static final String TABLE_NAME = "PBbook";

    public static final String Column_Uid = "uid";
    public static final String Column_Name = "name";
    public static final String Column_mobileNO ="mobileNO";
    public static final String Column_officeNO ="officeNO";
    public static final String Column_profile_image = "profile_image";

    public SQLiteDBHandler(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String Member_Create = "create table "+ TABLE_NAME + " (" +
                "idx INTEGER PRIMARY KEY AUTOINCREMENT,"+
                "uid INTEGER not null," +
                "name TEXT not null," +
                "mobileNO TEXT not null," +
                "officeNO TEXT," +
                "profile_image TEXT);";
        db.execSQL(Member_Create);
        Log.v(TAG,"DB Created");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.w("LOG_TAG", "Upgrading database from version " + oldVersion + " to " + newVersion
                + ", which will destroy all old data");
        // KILL PREVIOUS TABLES IF UPGRADED
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
        onCreate(db); //새로 생성하기
        Log.v(TAG,"DB Updated");
    }

    public void InsertData(String uid,String name, String mobileNO, String officeNO,String profile_image) {
        SQLiteDatabase db = getWritableDatabase(); // 쓰기 가능한 데이터베이스를 가져와 입력

        // 이름 + 휴대폰번호 기준으로 중복 체크
        String query = "select idx from " + TABLE_NAME
                + " where " + Column_Name + "= '"+ name +"' and " + Column_mobileNO + "= '" + mobileNO + "'";
        Cursor cursor = db.rawQuery(query, null);
        cursor.moveToFirst(); // Cursor를 제일 첫행으로 이동
        if( cursor.getCount() == 0) {  // 중복이 없으면 저장하라.
            ContentValues cv = new ContentValues(); // 객체 생성
            cv.put(Column_Uid, uid);
            cv.put(Column_Name, name);
            cv.put(Column_mobileNO, mobileNO);
            cv.put(Column_officeNO, officeNO);
            cv.put(Column_profile_image, profile_image);
            db.beginTransaction();  // 대량건수 데이터 입력 처리를 고려
            try{
                long rowId = db.insert(TABLE_NAME, null, cv);
                if(rowId < 0){
                    throw new SQLException("Fail to Insert");
                }
                db.setTransactionSuccessful();
            } catch(Exception e){
                Log.i(TAG,e.toString());
            } finally {
                db.endTransaction();
                Log.v(TAG,"DB Inserted " + name + " uid =" + uid);
            }
        }
        cursor.close();
        db.close();
    }

    /* Get the first row Column_ID from the table */
    public int getFirstId() {
        int idToUpdate = 0;
        String query = "select idx from " + TABLE_NAME + " LIMIT 1";

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor res = db.rawQuery(query, null);

        if (null != res && res.getCount() > 0) {
            res.moveToFirst(); // Cursor를 제일 첫행으로 이동
            idToUpdate = res.getInt(0);
        }
        return idToUpdate;
    }

    /* Update the table row with Column_ID - id */
    public boolean updateDB(Integer idx, String name, String mobileNO, String officeNO) {
        Log.i(TAG, "Updating Column_ID : " + idx);
        ContentValues cv = new ContentValues();
        cv.put(Column_Name, name);
        cv.put(Column_mobileNO, mobileNO);
        cv.put(Column_officeNO, officeNO);

        SQLiteDatabase db = this.getWritableDatabase();
        db.update(TABLE_NAME, cv, "idx = ? ", new String[]{Integer.toString(idx)});
        return true;
    }

    /* Delete the row with Column_ID - id from the employees table */
    public Integer deleteRow(Integer idx) {
        SQLiteDatabase db = this.getWritableDatabase();
        return db.delete(TABLE_NAME, "idx = ? ", new String[]{Integer.toString(idx)});
    }

    public int getTableRowCount() {
        String countQuery = "SELECT * FROM " + TABLE_NAME;
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(countQuery, null);
        Log.i(TAG, "Total Row : " + cursor.getCount());
        cursor.close();
        return cursor.getCount();
    }

    // Getting All Contacts
    public List<Address_Item> getAllPBook() {
        List<Address_Item> pbookData = new ArrayList<Address_Item>();
        // Select All Query
        String selectQuery = "SELECT  * FROM " + TABLE_NAME;

        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);

        // looping through all rows and adding to list
        if (cursor.moveToFirst() && cursor.getCount() > 0) {
            do {
                Address_Item contact = new Address_Item();
                contact.setUid(cursor.getString(1));
                contact.setName(cursor.getString(2));
                contact.setMobileNO(cursor.getString(3));
                contact.setOfficeNO(cursor.getString(4));
                contact.setProfile_image(cursor.getString(5));
                // Adding contact to list
                pbookData.add(contact); // DB에서 받아온 값을 ArrayList에 Add
            } while (cursor.moveToNext());
        }
        cursor.close();
        return pbookData; // return contact list
    }
}

 === Address_Item.java ===

 public class Address_Item {
    // PersonData 정보를 담고 있는 객체 생성
    private String profile_image; // 이미지 경로를 String으로 받기 위해서
    private String uid;
    private String name;
    private String mobileNO;
    private String officeNO;
    boolean checkBoxState;

    public Address_Item() {
    }

    public Address_Item(String profile_image, String uid, String name, String mobileNO, String officeNO, boolean checkBoxState) {
        this.profile_image = profile_image;
        this.uid = uid;
        this.name = name;
        this.mobileNO = mobileNO;
        this.officeNO = officeNO;
        this.checkBoxState = checkBoxState;
    }

    public String getProfile_image() {
        return profile_image;
    }

    public void setProfile_image(String profile_image) {
        this.profile_image = profile_image;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobileNO() {
        return mobileNO;
    }

    public void setMobileNO(String mobileNO) {
        this.mobileNO = mobileNO;
    }

    public String getOfficeNO() {
        return officeNO;
    }

    public void setOfficeNO(String officeNO) {
        this.officeNO = officeNO;
    }

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

    public void setCheckBoxState(boolean checkBoxState) {
        this.checkBoxState = checkBoxState;
    }
}


MainActivity.java 파일에서 구현할 코드는 다음과 같다.


 안드로이드 운영 체제에 기본 탑재된 데이터베이스

출처: http://cocomo.tistory.com/409 [Cocomo Coding]
구글 안드로이드 운영 체제에 기본 탑재된 데이터베이스

출처: http://cocomo.tistory.com/409 [Cocomo Coding]

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    SQLiteDBHandler sqLiteDBHandler;
    public SharedPreferences settings;
    ProgressDialog mProgressDialog;

    TextView textView;
    Context context;

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

        textView = (TextView) findViewById(R.id.sqlite_txt);

        sqLiteDBHandler = new SQLiteDBHandler(MainActivity.this);
        // 서버에서 데이터를 가져와서 DB에 insert
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("idx", settings.getString("idx",""));
        String postParams = builder.build().getEncodedQuery();
        new getOrgChartData().execute(Value.IPADDRESS + "/get_json.php",postParams);

        // 저장버튼을 클릭하면 SQLite DB에 저장된 데이터를 폰의 주소록에 저장하는 기능
        Button btn_contactSave = (Button) findViewById(R.id.btn_sqlite_save);
        btn_contactSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<Address_Item> contacts = sqLiteDBHandler.getAllPBook();
                int num = 0;
                for(Address_Item item : contacts){
                    num++;
                    int rawContactId = 0;
                    Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                    ContentResolver cr = getContentResolver();
                    String strContactName = item.getName();
                    String strMobileNO = item.getMobileNO();
                    String strofficeNO = item.getOfficeNO();
                    String strEmail = "";
                    String strPhoto = "";
                    if (item.getProfile_image().length() > 0) {
                        strPhoto = Value.IPADDRESS + "/photos/" + item.getProfile_image();
                    }
                    rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName);
                    if (rawContactId > 0) {
                        // 기존 전화번호가 존재하면 삭제하고 새로 입력
                        phonebook.ContactsIDdelete(cr, context, rawContactId);
                    }
                    phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                }
                Toast.makeText(getApplicationContext(), "총" + num + "개 연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
            }
        });

    }

    class getOrgChartData extends AsyncTask<String, Void, String> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // Create a progressdialog
            mProgressDialog = new ProgressDialog(MainActivity.this);
            mProgressDialog.setTitle("Personal Profile JSON Parse");
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setIndeterminate(false);
            mProgressDialog.show();  // Show progressdialog
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            searchJSON=result;
            showJSONObjectList();
            mProgressDialog.dismiss();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS="result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "userNM";
    private static final String TAG_MobileNO ="mobileNO";
    private static final String TAG_OfficeNO ="telNO";
    private static final String TAG_Image = "photo"; // 이미지 필드
    JSONArray peoples = null;

    protected void showJSONObjectList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                final String uid = c.getString(TAG_UID);
                final String name = c.getString(TAG_NAME);
                final String mobileNO = c.getString(TAG_MobileNO);
                final String officeNO = c.getString(TAG_OfficeNO);
                final String Profile_Image = c.getString(TAG_Image);

                // 서버에서 가져온 데이터 저장
                sqLiteDBHandler.InsertData(uid,name, mobileNO, officeNO,Profile_Image);
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }

        textView.setText(String.valueOf(sqLiteDBHandler.getTableRowCount()));
    }

}


도움되셨다면 00 클릭~ 해주시길 ^^


참고하면 도움이 될 게시글

https://sites.google.com/site/ydhanslab/andeuloideu/oebusqlitedbneohgi


728x90
블로그 이미지

Link2Me

,
728x90

안드로이드에서 Adapter 는 선택 위젯(ListView, GridView, Gallery, Spinner 등)에 일괄된 인터페이스를 제공해 준다.

특정 데이터를 읽어들인 후 특정 선택 기능에서 사용될 항목 View 를 만들어 선택 가능 위젯에 제공하는 역할을 한다.




ListView 는 Adapter 를 사용하여 데이터를 표시하는 View 이다.


1. DataSet (XML 파일 또는 서버 DB파일)에 있는 자료를 저장할 ArrayList 를 선언한다.

    ArrayList<String> items = new ArrayList<String>();


2. 파일을 가져와서 XmlPullParser 에 넣는다.

    END_DOCUMENT(종료 태그)가 나올때 까지 반복한다.

    반복하면서 TEXT 를 추출하여 1번의 items 에 자료를 저장한다.


XmlPullParser 는 문서를 순차적으로 읽으며 이벤트를 발생시킨다.

START_DOCUMENT 이벤트는 문서의 시작을
END_DOCUMENT 은 문서의 끝을
START_TAG는 태그의 시작을 (예 : <uid> )
END_TAG는 태그의 끝을 (예 : </uid> )
TEXT는 태그의 시작과 끝 사이에서 나타난다. (예 : <uid>여기서 TEXT 이벤트 발생</uid> )

XmlPullParser는 순차적으로(한줄한줄) 문서를 읽어가면서 이벤트를 발생시키므로 뒤로 돌아가지 못하는 문제가 있다.

그래서 이를 해결하기 위해
XmlPullParser.START_TAG 이벤트가 발생하면 임시변수(String)에 Tag값을 저장하고
XmlPullParser.TEXT이벤트에서 임시변수에 저장된 Tag값을 확인하여 적절한 변수에 값을 넣어야한다.


주의 : XmlPullParser.START_TAG와 XmlPullParser.END_TAG 에서는 getName()을 사용하여야 하고 XmlPullParser.TEXT에서는 getText()를 사용하여야한다. 그렇지 않으면  null값을 반환한다.
XmlPullParser.TEXT 이벤트는 태그의 택스트 영역에 문자가 존재하지 않아도 발생한다.




728x90
블로그 이미지

Link2Me

,
728x90

이제 ListView 에 간단하게 출력하는 예제를 살펴보자.


activity_main.xml

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

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#e7e7e7"
        />
</LinearLayout>


Student.java 클래스를 만든다.




이렇게 하면 파일이 완성된다.

public class Student {
    String name;
    String age;
    String address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}


이제 MainActivity.java 파일 코드를 구현한다.

아래 MainActivity.java 에서 case XmlPullParser.START_TAG: 부분을 살펴보자.

startTag 의 이름이 name 이면 다음 텍스트를 변수에 저장하라고 되어 있는 걸 알 수 있다.

이처럼 START_TAG 부분에서 텍스트를 어떻게 추출하는지 알 수 있다.

LOG 는 일부러 세부적으로 찍어보도록 했다. 로그를 확인하면서 어떻게 메시지를 파싱하는지 확인할 수 있다.


String startTag = xmlParser.getName();
if(startTag.equals("student")){
    student = new Student();
}
if(startTag.equals("name")){
    student.setName(xmlParser.nextText());
    Log.i(TAG,"TEXT : "+ xmlParser.getText());
    Log.i(TAG,"TEXT : "+ xmlParser.getName());
    Log.i(TAG,"TEXT : "+ student.getName());
}


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    final static String TAG ="XML";
    ListView listView;

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

        listView = (ListView) findViewById(R.id.listView);

        ArrayList<Student> list = parser();
        String[] data = new String[list.size()]; // ArrayList 크기만큼 배열 할당

        for(int i = 0; i < list.size(); i++){
            data[i] = list.get(i).getName()+" "+list.get(i).getAge()
                    +" "+list.get(i).getAddress();
        }

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),
            android.R.layout.simple_list_item_1, data);
        listView.setAdapter(adapter);
    }

    private ArrayList<Student> parser(){
        Log.i(TAG, "parser()");
        ArrayList<Student> arrayList = new ArrayList<Student>();

        // 내부 xml파일이용시
        InputStream inputStream = getResources().openRawResource(R.raw.student);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

        XmlPullParserFactory factory = null;
        XmlPullParser xmlParser = null;

        try {
            factory = XmlPullParserFactory.newInstance();
            xmlParser = factory.newPullParser();
            xmlParser.setInput(inputStreamReader);
            Student student = null;

            int eventType = xmlParser.getEventType();

            while (eventType != XmlPullParser.END_DOCUMENT){
                switch (eventType){
                    case XmlPullParser.START_DOCUMENT:
                        Log.i(TAG, "xml START");
                        break;
                    case XmlPullParser.START_TAG:
                        Log.i(TAG, "Start TAG :" + xmlParser.getName());
                        try {
                            String startTag = xmlParser.getName();
                            if(startTag.equals("student")){
                                student = new Student();
                            }
                            if(startTag.equals("name")){
                                student.setName(xmlParser.nextText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ student.getName());
                            }
                            if(startTag.equals("age")){
                                student.setAge(xmlParser.nextText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ student.getAge());
                            }
                            if(startTag.equals("address")){
                                student.setAddress(xmlParser.nextText());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ xmlParser.getName());
                                Log.i(TAG,"TEXT : "+ student.getAddress());
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        Log.i(TAG,"End TAG : "+ xmlParser.getName());
                        String endTag = xmlParser.getName();
                        if(endTag.equals("student")){
                            arrayList.add(student);
                        }
                        break;
                }
                try {
                    eventType = xmlParser.next();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } finally{
            try{
                if(inputStreamReader !=null) inputStreamReader.close();
                if(inputStream !=null) inputStream.close();
            }catch(Exception e2){
                e2.printStackTrace();
            }
        }
        return arrayList;
    }
}


결과화면


샘플 코드 파일 첨부

xml_internaldataParsing.zip


728x90
블로그 이미지

Link2Me

,
728x90

어플 내부에 있는 XML 파일을 파싱하는 예제다.

=== student.xml ===

 <person>
    <student>
        <name>홍길동</name>
        <age>32</age>
        <address>용인</address>
    </student>
    <student>
        <name>이순신</name>
        <age>31</age>
        <address>서울</address>
    </student>
    <student>
        <name>강감찬</name>
        <age>35</age>
        <address>충청</address>
    </student>
</person>


예제는 화면 출력 용도가 아니라 로그파일에 기록되는 결과를 보기 위한 목적이다.

파싱하는 소스 코드

- 먼저 layout 디렉토리 밑에 raw 라는 디렉토리를 생성한다.

- raw 디렉토리에 student.xml 파일을 생성하고 xml 내용을 추가한다.

- MainActivity.java 에서 아래와 같은 코드를 작성한다.


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class MainActivity extends AppCompatActivity {
    final static String TAG ="XML";

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

        parser();
    }

    private void parser(){
        Log.i(TAG, "parser()");

        // 내부 xml파일이용시
        InputStream inputStream = getResources().openRawResource(R.raw.student);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader reader = new BufferedReader(inputStreamReader);

        XmlPullParserFactory factory = null;
        XmlPullParser xmlParser = null;

        try {
            factory = XmlPullParserFactory.newInstance();
            xmlParser = factory.newPullParser();
            xmlParser.setInput(reader);

            int eventType = xmlParser.getEventType();

            while (eventType != XmlPullParser.END_DOCUMENT){
                switch (eventType){
                    case XmlPullParser.START_DOCUMENT:
                        Log.i(TAG, "xml START");
                        break;
                    case XmlPullParser.START_TAG:
                        Log.i(TAG, "Start TAG :" + xmlParser.getName());
                        break;
                    case XmlPullParser.END_TAG:
                        Log.i(TAG,"End TAG : "+ xmlParser.getName());
                        break;
                    case XmlPullParser.TEXT:
                        Log.i(TAG,"TEXT : "+ xmlParser.getText());
                        break;
                }
                try {
                    eventType = xmlParser.next();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } finally{
            try{
                if(reader !=null) reader.close();
                if(inputStreamReader !=null) inputStreamReader.close();
                if(inputStream !=null) inputStream.close();
            }catch(Exception e2){
                e2.printStackTrace();
            }
        }


    }
}


이제 컴파일을 하고 결과를 살펴본다.






이제 다음에는 ListView 에 출력하는 걸 해보면 XML 파싱을 정확하게 파악할 수 있다.

728x90
블로그 이미지

Link2Me

,
728x90

어플 기능 개선이나 버그 수정 등으로 업그레이드를 해야 할 경우가 생긴다.

이때 업그레이드 여부 검사를 하는 로직과 파일 다운로드 기능을 구현했다.


구글링으로 검색하면 파일 다운로드 로직은 많이 나온다.

http://www.androidbegin.com/tutorial/android-download-progress-bar/

약간씩 설명이 다르지만 대부분 유사한 설명이 나온다.


서버의 버전과 어플의 버전을 비교하여 서버의 버전이 높으면 파일을 자동으로 다운로드하는 로직으로 구현했다.

기본적인 개념은 http://link2me.tistory.com/1277 파일을 실행해보면 알 수 있다.

파일을 다운로드하면 상태바가 활성화되고, 다운로드 하지 않을 경우에는 화면에 보이지 않도록 UI 를 선택적으로 처리했다.


 import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class MainActivity extends AppCompatActivity {
    public SharedPreferences settings;
    static final int PERMISSION_REQUEST_CODE = 1;
    String[] PERMISSIONS = {"android.permission.READ_EXTERNAL_STORAGE","android.permission.WRITE_EXTERNAL_STORAGE"};

    LinearLayout linearLayout;
    TextView textView;
    ProgressBar progressBar;
    DownloadFileFromURL downloadFileAsyncTask;
    static boolean isUpgrade = false;

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

        // 네트워크 연결 검사
        if(NetworkConnection() == false){
            NotConnected_showAlert();
        }

        if (!hasPermissions(PERMISSIONS)) { //퍼미션 허가를 했었는지 여부를 확인
            requestNecessaryPermissions(PERMISSIONS);//퍼미션 허가안되어 있다면 사용자에게 요청
        }

        // Linear layout 정의
        linearLayout = (LinearLayout) findViewById(R.id.downloadprogress_layout);
        textView = (TextView)findViewById(R.id.txtView01);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);

        // 업그레이드 유무 검사
        VersionCheck();

        if(isUpgrade == false){
            linearLayout.setVisibility(View.GONE);
        } else {
            linearLayout.setVisibility(View.VISIBLE);
        }

        //취소 버튼 이벤트 처리 및 리스너 등록
        Button btn_Cancel = (Button)findViewById(R.id.btnCancel);
        btn_Cancel.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                downloadFileAsyncTask.cancel(true);
            }
        });

    }

    private void VersionCheck(){
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("idx", "2"); // settings.getString("idx","" )
        String postParams = builder.build().getEncodedQuery();

        new isUpgradeTask().execute(Value.IPADDRESS + "/file/UpgradeChk.php", postParams);
    }

    private void UpgradeChk(final String serverVersion){

        try {
            String Appversion = Value.VERSION;
            Appversion = Appversion.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
            System.out.println("App Version : " + Appversion + " Server Version : " + serverVersion);

            if (Integer.parseInt(Appversion) < Integer.parseInt(serverVersion)) { // 서버 버전이 더 높으면
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setTitle("버전 업그레이드");
                builder.setMessage("버전 업그레이드 되었습니다. 새로운 버전으로 다시 설치해주십시오.");
                builder.setPositiveButton("업데이트", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(getApplicationContext(), "파일 다운로드 중입니다.", Toast.LENGTH_SHORT).show();
                        isUpgrade = true;
                        linearLayout.setVisibility(View.VISIBLE);
                        // 백그라운드 객체를 만들어 줌
                        downloadFileAsyncTask = new DownloadFileFromURL(MainActivity.this);
                        downloadFileAsyncTask.execute(Value.IPADDRESS + "/file/apkDownload.php");
                    }
                });
                builder.setNegativeButton("취소", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 업데이트 취소시 어플을 완전 종료시킴
                        moveTaskToBack(true);
                        finish();
                        android.os.Process.killProcess(android.os.Process.myPid());
                    }
                });
                builder.create();
                builder.show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    class isUpgradeTask extends AsyncTask<String,String,String>{
        @Override
        protected String doInBackground(String... params) {
            BufferedReader bufferedReader = null;
            String Response = null;
            try {
                URL url = new URL(params[0]);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                if(conn != null){ // 연결되었으면
                    //add request header
                    conn.setRequestMethod("POST");
                    conn.setRequestProperty("USER-AGENT", "Mozilla/5.0");
                    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                    conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
                    conn.setConnectTimeout(10000);
                    conn.setReadTimeout(10000);
                    conn.setUseCaches(false);
                    conn.setDefaultUseCaches(false);
                    conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                    conn.setDoInput(true);

                    // Send post request
                    DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                    wr.writeBytes(params[1]);
                    wr.flush();
                    wr.close();

                    int responseCode = conn.getResponseCode();
                    System.out.println("GET Response Code : " + responseCode);
                    if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                        bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                        Response = bufferedReader.readLine();
                        Response = Response.replaceAll("[^0-9]", ""); // 버전에서 숫자만 추출
                    }
                    bufferedReader.close();
                }
                return Response;
                // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
            } catch(Exception e){
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result) {
            UpgradeChk(result);
        }

    }

    class DownloadFileFromURL extends AsyncTask<String,Integer,String>{
        private Context context;
        private String savePath;
        private File file, dir;
        int count;

        public DownloadFileFromURL(Context context) {
            this.context = context;
        }

        protected void onPreExecute() {
            super.onPreExecute();
            progressBar.setProgress(0);
        }

        protected String doInBackground(String... params) {
            URLConnection connection = null;
            InputStream input = null;
            OutputStream output = null;
            int lenghtOfFile =0;

            savePath = "/Android/data/" + getPackageName();
            dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), savePath);
            if (!dir.exists()) dir.mkdirs(); // make dir
            file = new File(dir, Value.APKNAME);  // APK 이름 변경

            try {
                URL url = new URL(params[0]);
                try {
                    connection = url.openConnection();
                    connection.connect();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                lenghtOfFile  = connection.getContentLength(); //파일 크기를 가져옴
                if (file.exists()) {
                    file.delete(); // 파일이 존재하면 기존 파일을 삭제
                }

                try {
                    input = new BufferedInputStream(url.openStream());
                    output = new FileOutputStream(file);
                    byte data[] = new byte[1024];
                    long total = 0;

                    while ((count = input.read(data)) != -1) {
                        if (isCancelled()) {
                            input.close();
                            return String.valueOf(-1);
                        }
                        total = total + count;
                        if (lenghtOfFile > 0) { // 파일 총 크기가 0 보다 크면
                            publishProgress((int)(total * 100 / lenghtOfFile));
                        }
                        System.out.println("Download Percent === " + (int)(total * 100 / lenghtOfFile));
                        System.out.println("Total === " + total);
                        output.write(data, 0, count); //파일에 데이터를 기록
                    }
                    System.out.println("Download total Size === " + lenghtOfFile);

                    output.flush();
                    output.close();
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            //백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
            progressBar.setProgress(progress[0]);
            textView.setText("진행정도 : "+progress[0]+"%");
        }

        protected void onPostExecute(String result) {
            //백그라운 작업이 끝난후에 호출되는 메소드
            System.out.println("result === " + result);
            linearLayout.setVisibility(View.GONE);
            if ( result == null ) {
                Toast.makeText(getApplicationContext(), "다운로드 완료되었습니다.", Toast.LENGTH_LONG).show();
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                context.startActivity(intent);
            } else {
                Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show();
            }
        }

        protected void onCancelled(){
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            // 작업을 취소했을 경우에 사용되는 메소드
            progressBar.setProgress(0);
            textView.setText("다운로드 진행 취소됨");
        }
    }

    private boolean hasPermissions(String[] permissions) {
        int res = 0;
        //스트링 배열에 있는 퍼미션들의 허가 상태 여부 확인
        for (String perms : permissions){
            res = checkCallingOrSelfPermission(perms);
            if (!(res == PackageManager.PERMISSION_GRANTED)){
                //퍼미션 허가 안된 경우
                return false;
            }
        }
        return true;  //퍼미션이 허가된 경우
    }

    private void requestNecessaryPermissions(String[] permissions) {
        //마시멜로( API 23 )이상에서 런타임 퍼미션(Runtime Permission) 요청
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(permissions, PERMISSION_REQUEST_CODE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){
        switch(permsRequestCode){
            case PERMISSION_REQUEST_CODE:
                if (grantResults.length > 0) {
                    boolean readAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                    boolean writeAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if( !readAccepted || !writeAccepted  ) {
                            showDialogforPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다.");
                            return;
                        }
                    }
                }
                break;
        }
    }

    private void showDialogforPermission(String msg) {

        final AlertDialog.Builder myDialog = new AlertDialog.Builder(  MainActivity.this);
        myDialog.setTitle("알림");
        myDialog.setMessage(msg);
        myDialog.setCancelable(false);
        myDialog.setPositiveButton("예", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    requestPermissions(PERMISSIONS, PERMISSION_REQUEST_CODE);
                }

            }
        });
        myDialog.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
                finish();
            }
        });
        myDialog.show();
    }

    public void NotConnected_showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid() );
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public boolean NetworkConnection() {
        int[] networkTypes = {ConnectivityManager.TYPE_MOBILE, ConnectivityManager.TYPE_WIFI};
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if(activeNetwork != null && activeNetwork.getType() == networkType){
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }
}

 import android.support.v7.app.AppCompatActivity;

public class Value extends AppCompatActivity {

    public static final String IPADDRESS = "http://192.168.0.2"; // SERVER IP
    public static final String VERSION = "1.0.0"; // App Version
    public static final String APKNAME = "AddressBook.apk" ; //APK name

}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="vertical"
    tools:context="com.tistory.link2me.filedownload.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="10dp"
        android:text="업그레이드 체크"  />

    <LinearLayout
        android:id="@+id/downloadprogress_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/txtView01"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="18dp"
            android:padding="10dp"
            android:text="Progress Status"/>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ProgressBar
                android:id="@+id/progressBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
                android:layout_marginTop="5dp"
                android:max="100"
                android:padding="10dp"
                android:layout_weight="8" />
            <Button
                android:id="@+id/btnCancel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginTop="5dp"
                android:layout_marginLeft="10dp"
                android:text="취소"
                android:textSize="12dp"
                />
        </LinearLayout>

    </LinearLayout>

</LinearLayout>


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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round"
        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>
    </application>

</manifest>


728x90
블로그 이미지

Link2Me

,
728x90

파일 다운로드 함수를 구현하기에 앞서 AsyncTask 다운로드 상태바 표시되는 예제다.

동영상 강좌 듣는 것을 테스트 하면서 명칭 일부를 수정한 거다.

AsyncTask 개념 설명은 http://link2me.tistory.com/1031 을 참조하면 된다.

실제 코드 구현하면서 에러 발생하는 부분 등을 같이 포함해서 기록해 두고 있다.


AsyncTask_ex01.zip


import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    ProgressBar pBar;
    DownloadFileAsyncTask downloadFileAsyncTask;

    int value;

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

        textView = (TextView)findViewById(R.id.txtView01);
        pBar = (ProgressBar)findViewById(R.id.progressBar);

        Button btn_Start = (Button)findViewById(R.id.btnStart);
        btn_Start.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                // 백그라운드 객체를 만들어줌
                downloadFileAsyncTask = new DownloadFileAsyncTask();
                downloadFileAsyncTask.execute();
            }
        });

        Button btn_Cancel = (Button)findViewById(R.id.btnCancel);
        btn_Cancel.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                downloadFileAsyncTask.cancel(true);
            }
        });
    }

    class DownloadFileAsyncTask extends AsyncTask<Integer, Integer, Integer> {

        protected void onPreExecute() { //초기화 단계에서 사용되는 메소드
            value = 0;
            pBar.setProgress(value);
        }

        protected Integer doInBackground(Integer... params) {
            // doInBackground 메소드는 필수 메소드
            while(isCancelled() == false){
                value++;
                if(value >=100){
                    break;
                }else{
                    publishProgress(value);
                }
                try{
                    Thread.sleep(100);
                }catch(Exception e){}
            } //while of End

            return value;
        }

        protected void onProgressUpdate(Integer... values) {
            //백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
            pBar.setProgress(values[0].intValue());
            textView.setText("작업 진행정도 : "+values[0].toString()+"%");
        }

        protected void onPostExecute(Integer result) {
            //백그라운 작업이 끝난후에 호출되는 메소드
            pBar.setProgress(0);
            textView.setText("작업 진행 완료");
        }

        protected void onCancelled(){
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            // 작업을 취소했을 경우에 사용되는 메소드
            pBar.setProgress(0);
            textView.setText("작업 진행 취소됨");
        }
    }
}

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

    <TextView
        android:id="@+id/titleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="AsyncTask Exercise"
        android:textSize="20dp"
        android:gravity="center"
        android:textColor="@color/colorAccent"
        android:layout_margin="10dp"
        />

    <TextView
        android:id="@+id/txtView01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18dp"
        android:padding="10dp"
        android:text="Progress Status"/>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_marginTop="5dp"
        android:max="100"
        android:padding="10dp"
        />

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp">

        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:layout_marginRight="20dp"
            android:text="시작"
            android:textSize="18dp"/>
        <Button
            android:id="@+id/btnCancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:text="취소"
            android:textSize="18dp"
            />

    </LinearLayout>
</LinearLayout>



728x90
블로그 이미지

Link2Me

,
728x90

permission denied for this window type/BadTokenException


윈도우 팝업창이 뜨지 않고 에러가 발생하면서 죽는다.

검색해보네 API Level 19 이상에서는

WindowManager.LayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT

을 사용하지 말고

LayoutParams.TYPE_TOAST or TYPE_APPLICATION_PANEL

를 사용하라고 나온다.


그래서 아래와 같이 변경했더니 앱이 죽지 않고 팝업창이 잘 뜬다.


mParams = new WindowManager.LayoutParams(
   width,
   WindowManager.LayoutParams.WRAP_CONTENT,
   WindowManager.LayoutParams.TYPE_TOAST,
   WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
           | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
           | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
           | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
   PixelFormat.TRANSLUCENT);

728x90
블로그 이미지

Link2Me

,
728x90

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


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

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



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

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







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


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




728x90
블로그 이미지

Link2Me

,
728x90

상단에 액션바가 차지하고 있어서 눈에 거슬린다.

상단 액션바를 안보이게 처리하는 방법을 알아봤는데 어떨 때는 되고 안될때도 있는데 API 버전에 따라 다른거 같기도 하다.


AndroidManifest.xml 파일에서 수정할 부분

android:theme="@style/Theme.AppCompat.NoActionBar"


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

          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>

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

          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>


이것만 수정하고 컴파일을 하면 화면이 검은 바탕색으로 변경되어 버린다.


activity_main.xml 파일 수정

배경색 추가를 한다. 배경색을 하얀색으로 변경하면 보이던 글자가 안보이는 현상이 생겼다.

그래서 색상을 맞춘다고 @color/whitegray  = #f0f0f0 로 만들었다.

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

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


문제는 또 발생한다. 모든 xml 파일 상단에 배경색을 저장해줘야 하는 문제점이 보인다.

검정색 글씨로 보여야 되는데 하얀색 글자로 보이면서 가독성이 엄청 떨어져 보인다.

배경색을 lightgray 로 변경했더니 하얀색 글자가 보이기는 하는데 영 불편하다.


결국 해결한 방법은


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

          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>


부분은 그대로 두고 참조하는 style.xml 파일 내용을 수정한다.

<item name="windowNoTitle">true</item> 라인을 추가한다.


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="windowNoTitle">true</item>
    </style>

</resources>



해결방법 2.

=== strings.xml ===

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Light" />
    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

=== AndroidManifest.xml ===

 <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"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>






728x90
블로그 이미지

Link2Me

,