728x90

PDO 방식으로 MySQL 과 연동하여 데이터를 가져오는 걸 테스트했다.

사용자 정의 함수를 만들어서 연동하는 방법이다.

사용자 함수 CustomFunction($ItemName,$Quantity) 의 결과가 있을 경우에는 데이터를 반환하고, 없으면 false 로 결과 반환을 한다.

true, fasle 로 처리했으므로 if($result = CustomFunction($ItemName,$Quantity)) 문으로 처리하면 원하는 결과를 얻을 수 있다.


<?php
require_once 'pdoconn.php';
// SELECT
$ItemName ='수박';
$Quantity = 500;
if($result = CustomFunction($ItemName,$Quantity)){
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $result->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$db = null;

function CustomFunction($ItemName,$Quantity){
    global $db;
    $sql='select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName and Quantity=:Quantity';
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':ItemName', $ItemName);
    $stmt->bindParam(':Quantity', $Quantity);
    $stmt->execute();
    if($stmt->fetch(PDO::FETCH_BOTH)){
        return $stmt;
    } else {
        return false;
    }
}
?>


함수의 개수가 많아지면 함수명이 중복될 수도 있고 관리가 어려워질 수 있다.

이제 Class 화를 해보자.

<?php
class TestClass
{
    function CustomFunction($ItemName,$Quantity){
        global $db;
        $sql='select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName and Quantity=:Quantity';
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':ItemName', $ItemName);
        $stmt->bindParam(':Quantity', $Quantity);
        $stmt->execute();
        if($stmt->fetch(PDO::FETCH_BOTH)){
            return $stmt;
        } else {
            return false;
        }
    }
}
?> 

bind_param 은 mysqli_stmt 방식이고, bindParam은 PDOStatement 방식이다.


TestClass 에 있는 함수를 이용하는 예제다.

처음에 연동했던 방식과 비교해서 어떤 부분이 달라졌는지만 살펴보면 알 수 있다.

<?php
require_once 'pdoconn.php';
require_once 'pdoClass.php';
$test = new TestClass;

$ItemName ='수박';
$Quantity = 500;
if($result = $test->CustomFunction($ItemName,$Quantity)){
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $result->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$db = null;
?> 


위 방식에는 문제가 있더라.

결과 개수가 다르게 나온다.

제대로 처리하기 위해서는 ....

<?php
include_once 'dbinfo.php';
try{
    // MySQL PDO 객체 생성
    $db = new PDO('mysql:host='.$db['host'].';dbname='.$db['name'].';charset=utf8', $db['user'], $db['pass']);
    // 에러 출력
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(Exception $e) {
    echo 'Failed to obtain database handle : '.$e->getMessage();
}
?>

<?php
class TestClass
{
    function CustomFunction($ItemName,$Quantity){
        global $db;
        $sql='select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName and Quantity=:Quantity';
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':ItemName', $ItemName);
        $stmt->bindParam(':Quantity', $Quantity);
        $stmt->execute();
        if($this->CustomFunctionCount($ItemName,$Quantity)>0){
            return $stmt;
        } else {
            return false;
        }
    }

    function CustomFunctionCount($ItemName,$Quantity){
        global $db;
        $sql='select count(*) from table_items where ItemName=:ItemName and Quantity=:Quantity';
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':ItemName', $ItemName);
        $stmt->bindParam(':Quantity', $Quantity);
        $stmt->execute();
        return $stmt->fetchColumn();
    }

}
?>

<?php
require_once 'pdoconn.php';
require_once 'pdoClass.php';
$test = new TestClass;

$ItemName ='수박';
$Quantity = 500;

//echo $test->CustomFunctionCount($ItemName,$Quantity).'<br />';
if($result = $test->CustomFunction($ItemName,$Quantity)){
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $result->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$db = null;
?>


'Web 프로그램 > PDO' 카테고리의 다른 글

[PHP] PDO MemberClass  (0) 2017.11.16
[PHP] PDO DB 연결방법  (0) 2017.11.16
MySQL 접속방식 PDO 접속방식으로 변환  (0) 2017.07.28
PDO(PHP Data Objects) 개념 및 사용법  (0) 2017.07.25
[리눅스] PDO-MYSQL 모듈 설치  (0) 2017.06.12
블로그 이미지

Link2Me

,
728x90

PDO 접속방식으로 변경하기 위해서 기존 MySQL 방식으로 만든 함수를 PDO 접속형식으로 변경하는 방법이다.


MySQL 

 PDO

 $result=mysql_query($sql);

 $stmt = $db->prepare($sql);

 $stmt->execute();

 $row=mysql_fetch_row($result)

 $row= $stmt->fetch(PDO::FETCH_NUM)

 $row=mysql_fetch_array($result)

 $row= $stmt->fetch(PDO::FETCH_BOTH)

 mysql_num_rows($result)

 $sql='select count(*) from tableName order by uid';
 $stmt = $db->query($sql);
 $num_rows = $stmt->fetchColumn();

 if($result=mysql_query($sql)){
     $row=mysql_fetch_row($result);
     return $row[0];
 } else {
     return 0;
 }

 $stmt = $db->prepare($sql);
 $stmt->execute();
 if($row=$stmt->fetch()){
     return $row[0];
 } else {
     return 0;
 }

 if($result = mysql_query($sql)){
     $row = mysql_fetch_row($result);
     if($row[0] == NULL){
         return 0;
     } else {
         return $row[0];
     }
 } else {
     return 0;
 }

 $stmt = $db->prepare($sql);
 $stmt->execute();
 if($row=$stmt->fetch()){
     if($row[0] == NULL){
         return 0;
     } else {
         return $row[0];
     }
 } else {
     return 0;
 }



블로그 이미지

Link2Me

,
728x90

mysql 확장 기능은 마침내 PHP 7에서 제거되었다.
PHP는 급격하게 성장하며 더 나은 프로그래밍 언어가 되고 있다.
PDO를 사용하는 것은 객체지향과 재사용 가능한 애플리케이션의 데이터베이스 층을 만드는 첫단계이다.

PDO(PHP Data Objects)란 여러가지 데이터베이스를 제어하는 방법을 표준화시킨 것이다.
데이터베이스는 다양한 종류가 있다. PDO 를 사용하면 동일한 방법으로 데이터베이스를 제어할 수 있다.

 

PDO를 이용하기 위한 준비사항

윈도우 Autoset9 (Apache + PHP + MySQL) 에서 PDO를 이용하려면 php.ini 파일에서 주석처리된 것을 없앤다. extension=php_pdo_mysql.dll
;extension=php_pdo_oci.dll
;extension=php_pdo_odbc.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll

 

Autoset9을 다시 실행하면 phpinfo() 에서 설치된 것을 확인할 수 있다.

 

PDO 를 이용하기 위한 준비가 되었다.

PHP 5.5 이상에서 이용 가능하다. AutoSet9 을 설치하면 PHP 5.6.0 버전을 이용한다.

CentOS 6.6 에서 APM 소스 설치방법으로 하는 방법도 가능하더라. 소스 설치 스크립트를 만들어 두면 편리하다.

 

PDO 기본 사용법

$variable = new PDO(DSN, 사용자명, PW);
$variable = null; // PDO 접속 종료
DSN : 접두사:host=localhost;dbname=DB명;charset=utf8;
접두사 : DB의 종류(MySQL, Pgsql, Sqlite)

 

=== dbinfo.php ===

<?php
$db['host'] = "localhost";
$db['name'] = "address";
$db['user'] = "address_root";
$db['pass'] = "autoset";
$db['port'] = "3306";

//use mysql;
//create user address_root@localhost identified by 'autoset';
//grant all privileges on address.* to address_root@localhost;  -- 사용자 권한 부여
//flush privileges;    -- // 변경된 내용을 메모리에 반영(권한 적용)
?>

 

객체 생성 : try catch 문에서 생성한다.

===  pdoconn.php ===

<?php
include_once 'dbinfo.php';
try{
    // MySQL PDO 객체 생성
    $dbconn = new PDO('mysql:host='.$db['host'].';dbname='.$db['name'].';charset=utf8', $db['user'], $db['pass']);

    // 에러 출력
    $dbconn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(Exception $e) {
    echo 'Failed to obtain database handle : '.$e->getMessage();
}
?>

 

new PDO DSN 입력할 때 실수하면 에러가 발생한다.

 

 

PDO SQL 실행

 

<?php
require_once 'pdoconn.php';
// SELECT
$sql="select uid,ItemName,Price,Quantity from table_items";
$stmt = $dbconn->prepare($sql);
$stmt->execute();

// Fetch 모드를 설정
$stmt->setFetchMode(PDO::FETCH_BOTH); // PDO::FETCH_ASSOC, PDO::FETCH_NUM
// 1 row 씩 가져오기
while($row= $stmt->fetch()) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
?>

 

검색조건을 추가해보자.

위의 내용과 비교해서 달라진 점이 무엇인지 확인해보면

검색조건이 where ItemName=:ItemName 로 되어 있고, $stmt->bindParam(':ItemName', $ItemName); 한줄을 추가했고, 실제 검색어 Value 인 $ItemName ='사과'; 을 한줄 추가했다.

<?php
require_once 'pdoconn.php';
// SELECT
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindParam(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemName ='사과';
$stmt->execute();

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
?>
 

 

$stmt->bindValue(':id', '1', PDO::PARAM_STR);

$stmt->bindValue(':ItemName', '사과');

'1', '사과' 처럼 값을 직접 넣을 때는 bindValue 를 사용해야 한다.

 

$stmt->bindParam(':ItemName', '사과'); // bindParam 을 사용하면 에러가 발생한다.

 

 

이제 bindParam 과 bindValue 차이를 확인할 수 있는 예제를 한번 보자.

아래의 첫번째 예제는 '사과'에 대한 결과를 반환하고, 아래의 두번째 예제는 '복숭아'에 대한 결과를 반환한다.

<?php
require_once 'pdoconn.php';
// SELECT
$ItemName ='사과';
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindValue(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemName ='복숭아'; // 이건 실행안함. Value 로 이미 인식되어서 인듯....
$stmt->execute();

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
$dbconn = null; // DB접속 종료
?>

<?php
require_once 'pdoconn.php';
// SELECT
$ItemName ='사과';
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindParam(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemName ='복숭아';
$stmt->execute();

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
$dbconn = null;
?>

 

이렇게 실행해야 하는 경우가 있는지는 모르지만 결과는 두개를 반환한다.

<?php
require_once 'pdoconn.php';
// SELECT
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindParam(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemNames = array('사과','복숭아');
foreach($ItemNames as $ItemName){
    $stmt->execute();
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$dbconn = null;
?>

 

IN 으로 받은 걸 처리하는게 올바른 것인지 궁금하여 Stakoverflow 에서 검색하고 테스트 해본 결과를 적어둔다.

<?php
require_once 'pdoconn.php';
// SELECT
$ItemNames = array('사과','복숭아');
$total_items = count($ItemNames);
$question_marks = array_fill(0, $total_items, '?');
$sql='select uid,ItemName,Price,Quantity from table_items where ItemName IN ('.implode(',', $question_marks). ')';
$stmt = $dbconn->prepare($sql);
$stmt->execute(array_values($ItemNames));

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
$dbconn = null;
?>

 

이제 배우는 중이라 그런지 MySQLi 에 비해 불편해보이는 것도 있는거 같다.

익숙해지면 장점이 보일지도.....

 

LIKE 검색어 예제

$likeString = '%' . $string . '%';
 $stmt = $dbconn->prepare("SELECT * FROM tableName WHERE columnName LIKE ?");
 $stmt->bind_param('s', $likeString);
 $stmt->execute();

 

 

Insert 예제

 $stmt = $dbconn->prepare("INSERT INTO db_fruit (id, type, colour) VALUES (:id, :name, :color)");
 $stmt->execute(array('id' => $newId, 'name' => $name, 'color' => $color));

 $stmt = $dbconn->prepare("INSERT INTO db_fruit (id, type, colour) VALUES (? ,? ,?)");
 $stmt->execute(array($newId, $name, $color));

 $stmt = $dbconn->prepare("INSERT INTO db_fruit (`id`, `type`, `colour`) VALUES (:id, :name, :colour)");
 $stmt->bindParam(':id', $newId, PDO::PARAM_INT);
 $stmt->bindParam(':type', $type, PDO::PARAM_INT);
 $stmt->bindParam(':colour', $colour, PDO::PARAM_STR);
 $stmt->execute();

 

https://www.w3schools.com/php/php_mysql_prepared_statements.asp 게시글도 읽어보니 도움 많이 된다.

 

블로그 이미지

Link2Me

,
728x90

안드로이드에서 설정(Setting) 정보를 변경하는 Activity 를 만들어 두어야 할 때 필요할 거 같아서 적어둔다.


준비사항

토글버튼 이미지 2개


toggle_drawable.zip


토글버튼 이미지 2개를 drawable 폴더에 복사하고, toggle_selector.xml 파일을 만든다.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/toggle_on" android:state_checked="true"/>
    <item android:drawable="@drawable/toggle_off" android:state_checked="false"/>

</selector>


activity_setting.xml 은 Layout을 정한다.

여러개의 환경설정 정보가 필요하다면 TableLayout 을 선택하는 것이 좋을 거 같다.

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ToggleButton
        android:id="@+id/btn_toggle1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:background="@drawable/toggle_selector"
        android:checked="true"
        android:text=""
        android:textOff=""
        android:textOn="" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_toggle1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceMedium" />

</RelativeLayout>


SettingActivity.java

토글버튼을 선택함에 따라 SharedPreferences 에 값이 변경되도록 하여 관리정보를 알 수 있도록 한다.

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.ToggleButton;

public class SettingActivity extends Activity {

    SharedPreferences pref;
    ToggleButton btn_choice;
    TextView text;

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

        btn_choice = (ToggleButton) findViewById(R.id.btn_toggle1);
        text = (TextView) findViewById(R.id.textView1);
        
        pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        if(pref.getString("choice", "").equals("1")){
            text.setText("ON");
        } else {
            text.setText("OFF");
        }

        btn_choice.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
                if(isChecked == true){
                    text.setText("ON");
                    SharedPreferences.Editor editor = pref.edit();
                    editor.putString("choice", "1");
                    editor.commit();
                } else {
                    text.setText("OFF");
                    System.out.println("토글버튼 해제");
                    SharedPreferences.Editor editor = pref.edit();
                    editor.putString("choice", "0");
                    editor.commit();
                }
            }
        });

    }

}



블로그 이미지

Link2Me

,
728x90

Eclipse 툴에서 패키지명을 변경할 때 해당되는 걸 적어둔다.

번호 1, 2 위에서 마우스 우클릭을 하고 Refactor -> Rename 을 선택하여 변경한다.

번호 3 에서는 패키지명을 직접 변경한다.

번호 4에서는 App 명을 변경하는 걸로 변경한다.




종료하고 다시 읽어들이면 Project Name 이 변경되어 있는 걸 확인할 수 있다.




블로그 이미지

Link2Me

,
728x90

삼성폰에서 Unrecognized profile 2130706433 for video/avc 메시지가 출력된다.

구글링을 해보니까 해결책은 아직 없는것 처럼 나오고 이런 증상을 보이는 폰의 종류들이 나온다.


Samsung Galaxy S6 and Samsung Galaxy S6 Edge+.


Same issue with note 5 upgraded to Android 6.0.1., i have another note 5 with Android 5.1 and is working fine.


Samsung S7 running Android 6.0.1. Screen is black or sometimes flickers dark green on VrVideoView player.


해결책인지는 모르겠는데 적어둔다.

https://developers.google.com/vr/android/reference/com/google/vr/sdk/base/Constants



블로그 이미지

Link2Me

,
728x90

최초 작성일 : 2017.7.18


@Override
public boolean onKeyDown(int keyCode, KeyEvent event){
  if ( event.getAction() == KeyEvent.ACTION_DOWN ){
      if ( keyCode == KeyEvent.KEYCODE_BACK ){
          AlertDialog.Builder alert_confirm = new AlertDialog.Builder(Main.this);
          alert_confirm.setMessage("프로그램을 종료 하시겠습니까?")
          .setCancelable(false)
          .setPositiveButton("종료",
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                  // 'YES'
                  finish();
              }
          }).setNeutralButton("취소",
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {                 
                  //finish();
              }
          }).setNegativeButton("백그라운드",
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                  // 'No'
                  System.out.println("백그라운드 상태로 동작합니다");
                  Intent intent = new Intent();
                  intent.setAction(Intent.ACTION_MAIN);
                  intent.addCategory(Intent.CATEGORY_HOME);
                  startActivity(intent);
              return;
              }
          });
          AlertDialog alert = alert_confirm.create();
          alert.show();
      }
      if ( keyCode == KeyEvent.KEYCODE_HOME ){
         
      }
  }
  return super.onKeyDown(keyCode, event);
}


인터넷에서 찾아서 사용해본 종료에 대한 메소드다.

과연 제대로 종료가 되는지 다른 어플에서 테스트를 해보니 어플이 완전 종료되지 않았다.

finish(); : 현재 Activity 종료



Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);


어플 종료라기 보다는 홈화면이 나오도록 하는 코드다.

어플이 종료되어 있는지 확인하면 종료되지 않고 남아있더라.


폭풍검색을 해서 찾아보니 어플 완전종료는 정말 쉽지 않더라.


'기본적'으로 안드로이드에는 '어플리케이션 종료' 라는 개념 자체가 없다.
한 번 실행된 프로세스는 메모리가 허락하는 한 영원히 죽지 않는다. 


http://m.blog.daum.net/creazier/15309783 에 관련 내용이 비교적 잘 정리되어 있다.

좀 오래된 내용이라 현재 기준 안드로이드에서 잘 동작하는지 여부는 아직 테스트를 못해봤다.


activity .finish(); // 현재 Activity 만 종료 처리
android.os.Process.killProcess(android.os.Process.myPid());

하면 종료될 것이라고 해서 테스트해보면 죽지 않고 좀비처럼 다시 살아난다. ㅠㅠ

Activity 가 1개만 떠있는 경우에만 정상적으로 죽는다고 하던데, 다른 Activity 를 실행하지 않은 상태에서도 테스트해보면 죽지 않고 살아 있더라.


어플 완전 종료가 되는 걸 찾게되면 수정해서 작성하련다. (2017.8.23)


드디어 완전하게 처리되는 걸 찾아냈다. (2017.8.31)

정말 우연히 Audio Player 종료처리하는 걸 테스트하다가 완전 종료되는 걸 확인했다.


if (Build.VERSION.SDK_INT >= 21)
    finishAndRemoveTask();
else
    finish();
System.exit(0);


안드로이드 스튜디오에서 컴파일하는 API 가 21보다 큰 경우인데 계속 예전 버전 종료인 finish(); 로만 처리를 하고 있었던 거다.

위 코드를 넣고 처리하니까 화면상에 남아있지 않았다.


finishAffinity()
Finish this activity as well as all activities immediately below it in the current task that have the same affinity.

finishAndRemoveTask()
Call this when your activity is done and should be closed and the task should be completely removed as a part of finishing the root activity of the task.

블로그 이미지

Link2Me

,
728x90
SQLiteDB에 있는 자료를 읽어서 HashMap 메모리 상에 올리는 예제이다.

HashMap<String, SQLite_Item> sqliteDBMap = new HashMap<String, SQLite_Item>();
public void SQLiteDB2ArrayList(){
    sqliteDBMap.clear(); // 메모리 초기화
    sqLiteDBHandler = new SQLiteDBHandler(Main.this);
    
    SQLiteDatabase db = sqLiteDBHandler.getReadableDatabase();
    db.beginTransaction();
    
    Cursor cursor = sqLiteDBHandler.LoadSQLiteDBCursor();
    try {
        cursor.moveToFirst();
        System.out.println("SQLiteDB 개수 = " + cursor.getCount());
        while (!cursor.isAfterLast()) {
            SQLite_Item item = new SQLite_Item();
            item.setIdx(cursor.getString(0));
            item.setUserNM(cursor.getString(1));
            item.setMobileNO(cursor.getString(2));
            item.setTelNO(cursor.getString(3));
            item.setTeam(cursor.getString(4));
            item.setPosition(cursor.getString(5));
            item.setCheckState(cursor.getInt(6));
            // HashMap 에 추가
            sqliteDBMap.put(cursor.getString(0), item);
            cursor.moveToNext();
        }
        db.setTransactionSuccessful();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        cursor.close();
        db.endTransaction();
    }        
}


sqLiteDBHandler = new SQLiteDBHandler(context); // BroadcastReceiver 에서 사용할 때


블로그 이미지

Link2Me

,
728x90

안드로이드폰에서 전화수신시 앱에서 수신전화의 상태를 감지하여 전화수신 팝업을 띄우기 위한 기본 코드라고 보면 된다.

전화벨이 울리면 메시지가 2번씩 수신된다. 전화에 대한 정보를 2번씩 보내주나 보다.

그래서 팝업창 구현 등을 하려면 1번만 메시지가 수신되어야 하므로 1번만 띄우도록 처리하는 로직이 포함되었다.


--- AndroidManifest.xml 파일 추가할 사항 ---

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

<receiver
    android:name=".CallStateReceiver">
    <intent-filter>
        <action android:name="android.intent.action.PHONE_STATE"/>
    </intent-filter>
</receiver>

 --- CallStateReceiver.java ---

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;

public class CallStateReceiver extends BroadcastReceiver {
    static String mLastState;
    static final String TAG = "CallStateListner";
    
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "CallStateReceiver >>>>>>> 전화 수신");
        
        // 전화 수신 체크
        CallReceivedChk(context, intent);
    }
    
    private void CallReceivedChk(Context context, Intent intent) {
        TelephonyManager telephony = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
        telephony.listen(new PhoneStateListener(){
            @Override
            public void onCallStateChanged(int state, String incomingNumber) {
                String mState = String.valueOf(state);
                if (mState.equals(mLastState)) { // 두번 호출되는 문제 해결 목적
                    return;
                } else {
                    mLastState = mState;
                }
                
                switch(state) {
                    case TelephonyManager.CALL_STATE_IDLE:
                        Log.d(TAG,"전화 수신 상태가 아닙니다 : CALL_IDLE");
                        break;
                    case TelephonyManager.CALL_STATE_OFFHOOK:
                        Log.d(TAG, "전화를 받았습니다 : CALL_OFFHOOK");
                        break;
                    case TelephonyManager.CALL_STATE_RINGING:
                        Log.d(TAG, "CALL_RINGING, 수신 전화번호 : " + PhoneNumberUtils.formatNumber(incomingNumber));
                        // 처리하고자 하는 코드 추가하면 된다.
                        break;            
                }
            }    
        }, PhoneStateListener.LISTEN_CALL_STATE);
    }

}


위 코드를 가지고 활용을 해서 팝업창을 띄우는 코드를 새롭게 만들거나

http://gun0912.tistory.com/46 에 있는 코드를 활용하면 팝업창을 띄울 수 있다.

위 코드를 그대로 사용하면 앱이 죽는 증상이 있다.
코드가 100% 완벽하게 동작되도록 하려면 개발자 본인이 수정 보완해야 한다.

코드에 대한 완벽한 분석과 기초지식이 있어야 가능하더라.


스마트폰 스크린이 잠겨있는 OFF 상태에서 전화가 수신되면 수신 팝업창을 ON 상태로 바꿔 사용자에게 메시지를 알려주려면 어떻게 해야 할까?


안드로이드 시스템에서는 장비가 휴면모드로 들어가게 되면 배터리 소모를 최소화하기 위하여 불필요한 CPU나 와이파이를 포함한 모든 기능들은 정지시키려고 한다.
Service 가 지속적으로 실행이 되고 있는 것을 확실하게 보장하기 위해서 "WAKE Locks"를 사용해야 한다. Wake Lock는 앱에서 특정 기능들을 계속 활성화 시켜놓아야 한다는 것을 시스템에 알려주는 역할을 하는 것이다.


private static PowerManager.WakeLock wakeLock;
private void WakeLock(Context context, Intent intent) {
    // 잠든 화면 깨우기
    if (wakeLock != null) {
        return;
    }

    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
            | PowerManager.ON_AFTER_RELEASE, "hi");
    wakeLock.acquire();  
}


이 메소드를 BroadcastReceiver 에 추가한다.


START_STICKY : 재생성과 on StartCommand() 호출(with null intent)
START_NOT_STICKY : 서비스 재 실행하지 않음
START_REDELIVER_INTENT : 재생성과 on StartCommand() 호출(with same intent)
 


읽어볼만한 자료

배터리를 잡아먹는 주원인 WakeLock : http://widzard.tistory.com/36

블로그 이미지

Link2Me

,
728x90

Android Audio Player 관련 기능을 테스트 해보고 있는 중이다.

어떤 방식으로 하느냐에 따라 구현할 내용이 달라질 거 같은데 방향 잡기가 쉽지 않다.


먼저 AndroidManifest.xml file 에 권한을 추가한다.

<uses-permission android:name="android.permission.INTERNET" />
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


이미 안드로이드에서는 음악, 주소록같은 기본적인 어플리케이션이 존재한다.
이런 기본적인 앱안에는 정보들을 담은 DB가 구현되어 있다.
이런 앱들은 자신의 앱의 데이터베이스에 접근할 수 있도록 도와주는 컨텐트 프로바이더(Content Provider)란 것을 제공한다.
앱이 ContentProvider를 접근할 때에는 ContentResolver를 이용한다.
ContentResolver는 기본적으로 CRUD(Create Retreive Update Delete) 함수들을 제공하며 이를 통해 다른 어플리케이션에 있는 데이터베이스를 조작할 수 있다.


휴대폰에 있는 오디오 파일을 읽어오는 함수는 아래와 같다.

private void loadAudio() {
    ContentResolver contentResolver = getContentResolver();

    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0";
    String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";
    Cursor cursor = contentResolver.query(uri, null, selection, null, sortOrder);
    cursor.moveToFirst();
    System.out.println("음악파일 개수 = " + cursor.getCount());
    if (cursor != null && cursor.getCount() > 0) {
        do {
            long track_id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));
            long albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
            String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
            String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
            String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
            long mDuration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
            String datapath = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
            System.out.println("mId = " + track_id + " albumId = " +albumId+ " title : "+title+" album : "+album+" artist: "+artist+" 총시간 : "+mDuration+" data : "+data);
            // Save to audioList
            listViewAdapter.addItem(track_id,albumId,title,artist,album,mDuration,datapath,false);
        } while (cursor.moveToNext());
    }
    cursor.close();
}



listViewAdapter.addItem 을 위한 정의서인 Song_Item 클래스를 만든다.

담을 내용을 정하는 것은 개발자 몫이다.

public class Song_Item {
    private long mId; // 오디오 고유 ID
    private long AlbumId; // 오디오 앨범아트 ID
    private String Title; // 타이틀 정보
    private String Artist; // 아티스트 정보
    private String Album; // 앨범 정보
    private long Duration; // 재생시간
    private String DataPath; // 실제 데이터 위치
    boolean checkBoxState;

    public Song_Item() {
    }

    public Song_Item(long mId, long albumId, String title, String artist, String album, long duration, String dataPath, boolean checkBoxState) {
        this.mId = mId;
        AlbumId = albumId;
        Title = title;
        Artist = artist;
        Album = album;
        Duration = duration;
        DataPath = dataPath;
        this.checkBoxState = checkBoxState;
    }

    public long getmId() {
        return mId;
    }

    public void setmId(long mId) {
        this.mId = mId;
    }

    public long getAlbumId() {
        return AlbumId;
    }

    public void setAlbumId(long albumId) {
        AlbumId = albumId;
    }

    public String getTitle() {
        return Title;
    }

    public void setTitle(String title) {
        Title = title;
    }

    public String getArtist() {
        return Artist;
    }

    public void setArtist(String artist) {
        Artist = artist;
    }

    public String getAlbum() {
        return Album;
    }

    public void setAlbum(String album) {
        Album = album;
    }

    public long getDuration() {
        return Duration;
    }

    public void setDuration(long duration) {
        Duration = duration;
    }

    public String getDataPath() {
        return DataPath;
    }

    public void setDataPath(String dataPath) {
        DataPath = dataPath;
    }

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

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


MainActivity.java 에서 구현할 내용 일부를 발췌하여 적어둔다.

ListViewAdapter 구현 내용이라고 보면 된다.

ListView 상에 가져오는 코드를 작성했는데 Player.java 코드를 아직 제대로 구현하지 못한 상태다.

구글링해서 얻은 코드들을 발췌하고 내가 원하는 기능으로 구현해보는 중이다.

구현하려는 방법이 달라서 기존 코드를 그대로 사용하지는 않았다.

일부 코드는 그대로 활용해도 너무 좋은 코드가 있어서 그냥 사용한 부분도 있다.


ListViewAdapter 에 아무것도 담지 않은 방법으로 먼저 생성한 다음에 loadAudio() 을 읽어들이는 방식은 장점이 있다. loadAudio() 을 전체 오디오 파일을 가져오게 할 수도 있지만 원하는 것만 검색해서 가져오게 하는 방법도 가능하다. 즉 검색기능을 활용하여 데이터를 가져오는 방법이 가능하여 편리하여 이런 방법을 사용하고 있다.

이 부분은 개발자마다 코드 구현 방식이 다른 것을 구글링을 해보면 알 수 있다.


 public class MainActivity extends AppCompatActivity {
    private ListView audiolistView; // 리스트뷰
    private ArrayList<Song_Item> songsList = null; // 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter

    Context context;

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

        context = this.getBaseContext();

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        songsList = new ArrayList<Song_Item>(); // ArrayList 생성
        audiolistView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
        audiolistView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        audiolistView.setOnItemClickListener(songslistener);

        // 체크박스 보임/안보임 관련 코드는 생략했음...

        loadAudio();

    }

    class ViewHolder {
        public ImageView mImgAlbumArt;
        public TextView mTitle;
        public TextView mSubTitle;
        public TextView mDuration;
        public CheckBox checkbox;
    }

    private class ListViewAdapter extends BaseAdapter {
        Context context;

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

        // 음악 데이터 추가를 위한 메소드
        public void addItem(long mId, long AlbumId, String Title, String Artist, String Album,long Duration,String DataPath, boolean checkItem_flag) {
            Song_Item item = new Song_Item();
            item.setmId(mId);
            item.setAlbumId(AlbumId);
            item.setTitle(Title);
            item.setArtist(Artist);
            item.setAlbum(Album);
            item.setDuration(Duration);
            item.setDataPath(DataPath);
            item.setCheckBoxState(checkItem_flag);
            songsList.add(item);
        }

        // CheckBox를 모두 선택하는 메서드
        public void setAllChecked(final boolean ischeked) {
            final int tempSize = songsList.size();
            if(isAll == true){
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(true);
                }
            } else {
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(false);
                }
            }
        }

        @Override
        public int getCount() {
            return songsList.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return songsList.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();
            final Integer index = Integer.valueOf(position);

            // 화면에 표시될 View
            if(convertView == null){
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.song_item,parent,false);

                convertView.setBackgroundColor(0x00FFFFFF);
                convertView.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.mImgAlbumArt = (ImageView) convertView.findViewById(R.id.album_Image);
                viewHolder.mTitle = (TextView) convertView.findViewById(R.id.txt_title);
                viewHolder.mSubTitle = (TextView) convertView.findViewById(R.id.txt_subtitle);
                viewHolder.mDuration = (TextView) convertView.findViewById(R.id.txt_duration);
                viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.list_cell_checkbox);

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            final Song_Item songItem = songsList.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            Bitmap albumArt = MainActivity.getArtworkQuick(context, (int)songItem.getAlbumId(), 100, 100);
            if(albumArt != null){
                viewHolder.mImgAlbumArt.setImageBitmap(albumArt);
            }
            viewHolder.mTitle.setText(songItem.getTitle());
            viewHolder.mSubTitle.setText(songItem.getArtist());

            int dur = (int) songItem.getDuration();
            int hrs = (dur / 3600000);
            int mns = (dur / 60000) % 60000;
            int scs = dur % 60000 / 1000;
            String songTime = String.format("%02d:%02d:%02d", hrs,  mns, scs);
            if(hrs == 0){
                songTime = String.format("%02d:%02d", mns, scs);
            }
            viewHolder.mDuration.setText(songTime);

            if (isMSG == false) {
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(), "노래 재생합니다" + songItem.getAlbumId(), Toast.LENGTH_SHORT).show();

                        Intent intent = new Intent(MainActivity.this, Player.class);
                        intent.putExtra("path", songItem.getDataPath());
                        intent.putExtra("mTitle", songItem.getTitle());
                        intent.putExtra("mDuration", songItem.getDuration());
                        startActivity(intent);
                    }
                });
            } else {
                if (isMSG == true) {
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.

                    viewHolder.checkbox.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if(songsList.get(position).isCheckBoxState() == true){
                                songsList.get(position).setCheckBoxState(false);
                            } else {
                                songsList.get(position).setCheckBoxState(true);
                            }
                        }
                    });

                    convertView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if(viewHolder.checkbox.isChecked() == false){
                                viewHolder.checkbox.setChecked(true);
                                songsList.get(position).setCheckBoxState(true);
                            } else {
                                viewHolder.checkbox.setChecked(false);
                                songsList.get(position).setCheckBoxState(false);
                            }
                        }
                    });

                }
            }

            // 재사용 문제 해결
            if(songsList.get(position).isCheckBoxState() == true){
                viewHolder.checkbox.setChecked(true);
            } else {
                viewHolder.checkbox.setChecked(false);
            }

            return convertView;
        }

    }

    /* Album ID로 부터 Bitmap 이미지를 생성해 리턴해 주는 메소드 */
    private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
    private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");

    // Get album art for specified album. This method will not try to
    // fall back to getting artwork directly from the file, nor will it attempt to repair the database.
    private static Bitmap getArtworkQuick(Context context, int album_id, int w, int h) {
        w -= 2;
        h -= 2;
        ContentResolver res = context.getContentResolver();
        Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
        if (uri != null) {
            ParcelFileDescriptor fd = null;
            try {
                fd = res.openFileDescriptor(uri, "r");
                int sampleSize = 1;

                sBitmapOptionsCache.inJustDecodeBounds = true;
                BitmapFactory.decodeFileDescriptor(
                        fd.getFileDescriptor(), null, sBitmapOptionsCache);
                int nextWidth = sBitmapOptionsCache.outWidth >> 1;
                int nextHeight = sBitmapOptionsCache.outHeight >> 1;
                while (nextWidth>w && nextHeight>h) {
                    sampleSize <<= 1;
                    nextWidth >>= 1;
                    nextHeight >>= 1;
                }

                sBitmapOptionsCache.inSampleSize = sampleSize;
                sBitmapOptionsCache.inJustDecodeBounds = false;
                Bitmap b = BitmapFactory.decodeFileDescriptor(
                        fd.getFileDescriptor(), null, sBitmapOptionsCache);

                if (b != null) {
                    // finally rescale to exactly the size we need
                    if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) {
                        Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true);
                        b.recycle();
                        b = tmp;
                    }
                }

                return b;
            } catch (FileNotFoundException e) {
            } finally {
                try {
                    if (fd != null)
                        fd.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }

}


song_item.xml 파일과 activity_main.xml 파일은 첨부파일로 올린다.

layout.zip


이런 형태로 데이터를 가져온 모습을 볼 수 있게 된다.


Player.java 파일 구현할 내용에 따라 넘겨줄 내용이 달라지므로 그 부분까지 완성되면 코드 전체를 첨부해볼 생각이다.

아직은 배워야 할 기능들이 많아서 완성된 코드까지는 많은 시간이 걸릴 것 같다.


두고 두고 읽어보고 싶은 블로그 글을 검색으로 알게되어 적어둔다. 나같은 초보에겐 주옥같은 글이다.

http://unikys.tistory.com/350


앞으로 참고할 사이트

https://www.sitepoint.com/a-step-by-step-guide-to-building-an-android-audio-player-app/


블로그 이미지

Link2Me

,
728x90

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


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

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


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


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

AcroDiff.exe

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



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

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

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

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

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


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


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

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

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


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

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

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



블로그 이미지

Link2Me

,
728x90

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

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

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


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



4번을 누르면.....


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

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


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


블로그 이미지

Link2Me

,
728x90

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




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

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

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

블로그 이미지

Link2Me

,
728x90

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

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


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


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


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


블로그 이미지

Link2Me

,
728x90

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



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



블로그 이미지

Link2Me

,
728x90

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

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

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

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


옮기는 방법은

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

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

F:\Android\AndroidStudio_config 로 옮겼다.



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

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


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


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


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



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


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

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


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

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


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


블로그 이미지

Link2Me

,
728x90

안드로이드 연락처(Contacts) 에 있는 데이터를 메모리로 읽어오기 위한 방법은 ArrayList 를 여러개 선언하는 방법도 있지만, Contact_Item 클래스를 만들고 HashTable 를 사용해서 메모리에 저장하는 방법이 좋다.


Contacts_Item 클래스를 정의하는 방법은 http://link2me.tistory.com/1251 게시글을 참조하면 된다.

여기서는 결과를 적는다.

단순하게 연락처에 있는 데이터를 가져오는 클래스가 아니라 서버에 있는 데이터를 연락처에 내려받아 저장한 데이터를 가져오기 위한 것이라 형태가 다른 contactKey 를 추가했다.


 public class Contacts_Item {   
    String contactId; // 연락처(Contacts) Contact_ID 로 데이터 수정/삭제 키 값
    String contactName; // 연락처 표시이름
    String contactmobileNO; // 연락처 휴대폰번호
    String contactofficeNO; // 연락처 사무실번호
    String contactKey; // 서버와의 데이터 동기화를 위한 키

    public Contacts_Item() {
    }

    public Contacts_Item(String contactId, String contactName, String contactmobileNO, String contactofficeNO, String contactKey) {
        this.contactId = contactId;
        this.contactName = contactName;
        this.contactmobileNO = contactmobileNO;
        this.contactofficeNO = contactofficeNO;
        this.contactKey = contactKey;
    }

    public String getContactId() {
        return contactId;
    }

    public void setContactId(String contactId) {
        this.contactId = contactId;
    }

    public String getContactName() {
        return contactName;
    }

    public void setContactName(String contactName) {
        this.contactName = contactName;
    }

    public String getContactmobileNO() {
        return contactmobileNO;
    }

    public void setContactmobileNO(String contactmobileNO) {
        this.contactmobileNO = contactmobileNO;
    }

    public String getContactofficeNO() {
        return contactofficeNO;
    }

    public void setContactofficeNO(String contactofficeNO) {
        this.contactofficeNO = contactofficeNO;
    }

    public String getContactKey() {
        return contactKey;
    }

    public void setContactKey(String contactKey) {
        this.contactKey = contactKey;
    }
}


연락처 Hashtable 를 선언, 등록, 사용하는 방법이다.

HashMap 에 대한 기본 개념은 http://link2me.tistory.com/1210 참조하면 된다.


선언

HashMap<String, Contacts_Item> contactMap = new HashMap<String, Contacts_Item>();


등록

temp_ID 와 temp_keyIDX 는 개념 설명이므로 실 코드는 기록하지 않는다.

Contacts_Item item = new Contacts_Item(); 객체를 생성하고 데이터를 저장한 다음, 선언한 HashMap 에 저정한다. contactMap.put(temp_key, item);


public void Contacts2ArrayList() {
    contactMap.clear(); // 메모리 초기화
    Cursor cursor = ContactHelper.LoadContactsCursor(context.getContentResolver(), search_name);
    cursor.moveToFirst();
    System.out.println("연락처 개수 = " + cursor.getCount());
    while (!cursor.isAfterLast()) {
        String ContactId = cursor.getString(0);
        String temp_key = "";
        if (temp_ID.indexOf(ContactId) > -1) {
            temp_key = temp_keyIDX.get(temp_ID.indexOf(ContactId));
        }
        if (!temp_key.equals("")) { // if 문은 상황에 따라 사용여부 결정하면 된다.
            Contacts_Item item = new Contacts_Item();
            item.setContactId(ContactId);
            item.setContactName(cursor.getString(1));
            item.setContactmobileNO(cursor.getString(2));
            item.setContactofficeNO(cursor.getString(3));
            item.setContactKey(temp_key);
            contactMap.put(temp_key, item);
        }
        cursor.moveToNext();
    }
    cursor.close();
}


검색

if (contactMap.containsKey(idx)) {
    Contacts_Item item = contactMap.get(idx);
    String contactName = item.getContactName();
    String contactId = item.getContactId();
    String contactmNO = item.getContactmobileNO();
    String contactoNO = item.getContactofficeNO();

   

    // 실제 처리할 메소드 추가하면 된다.

}


블로그 이미지

Link2Me

,
728x90

안드로이드 연락처(Contacts)를 추가시 사진 이미지를 다운로드 하는 메소드를 추가했다.

안드로이드에서 이미지를 다루기 위한 객체로 Bitmap을 많이 사용한다.
실질적으로 파일을 저장할 때 jpeg을 많이 사용하므로 관련 코드를 고려했다.


 public class ContactHelper {
    // 신규 추가(성명, 휴대폰번호, 사무실번호, 이메일, 사진, IDX, groupId)
    public static void insertPhoneContacts(Context context, String display_name, String mobileNO, String officeNO,
            String strEmail, String strPhoto, String strIDX, long groupId) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
        ops.add(op.build());

        // DISPLAY NAME(성명)
        op = 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.DISPLAY_NAME, display_name);
        ops.add(op.build());

        // 그룹 세팅
        op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
        ops.add(op.build());

        // 휴대폰 번호
        op = 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, mobileNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, strIDX)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        ops.add(op.build());

        // 사무실 번호
        op = 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, officeNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);

        op.withYieldAllowed(true);
        ops.add(op.build());

        // EMAIL
        op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, strEmail)
                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK);
        ops.add(op.build());

        // Photo Image
        Bitmap cachedImage = DownloadImageFromPath(strPhoto);
        if (cachedImage != null) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            cachedImage.compress(CompressFormat.JPEG, 100, out);

            byte[] b = out.toByteArray();

            op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
                    .withValue(ContactsContract.CommonDataKinds.Photo.DATA15, b);
            ops.add(op.build());

        }

        try {
            context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }

    }

    // 이미지 다운로드
    private static Bitmap DownloadImageFromPath(String path) {
        InputStream instream = null;
        Bitmap image = null;
        int responseCode = -1;
        try {

            URL url = new URL(path); // "http://192.168.xx.xx/imagepath/img1.jpg
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true);
            conn.connect();
            responseCode = conn.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // download
                instream = conn.getInputStream();
                image = BitmapFactory.decodeStream(instream);
                instream.close();
            }

        } catch (Exception ex) {
            Log.e("Exception", ex.toString());
        }
        return image;
    }
   
}


이미지 크기와 높이를 조정하는 코드는 https://stackoverflow.com/questions/18210700/best-method-to-download-image-from-url-in-android 참조하면 된다.

블로그 이미지

Link2Me

,
728x90

안드로이드 연락처에 그룹명을 생성하고, 그룹에 인원을 추가하는 코드다.


사용법

String group_name ="그룹명";
long groupId = ContactHelper.getGroupId(getContentResolver(), group_name);
if(groupId == 0){ // 해당 그룹이 없으면 추가
    ContactHelper.createNewGroup(getContentResolver(), group_name);

    groupId = ContactHelper.getGroupId(getContentResolver(), group_name);

}
long rawId = ContactHelper.getrawIdFromContactId(getContentResolver(),30255);

// 그룹에 인원 추가
ContactHelper.addContactToGroup(getContentResolver(),rawId,groupId);

※ 이 코드를 계속 실행하면 데이터가 계속 추가되는 현상이 있는데 해결 코드를 추가로 구현해야 할 거 같다.


//****************** Contact Group ***************************/
// 그룹에 인원 추가하기
public static boolean addContactToGroup(ContentResolver contactHelper,long rawId, long groupId) {
    try {
        ContentValues values = new ContentValues();
        values.put(RawContacts.Data.RAW_CONTACT_ID, rawId);
        values.put(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
        values.put(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
        contactHelper.insert(ContactsContract.Data.CONTENT_URI, values);
        return true;
    } catch (Exception e){
        e.printStackTrace();
    }
    return false;
}

// 그룹 ID 조회
public static long getGroupId(ContentResolver contactHelper, String group_name) {
    long group_id = 0;

    Uri contactUri = ContactsContract.Groups.CONTENT_URI;
    String[] projection = {ContactsContract.Groups._ID};
    String where = ContactsContract.Groups.TITLE + " = ?";
    String[] whereParams = {group_name};

    Cursor cursor = contactHelper.query(contactUri, projection, where, whereParams, null);
    if (cursor.moveToFirst()) {
        do {
            group_id = cursor.getLong(0);
            Log.e("title", group_name);
            Log.e("id", Long.toString(group_id));
        } while (cursor.moveToNext());
    }

    return group_id;
}

// 그룹 추가
public static void createNewGroup(ContentResolver contactHelper, String group_name) {
    ContentValues cv = new ContentValues();
    cv.put(ContactsContract.Groups.TITLE, group_name);
    cv.put(ContactsContract.Groups.DELETED, 0);
    cv.put(ContactsContract.Groups.SHOULD_SYNC, true);
    cv.put(ContactsContract.Groups.GROUP_VISIBLE, 1);
    contactHelper.insert(ContactsContract.Groups.CONTENT_URI, cv);
}

// 그룹명 수정
public static void updateGroup(ContentResolver contactHelper, String name, long groupId) {
    ContentValues cv = new ContentValues();
    cv.put(ContactsContract.Groups.TITLE, name);
    contactHelper.update(ContactsContract.Groups.CONTENT_URI, cv,
            ContactsContract.Groups._ID + " = " + groupId, null);
}

// 그룹 삭제
public static void deleteGroup(ContentResolver contactHelper, long groupId) {
    Uri contactUri = ContactsContract.Groups.CONTENT_URI;
    String where = ContactsContract.Groups._ID + " = " + groupId;
    contactHelper.delete(contactUri, where, null);
}

public static long getrawIdFromContactId(ContentResolver contactHelper, long contactId) {
    // ContactsContract.RawContacts._ID = ContactsContract.Data.RAW_CONTACT_ID
    long rawId = -1;
    Uri contactUri = ContactsContract.RawContacts.CONTENT_URI;
    String[] projection = {RawContacts._ID};
    String where = RawContacts.CONTACT_ID + "=?";
    String[] whereParams = new String[]{String.valueOf(contactId)};
    Cursor cursor = null;

    try {
        cursor = contactHelper.query(contactUri, projection, where, whereParams, null);
        if (cursor.moveToFirst()) {
            rawId = cursor.getLong(cursor.getColumnIndex(RawContacts._ID));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return rawId;
}




블로그 이미지

Link2Me

,
728x90

안드로이드 연락처(Contacts)를 신규 추가하는 코드다.

메소드 Overloading 을 사용해서 신규 저장하는 코드를 여러개 만들었다.

※ 메소드 오버로딩 : 같은 클래스내 같은 메소드명, 매개변수명이 다르거나 개수가 다르다.


본 코드가 구글 검색으로 찾은 코드와 다른 점 한가지가 있다.

.withValue(ContactsContract.CommonDataKinds.Phone.LABEL, strIDX)

서버 데이터를 Contacts 에 저장하고 비교를 해서 없는 것은 추가하고 있는 것은 달라진 정보만 업데이트하는 로직으로 구현하려고 이름 + 휴대폰번호 정보를 key 로 사용하고자 했으나,

서버 데이터에서 이름 + 휴대폰번호 중 한가지 정보가 달라지면 Contacts 에서는 처리하기가 어려울 거 같았다.

그래서 Contacts 에서 key로 사용할 칼럼을 고민하다가 휴대폰 번호가 가장 검색하기가 쉬운 점에 착안하여 위 칼럼을 key 로 사용했고, 결과는 대성공이다.


import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
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.util.Log;

public class ContactHelper {

    // 연락처 신규 저장
    public static void insertPhoneContacts(Context context, String strName, String mobileNO, String officeNO, String strIDX) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
        ops.add(op.build());

        // DISPLAY NAME(성명)
        op = 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.DISPLAY_NAME, strName);
        ops.add(op.build());

        // 휴대폰 번호
        op = 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, mobileNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, strIDX)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        ops.add(op.build());

        // 사무실 번호
        op = 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, officeNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);

        op.withYieldAllowed(true);
        ops.add(op.build());

        try {
            context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }

    }

    public static void insertPhoneContacts(Context context, String strName, String mobileNO, String officeNO,
            String strEmail, String strTeam, String strPosition, String strMission, String strPhoto, String strIDX) {

        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
        ops.add(op.build());

        // DISPLAY NAME(성명)
        op = 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.DISPLAY_NAME, strName);
        ops.add(op.build());

        // 휴대폰 번호
        op = 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, mobileNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, strIDX)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        ops.add(op.build());

        // 사무실 번호
        op = 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, officeNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
        ops.add(op.build());

        // EMAIL
        op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, strEmail)
                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK);
        ops.add(op.build());

        // Photo Image
        MemoryCache memoryCache = new MemoryCache();
        Bitmap cachedImage = memoryCache.get(strPhoto);
        if (cachedImage != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            cachedImage.compress(CompressFormat.JPEG, 100, baos);

            byte[] b = baos.toByteArray();

            op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
                    .withValue(ContactsContract.CommonDataKinds.Photo.DATA15, b);
            ops.add(op.build());
        }

        /* Organization */
        op = ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, strPosition)
                .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, "회사명")
                .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, strTeam)
                .withValue(ContactsContract.CommonDataKinds.Organization.JOB_DESCRIPTION, strPosition)
                .withValue(ContactsContract.CommonDataKinds.Organization.TYPE,
                        ContactsContract.CommonDataKinds.Organization.TYPE_WORK);
        op.withYieldAllowed(true);
        ops.add(op.build());

        try {
            context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }

    }
    
    // 연락처 신규 저장
    public static void insertPhoneContacts(Context context, String strName, String mobileNO, String officeNO, String strEmail,
            String strTeam, String strPosition, String strMission, String strPhoto, String strNote, String strIDX) {

        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());
        // DISPLAY NAME(성명)
        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.DISPLAY_NAME, strName).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, mobileNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, strIDX)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                .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, officeNO)
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_WORK).build());
        // EMAIL
        ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Email.DATA, strEmail)
                .withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK).build());

         //메모
         ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
         .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
         .withValue(ContactsContract.Data.MIMETYPE,
         ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE)
         .withValue(ContactsContract.CommonDataKinds.Note.NOTE, strNote).build());

        /* Organization */
        ops.add(ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, strPosition)
                .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, "회사명")
                .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, strTeam)
                .withValue(ContactsContract.CommonDataKinds.Organization.JOB_DESCRIPTION, strPosition)
                .withValue(ContactsContract.CommonDataKinds.Organization.TYPE,
                        ContactsContract.CommonDataKinds.Organization.TYPE_WORK).build());

        // Photo Image
        MemoryCache memoryCache = new MemoryCache();
        Bitmap cachedImage = memoryCache.get(strPhoto);
        if (cachedImage != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            cachedImage.compress(CompressFormat.JPEG, 100, baos);

            byte[] b = baos.toByteArray();

            ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
                    .withValue(ContactsContract.CommonDataKinds.Photo.DATA15, b).build());
        }

        try {
            // 연락처 제공자는 applyBatch()에서의 모든 작업을 하나의 트랜잭션으로서 수행
            context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }

    }
}



블로그 이미지

Link2Me

,