728x90

안드로이드 Checkbox

- isChecked () 메소드를 통해 체크 박스가 선택되어 있는지를 확인할 수 있다.
- setChecked () 메소드를 통해 상태값을 지정할 수 있다.
- 체크된 상태가 변경될 경우에는 onCheckedChanged () 메소드가 자동으로 호출되어 후작업을 하게 된다.
  onCheckedChanged ()메소드에 실제 동작을 추가하기 위해서는
  setOnCheckedChangeListener () 메소드를 통해 OnCheckedChangeListener 인터페이스를 구현하여 추가하면 된다.

checkBox1 = (CheckBox) findViewById(R.id.checkBox1);
if(checkBox1.isChecked()){
    textView1.setText("체크박스 선택됨");
} else {
    textView1.setText("체크박스 선택 취소됨");
}


ListView Checkbox 에 대한 자료를 검색하면 http://www.mysamplecode.com/2012/07/android-listview-checkbox-example.html 를 많이들 추천하고 있다.

테스트 해 본 결과 만족스러운 결과를 얻을 수 없었다.


ListView 에 Checkbox를 포함시켜서 스크롤 시키면 체크박스 상태가 변한다.

Adapter 는 자체적으로 메모리를 유연하게 사용하기 위해서 자동으로 화면 바깥쪽으로 벗어난 Adapter 객체를 반대쪽으로 이동시켜 재사용한다.


체크박스 객체 자체를 ArrayList, Hashmap 등에 담아서 사용해 봤는데 해결이 안된다.

많은 자료들을 검색하고 테스트 해보는 중인데 어렵다. 3주동안 해결을 못했다는 내용도 있더라.


스크롤뷰 문제만 빼면 나머지 부분은 성공(?)된 기능이라고 볼 수 있다.

- 전체 선택 및 해제 : 잘 됨

- 부분 선택 및 해제 : 스크롤이 안된 상태에서는 정상, 스크롤 하면 데이터가 꼬여버림.


서버에서 가져온 데이터를 ArrayList 에 저장하고 저장된 데이터 중에서 체크된 것만 뽑아내는 로직이다.

이 로직은 검증용으로 활용하기 위한 용도다.

StringBuffer responseText = new StringBuffer();
responseText.append("The following were checkBoxState...");
for(int i=0;i<arrayList.size();i++){
    if(arrayList.get(i).isCheckBoxState() == true){
        responseText.append("\n" + arrayList.get(i).getUid());
    }
}
Toast.makeText(getApplicationContext(),responseText, Toast.LENGTH_LONG).show();
 



해결방안 : 아래 소스를 http://blog.naver.com/akdlqnsl/220469620347 참조/수정하면 해결 가능하다.

이 소스를 통해서 체크박스 선택에 문제가 없다는 걸 확인했다.

다른 게시글에 나온 방법들로는 해결을 못했다. 내가 구현한 ListViewAdatapter 생성자 구현방법이 위 예제에 나온 것들과 달라서 에러가 발생하더라.

체크박스 처리하는 부분을 구현해보면서 ListViewAdapter 에 대한 이해를 좀 더 할 수 있게 되었다.

ArrayList, Hashmap 에 대한 것을 더 많이 테스트해보고 완벽하게 이해하는 것이 다양한 응용을 가능하게 할 수 있을 거 같다.


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 ====


import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.telephony.PhoneNumberUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

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

import java.util.ArrayList;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "AppPermission";
    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;
    private Button btn_chatting;
    private Button btn_msg;

    private ArrayList<Address_Item> arrayList = null; // 데이터 리스트
    static ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter
    ProgressDialog mProgressDialog;

    static boolean isMSG = false;
    static boolean isAll = false;
    static boolean flag = false;
    static CheckBox check_all;

    RelativeLayout relative1;
    RelativeLayout relative2;

    Context context;

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

        isMSG = false;
        isAll = false;

        editText = (EditText) findViewById(R.id.et_text01);

        // Relative layout 정의
        relative1 = (RelativeLayout) findViewById(R.id.list_view_relative1);
        relative2 = (RelativeLayout) findViewById(R.id.list_view_relative2);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        arrayList = new ArrayList<Address_Item>(); // ArrayList 생성

        listView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","" )
                String postParams = builder.build().getEncodedQuery();
                new getJSONData().execute(Value.IPADDRESS + "/get_json.php", postParams);
                hideSoftKeyboard();
            }
        });

        if (isMSG == false) {
            relative1.setVisibility(View.VISIBLE);
            relative2.setVisibility(View.GONE);
        } else if (isMSG == true) {
            relative1.setVisibility(View.GONE);
            relative2.setVisibility(View.VISIBLE);
        }

        btn_msg = (Button) findViewById(R.id.lv_btn_msg);
        btn_msg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = true;
                relative1.setVisibility(View.GONE);
                relative2.setVisibility(View.VISIBLE);
                listViewAdapter.notifyDataSetChanged();
            }
        });

        // all checkbox
        check_all = (CheckBox) findViewById(R.id.lv_checkbox_all);
        check_all.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (check_all.isChecked() == false) {
                    isAll = false;
                    listViewAdapter.setAllChecked(check_all.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                } else if (check_all.isChecked() == true) {
                    isAll = true;
                    listViewAdapter.setAllChecked(check_all.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                }
            }
        });

        Button calcel = (Button) findViewById(R.id.btn_cancle);
        calcel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = false;
                relative1.setVisibility(View.VISIBLE);
                relative2.setVisibility(View.GONE);
                listViewAdapter.setAllChecked(false);
                check_all.setChecked(false); // 전체 선택 체크박스 해제
                listViewAdapter.notifyDataSetChanged();
            }
        });

        Button send = (Button) findViewById(R.id.btn_send);
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                StringBuffer responseText = new StringBuffer();
                responseText.append("The following were selected...");
                for(int i=0;i<arrayList.size();i++){
                    if(arrayList.get(i).isCheckBoxState() == true){
                        responseText.append("\n" + arrayList.get(i).getUid());
                    }
                }
                Toast.makeText(getApplicationContext(),responseText, Toast.LENGTH_LONG).show();
            }
        });

    }

    private void hideSoftKeyboard() { // 소프트 키보드 숨기기
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(editText.getApplicationWindowToken(), 0);
    }

    class getJSONData 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;
            showList();
            mProgressDialog.dismiss();
        }
    }

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

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

            arrayList.clear(); // 서버에서 가져온 데이터 초기화
            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);

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

            runOnUiThread(new Runnable() { // 화면에 반영하기 위하여 runOnUiThread()를 호출하여 실시간 갱신한다.
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    class ViewHolder {
        public LinearLayout child_layout;
        public ImageView profile_Image;
        public TextView tv_name;
        public TextView tv_mobileNO;
        public TextView tv_officeNO;
        public ImageView child_btn;
        public CheckBox checkbox;
    }

    private class ListViewAdapter extends BaseAdapter {
        ImageLoader imageLoader;
        Context mContext;

        public ListViewAdapter(Context context) {
            this.mContext = context;
            imageLoader = new ImageLoader(context);
        }

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

        // 아이템 데이터 추가를 위한 메소드
        public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO, boolean checkItem_flag) {
            Address_Item item = new Address_Item();
            item.setProfile_image(profile_image);
            item.setUid(uid);
            item.setName(name);
            item.setMobileNO(mobileNO);
            item.setOfficeNO(officeNO);
            item.setCheckBoxState(checkItem_flag);

            arrayList.add(item);
        }
    }


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

        @Override
        public Object getItem(int position) {
            return arrayList.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 int checkBoxPosition = position;
            final Integer index = Integer.valueOf(position);

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            final Address_Item addressItem = arrayList.get(position);

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

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

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

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
                viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
                viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
                viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
                viewHolder.child_btn = (ImageView) convertView.findViewById(R.id.child_Btn);
                viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.list_cell_checkbox);
                if(viewHolder.checkbox != null){
                    // 체크박스의 상태 변화를 체크한다.
                    viewHolder.checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            int getPosition = (Integer) buttonView.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                            arrayList.get(index).setSelected(buttonView.isChecked());
                        }
                    });
                }

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

            // 아이템 내 각 위젯에 데이터 반영
            if (addressItem.getProfile_image().equals("")) {
                final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
                viewHolder.profile_Image.setImageBitmap(Base_Profile);
            } else {
                String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
                imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
            }

            viewHolder.tv_name.setText(addressItem.getName());
            viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()));
            viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()));

            if (isMSG == false) {
                viewHolder.child_btn.setVisibility(View.VISIBLE);
                viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                       
                    }
                });
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ===" + addressItem.getUid(), Toast.LENGTH_SHORT).show();
                    }
                });
            } else {
                if (isMSG == true) {
                    convertView.setOnClickListener(null); // 체크박스가 활성화되면 convertView 클릭 리스너 비활성화
                    viewHolder.child_btn.setVisibility(View.GONE);
                    //convertView.setClickable(false);
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.
                    //viewHolder.checkbox.setChecked(false);

                }
            }

            // 체크된 아이템인지 판단할 boolean 변수
            for(int i=0; i < arrayList.size();i++){
                if(arrayList.get(i).isCheckBoxState() == true){
                    viewHolder.checkbox.
setCheckBoxState(true);
                } else {
                    viewHolder.checkbox.
setCheckBoxState(false);
                }
                notifyDataSetChanged();
            }

            return convertView;
        }

}

728x90
블로그 이미지

Link2Me

,
728x90

객체 지향 프로그래밍(Object-Oriented Programming: OPP)은 데이터와 절차를 하나의 덩어리(객체)로 묶어서 생각하는 방법이다.
객체 안의 변수를 필드(field)라 부르고, 객체 안의 함수를 메소드(method)라 부른다.
하나의 객체(Object)는 필드와 메소들 이루어진 소프트웨어의 묶음이다.

자바는 객체 지향 기법을 지원하지만 객체 지향을 사용한다고 해서 절차 지향 프로그램을 하지 않는다는 것이 아니다. 실제로 객체의 내부를 설계할 때는 절차 지향 프로그래밍을 사용한다.


절차지향 언어의 특징

- 절차 지향 프로그래밍이란, 프로그래밍을 순서, 즉 절차에 따라 프로그래밍하는 것을 의미한다.

- 절차 지향의 대표적인 프로그램은 C언어이다.


객체지향 언어의 특징

- 사람(주체)가 바라본 대상(사람, 사물, 객체 등)을 객체(object)라고 한다.

- 객체지향 언어는 사람이 일상적으로 보고, 느끼고, 생각하는 관점에서 프로그래밍을 하는 것이다.

- 객체 지향 프로그램의 첫번째 단계는 객체의 추출이다.

  객체는 각각 속성과 기능들을 가진다.


Class (클래스)

- 클래스는 설계서(붕어빵틀)다. 즉, 실체가 아니라 타입이다.

- Java의 모든 클래스는 Object 클래스를 상속한다.

- new 명령으로 해당 클래스 타입의 객체를 생성한 후, 그 객체에 데이터를 저장하고, 그 객체의 멤버 메소드를 실행하는 것이다.

  Contacts phonebook; // 클래스 타입 변수 선언

  phonebook = new Contacts(); // 전화번호부 실제 객체 생성 (변수 = new 클래스명();)

  인스턴스 변수는 반드시 객체를 생성해야만 호출이 가능하다.

  참조 변수 지정과 객체 생성을 한번에 할 수 있다.

  Contacts phonebook = new Contacts(); // 전화번호부 객체 생성, Class 타입 변수명 = new 생성자 ();

  phonebook .number = "010-0000-0000"; // Class 인스턴스를 생성한 다음 참조변수 phonebook 을 통해야 한다.

  참조변수의 특징은 메모리에 생성되어 있는 인스턴스의 위치값을 지정한다.

- final 메소드는 Class 상속되는 것은 허용하되, 메소드의 오버라이딩은 허용하지 않겠다는 의미다.


메서드(함수)가 호출되면 수행에 필요한 만큼의 메모리 스택에 할당을 받는다.

메서드(함수)가 수행을 마치고 나면 사용했던 메모리를 반환하고 스택에서 제거된다.


같은 Class 에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.

static 메서드(함수)는 인스턴스 메서드를 호출할 수 없다.

인스턴스 메서드(함수)는 인스턴스 메서드와 클래스 메서드 모두 인스턴스 생성없이 바로 호출이 가능하다.

클래스 변수가 인스턴스 변수를 참조, 호출하기 위해서는 반드시 객체를 생성해야 한다.


참조형 변수는 메모리에 생성되어 있는 인스턴스의 위치값을 저장한다.


 Premitive Type 변수

 - byte, short, int, long, float, double, char, boolean 8가지 타입을 제공한다.

 - type 마다 size가 fix 되어 있다.
 - 기본값이 있으므로 null이 존재하지 않는다.
 - generic을 이용할 때 필요한 wraper class가 존재한다. (Integer, Long..)
 - 컴파일 시, 크기를 초과하면 에러가 발생한다.

 Reference Type 변수

 - 기본형을 제외한 모든 타입은 Reference Type 이며 java.lang.Object 를 상속 받는다.

 - 참조형에는 Class Type, Interface Type, Array Type이 있다.

 - 기본값은 아무런 참조 정보가 없으므로 null을 리턴한다.

 - 생성된 객체는 Heap Memory 에 저장된다.

 primitive type의 == 연산은 값을 비교한다.
 reference type의 == 연산은 주소를 비교한다.
 리터럴 방식을 통해 만든 변수와, new를 통해 저장되는 메모리가 다르다.
 두 변수의 참조 메모리가 서로 다르다는 뜻이다.
 reference 타입에서 설명한 대로 new로 만든 객체는 Heap 영역에 저장이 되고,
 리터럴 방식으로 만들어진 객체는 String Constant Pool영역에 저장되기 때문이다.

 primitive Type은 value를 전달한다.
 reference Type은 reference를 전달한다.


 String은 reference Type 으로 다른 reference Type과 다른점은 JVM에서 String constant pool을 이용해 메모리 관리를 한다.

 - if(str == input) {} // str 이 String 변수이면 == 로 비교할 수 없다.

 - if(str.equals(input)) 으로 비교해야 한다.

 변수는 사용하기 전에 선언되어야 한다. 변수의 선언이란 "이름"과 "타입"을 정해주는 것이다.

 Java 에서 변수의 선언은 Class 내에 선언한다.

 main 함수는 C언어에서는 1개만 존재하지만, Java 에서는 Class 파일마다 존재해도 된다.

 - 메서드 내부에 선언된 변수는 그 메서드 내부에서만 사용 가능하다.

 - 메서드 외부(클래스 내부)에 선언된 변수는 클래스 내에서 사용 가능하다.



생성자(Constructor)는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드다.

- 생성자의 이름은 클래스 이름과 같아야 한다.

- 생성자는 리턴값이 없다.

- 생성자는 매개변수가 없는 것도 있고, 있는 것도 있다.

- 클래스가 객체 생성될 때 맨 처음 호출되는 것이다.

- 모든 클래스는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.

  만약, 생성자가 하나도 정의되어 있지 않는 경우,

  컴파일러는 자동으로 매개변수가 없는(no-parameter) 기본 생성자를 추가하여 컴파일 한다.

- 사용자가 생성자를 구현해주면 no-parameter 생성자는 자동으로 추가되지 않는다.

  생성자를 직접 만들어 줄 경우에는 no-parameter 생성자를 함께 만들어주는 것을 습관화하는 것이 좋다.

- 모든 서브 클래스의 생성자는 먼저 수퍼 클래스의 생성자를 호출한다.

  1. super(매개변수)를 통해 명시적으로 호출을 해준다.

  2. super(매개변수)를 명시적으로 지정하지 않으면 자동으로 no-parameter 생성자가 호출된다.


※ 흔한 오류

    수퍼클래스에 no-parameter 생성자가 없는데, 서브 클래스의 생성자에서 super() 호출을 안해주는 경우


super
- 자신을 가리키는 키워드가 this 라면, 부모를 가리키는 키워드는 super
- super() 는 부모의 생성자를 의미한다.
- 부모의 생성자를 임의로 호출하지 않으면, 부모 class의 기본(no-parameter) 생성자가 자동으로 호출된다.
- 명시적으로 호출하지 않으면 super()를 컴파일 시 자동으로 추가된다.

public class Contacts {
    public Contacts(){  // default 생성자 = no-parameter 생성자     

       // 클래스 인스턴스를 메모리에 생성한다.

       // 주로 인스턴스 변수를 초기화한다.

    }   
}


Contacts pbook = new Contacts(); // default  생성자 호출

default 생성자 호출시 default 생성자가 안 만들어져 있으면 오류가 발생한다.



public class Value {

   // Java에서는 클래스 외부에는 변수를 선언할 수 없다.

    public static final String IPADDRESS = "http://10.100.100.2";  // 서버의 IP주소

    String number;

}


getDbData(Value.IPADDRESS + "/get_json.php");


- 클래스 정적(static) 변수는 객체 생성없이 직접 사용이 가능하다.

- 클래스이름.메서드이름(Value.IPADDRESS) 과 같은 식으로 호출이 가능하다.

- 클래스 정적 변수는 모든 인스턴스가 하나의 저장공간을 가지므로 항상 공통된 값을 갖는다.

  모든 인스턴스에 공통된 값을 유지해야 하는 경우에 static을 붙인다.

  프로그램 실행시 static 지정자가 붙은 멤버들은 단 한번의 실행으로 메모리에 생성된다.

  프로그램 종료 시 메모리에서 소멸된다.


접근 제한자

접근 제한자는 Class 의 멤버에 대해 다른 Class 에서 접근하는 것을 제한하는 용도로 사용된다.

- public : 모든 Class 에서 접근 가능

- protected : 같은 패키지에 있는 Class, 그리고 다른 패키지의 자손 클래스에서 접근이 가능

- default : 같은 패키지에 있는 Class 들만 접근 가능

  String name; // 아무런 접근 지정자도 지정되지 않았으므로 default 접근 지정자다.

- private : 같은 Class 내에서만 접근 가능


지시자

클래스 내부

동일 패키지

상속받은 클래스

이외의 영역

 private


 X

X

X

 default


X

X

 protected


X

 public



- 클래스의 멤버 변수에 접근할 때에는 기본적으로 getter/setter 메서드를 사용하여 간접 접근을 해야 한다.


클래스 상속(inheritance)

public class SearchActivity extends Activity {
}

public class MainActivity extends AppCompatActivity {

       // ....

}

public class Child extends Parent {
}

- 상속이란 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.

- 상속을 받는다는 것은 조상 클래스(Activity)를 확장(extends)한다는 의미로 해석할 수 있다.

  상속은 강한 연결고리를 형성한다.

  복합관계는 강한 연결고리를 형성하지 않는다.

- 자바는 단일 상속만 허용하고 다중 상속은 허용하지 않는다.

  C++ 에서는 여러 클래스로부터 상속받는 다중상속을 허용한다.

  public class Child extends Parent1, Parent2 { // 이와 같은 표현은 허용하지 않는다.

  }

- 모든 상속 계층의 최상위에는 Object 클래스가 위치한다.

- 생성자와 초기화 블럭은  상속되지 않는다. 멤버만 상속된다.

- 클래스 속성과 기능들을 다른 클래스에서 재사용할 수 있다.

- 중복 코드를 제거하면 가독성이 높아지고 개발 시간을 단축할 수 있다.

- 각 클래스의 공통점을 가진 클래스를 부모 클래스로 만든다.


추상(abstract) 클래스

- 미완성 설계도

- 추상 클래스로 인스턴스는 생성할 수 없다.

  추상 클래스는 상속을 통해서 자손 클래스에 의해서만 완성할 수 있다.

- 추상 클래스는 abstract 를 붙이기만 하면 된다.



오버라이딩(overriding)

조상(상위) 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다.

- 상위 클래스에 정의된 이름, 반환형, 매개변수 선언까지 완전히 동일한 메소드를 하위 클래스에서 다시 정의하는 것이다.

- 하위 클래스에 정의된 메소드에 의해 상위 클래스의 메소드는 가리워진다.

overriding 의 조건

- 이름이 같아야 한다.

- 매개변수가 같아야 한다.

- 리턴타입이 같아야 한다.

- static, final, private 가 지정된 메소드는 오바라이딩 불가

  super.toString(); 처럼 부모 클래스 메소드를 호출하거나, private 를 protected 로 변수명을 변경하면 상속이 가능하다.



다형성(Polymorphism)

- 수퍼클래스 타입의 참조변수가 서브클래스 타입의 객체를 참조할 수 있다.

   Computer test = new Notebook();



method overloading

같은 클래스 내에서 변수와 마찬가지로 메소드도 서로 구별할 수 있어야 한다.

하지만 자바에서는 같은 클래스 내에서 아래와 같은 조건이면 동일 이름을 가진 메소드를 정의할 수 있다.

1. 메소드 이름이 같아야 한다.

2. 메소드이 매개변수의 개수 또는 매개변수의 타입이 달라야 한다.

3. 매개변수는 같고 리턴타입이 다른 것은 오버로딩이 성립되지 않는다.

오버로딩의 조건

  1. 메서드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
    (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)

 



출처: https://java.ihoney.pe.kr/99 [허니몬(Honeymon)의 자바guru]

오버로딩의 조건

  1. 메서드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
    (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)

 



출처: https://java.ihoney.pe.kr/99 [허니몬(Honeymon)의 자바guru]


오버로딩의 조건

  1. 메서드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 매개변수는 같고 리턴타입이 다른 경우는 오버로딩이 성립되지 않는다.
    (리턴타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.)


출처: https://java.ihoney.pe.kr/99 [허니몬(Honeymon)의 자바guru]


패키지(package)

package com.tistory.link2me.addresschart;


- 패키지란, 클래스의 묶음이다.

- 하나의 소스파일에는 최상단에 단 한번의 패키지 선언을 허용한다.

- 패키지명은 선언시 반드시 소문자로 선언해야 한다.

- 모든 클래스는 반드시 하나의 패키지에 속해야 한다.

- 패키지는 점(.)을 구분자로 하여 계층 구조로 구성할 수 있다.

- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.


import 문

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;


- 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야 한다.

- 하지만, 클래스의 코드를 작성하기 전에 import 문으로 사용하고자 하는 클래스의 패키지를

  미리 명시해 주면 소스코드에 사용되는 클래스 이름에서 패키지명을 생략할 수 있다.


728x90

'안드로이드 > Java 문법' 카테고리의 다른 글

[Java] 정보은닉과 캡슐화  (0) 2017.10.16
자바 기본 데이터형과 크기  (0) 2017.10.11
ArrayList and HashMap  (0) 2017.03.06
Java foreach 문  (0) 2017.03.01
자바 배열과 기본 문법  (0) 2016.07.11
블로그 이미지

Link2Me

,
728x90

뷰플리퍼는 여러 개의 뷰를 한 화면에서 보여줄 수 있는 기능을 가진 컨테이너이다.
그래서 여러 개의 뷰를 작성하고 이 뷰들을 한 장 한 장 넘기면서 볼 수 있다.
- 여러 개의 뷰를 한 화면에서 보여줄 수 있다
- 이전, 이후 뷰를 볼 수 있는 메소드가 준비되어 있다


테스트 환경 : Android Studio 2.3.1


view_flipper.xml

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

    <LinearLayout
        android:id="@+id/screenIdx"
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal">
    </LinearLayout>

    <ViewFlipper
        android:id="@+id/flipper"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


activity_main.xml

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

    <com.tistory.link2me.viewfliper.View_Flipper
        android:id="@+id/screen"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp" />

</RelativeLayout>


View_Flipper.java

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ViewFlipper;

public class View_Flipper extends LinearLayout implements View.OnTouchListener {

    public static int cntIndex= 10; // 뷰플리퍼를 구현할 화면의 갯수
    LinearLayout indexLayout; // 현재 화면의 인덱스를 표현하기 위한 레이아웃
    ImageView[] indexImgs; // 현재 화면의 인덱스를 나타내는 이미지들
    View[] views; // 뷰플리퍼에 사용할 뷰
    ViewFlipper viewFlipper;

    float startX; // 손가락으로 화면을 터치했을 때 x좌표값을 저장하기 위한 변수
    float endX; // 화면에서 손가락을 뗏을 때 x좌표값을 저장하기 위한 변수

    int currentIndex = 0; //현재의 화면 인덱스 값

    public View_Flipper(Context context) {
        super(context);
        init(context);
    }

    public View_Flipper(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public void init(Context context){
        setBackgroundColor(0xffbbbbff);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.view_flipper, this, true);

        indexLayout = (LinearLayout) findViewById(R.id.screenIdx);
        viewFlipper = (ViewFlipper) findViewById(R.id.flipper);
        viewFlipper.setOnTouchListener(this);

        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        params.leftMargin = 50;

        indexImgs = new ImageView[cntIndex];
        views = new ImageView[cntIndex];

        //index 이미지와 뷰플리퍼 화면을 만드는 과정
        for(int i=0; i < cntIndex; i++){
            indexImgs[i] = new ImageView(context);

            if (i == currentIndex){
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_on);
            }else {
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_off);
            }

            //indexImgs[i].setPadding();
            indexLayout.addView(indexImgs[i], params);

            ImageView currentView = new ImageView(context);
            currentView.setImageResource(R.mipmap.gametitle_01+i);
            views[i] = currentView;

            viewFlipper.addView(views[i]);
        }
    }

    //인덱스 이미지를 수정
    public void modifyIndex(){
        for(int i = 0; i <cntIndex; i++){
            if(i == currentIndex) {
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_on);
            }else{
                indexImgs[i].setImageResource(android.R.drawable.btn_star_big_off);
            }
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if(v != viewFlipper) return false;

        if(event.getAction() == MotionEvent.ACTION_DOWN){
            startX = event.getX();
        } else if(event.getAction() == MotionEvent.ACTION_UP){
            endX = event.getX();
            if(startX < endX){ // 왼쪽에서 오른쪽으로 터치
                //viewFlipper에 애니메이션 설정
                viewFlipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.left_in));
                viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.right_out));

                //인덱스체크 - 첫번째화면이면 동작없음
                if(currentIndex > 0){
                    viewFlipper.showPrevious();
                    currentIndex--;
                    modifyIndex();
                }
            } else if(startX > endX){ // 오른쪽에서 왼쪽으로 터치
                viewFlipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.right_in));
                viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.left_out));

                //인덱스체크 - 마지막화면이면 동작없음
                if(currentIndex < (cntIndex-1)){
                    viewFlipper.showNext();
                    currentIndex++;
                    modifyIndex();
                }
            }
        }
        return true;
    }
}


나머지 파일을 포함해서 실행해 볼 수 있는 전체 파일

view_flipper.zip


728x90

'안드로이드 > Layout' 카테고리의 다른 글

Android Fragment 기본 예제  (0) 2018.09.11
FloatingActionButton(FAB)  (0) 2018.08.15
LinearLayout weight  (0) 2018.03.01
Fragment 화면 이동  (0) 2017.03.26
안드로이드 Layout  (0) 2016.07.23
블로그 이미지

Link2Me

,
728x90

안드로이드 PUSH 서버, 메신저 만드는 방법을 검색해보다가 알게된 내용을 적어둔다.


아파치 웹서버

nginx

  • 쓰레드/프로세스 기반 구조
  • 클라이언트의 요청이 들어오면 쓰레드를 생성
  • 사용자가 많으면 많은 쓰레드 생성 ==> 메모리 및 CPU 낭비, Context-Switching Overhead 발생
  • 비동기(async) 이벤트(ioctl, send, recv, epoll)기반 구조
  • 다수의 연결을 효과적으로 처리
  • 대부분의 코어 모듈이 Apache보다 적은 리소스로 더 빠르게 동작


안드로이드 앱의 구성 요소와 데이터 전달




  • 제작 : 기획부터 개발까지 하나의 서비스를 온전히 만들어내는 것
  • 개발 : 결정된 기획과 디자인을 바탕으로 소프트웨어를 구현하는 것
  • 고객이 불편한 부분을 이해하고 체감해야 한다.
  • UI 쓰레드는 UI만 처리하게 하라.
  • 다양한 화면 해상도를 지원하라
  • 화면 해상도에 맞게 다양한 리소스를 제공하라.
  • 네트워크는 항상 느리다고 가정하라.
  • 다양한 하드웨어에 대비하라. (터치스크린, 키보드, 센서)
  • 좋은 코드를 작성하라 (CPU 사용량 낮고, 메모리 사용량 낮고, 가독성은 높게)
    - 필요한 기능을 최대한 간결히 작성하라
    - 클래스/메소드/변수 이름을 명확히 하라.
    - 코드를 어렵게/복잡하게 작성하지 마라.
  • 객체는 최대한 적게 생성하라
    - static 메소드를 사용해서 객체 생성을 줄일 수 있다.
    - 반복문에서는 객체 생성을 줄여야 한다.
    - 객체를 재사용한다.
  • 불필요한 코드를 제거하라
    - 쓸데없는 캐스팅을 줄여야 한다.
  • 메소드는 정적(static) 메소드로 선언하라.
  • 클래스 내에서는 Getter 와 Setter 를 사용해서 변수에 접근하지 말라.
  • 값이 고정된 변수는 상수로 선언하라.
  • enum은 되도록 사용하지 말아라.
  • 부동소수형은 되도록 사용하지 말아라.
    정수형이 부동소수형보다 일반적으로 2배 더 빠르다.
  • 네티브 코드는 되도록 사용하지 말아라.
    - 디바이스에 따라 호환되지 않을 수 있다.
    - 디버깅을 하는 것이 쉽지 않다.
  • 자바의 코드 노하우가 반드시 안드로이드의 코드 노하우는 아니다.
    - 가독성을 위해 중첩 클래스를 사용하지 마라.
    - 클래스는 되도록 작게 생성하라.
  • 버전은 어떤 범위까지 지정할 것인가?
    - 지원하려는 플랫폼 구버전의 범위를 지정하는 것은 재량이지만,
      최신 버전의 플랫폼을 지원하는 것은 기본으로 한다.
  • 오픈소스로 공개된 많은 커스텀된 GUI 컴포넌트 코드들이 있으며, 이를 구글 검색 등을 통해 쉽게 찾아낼 수 있다. https://github.com/wasabeef/awesome-android-ui
  • 안드로이드 내부에서 기본 이미지를 제공하지만 매우 제한적이다.
    구글이 Material Design 풍의 아이콘 셋들을 오픈소스로 공개했다.
    https://github.com/google/material-design-icons/releases
  • 안드로이드 앱 개발할 시에는 다양한 버전에서의 테스트가 필요한데, Genymotion을 이용하면 쉽게 해결할 수 있다. https://www.genymotion.com/
  • 출시할 때에는 꼭 앱이 디버깅용이 아닌 출시용으로 빌드가 됐는지 확인하고, 서버도 디버깅 모드로 배포되었는지 여부를 확인한다.
  • 모든 준비가 완료되었다면, Play 스토어 등과 같은 모바일 어플리케이션 마켓 플레이스에 배포를 진행하면 된다.


728x90
블로그 이미지

Link2Me

,
728x90

// Back 버튼을 눌러도 종료되지 않게 처리
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if( keyCode == KeyEvent.KEYCODE_BACK ) {
        return true;
    }       
    return super.onKeyDown(keyCode, event);
 }


// Back 버튼을 누르면 어플 종료여부 확인 처리
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if( keyCode == KeyEvent.KEYCODE_BACK ) {
        new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle("Quit").setMessage("어플을 종료하시겠습니까?").setPositiveButton("Yes", new DialogInterface.OnClickListener() {
            @Override
            public void onClick( DialogInterface dialog, int which) {
                moveTaskToBack(true); // 본 Activity finish후 다른 Activity가 뜨는 걸 방지.
                finish();
                //application 프로세스를 강제 종료
                android.os.Process.killProcess(android.os.Process.myPid() );
            }
        }).setNegativeButton( "No", null ).show();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}




728x90
블로그 이미지

Link2Me

,
728x90

Android Studio 에서 연락처 저장하는 기능으로 구현에 필요한 내용은 모두 적어둔다.

서버에서 읽어온 사진 이미지까지 저장할 수 있도록 했다.

서버에서 사진 이미지를 읽어오고 나면 Cache에 저장했으므로

MemoryCache memoryCache = new MemoryCache();
Bitmap cachedImage = memoryCache.get(strPhoto);

를 했는데도 불구하고 null 값이 나온다.


연락처(Contacts)에 전화번호를 저장하는 기능은 검사하여 있으면 삭제후 다시 신규 저장하는 방식으로 구현되어 있는데, 이런 로직보다는 데이터가 있으면 비교하여 달라진 것이 있으면 Update 하고 동일하면 패스하는 로직으로 구현하는 것이 바람직하다. 이런 구현방식은 개발자의 몫이기 때문에 여기에는 적어놓지 않는다.


import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.util.Log;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class Contacts {
    public Contacts(){
        
    }
    
     // 전화번호부에 존재하는 데이터인지 여부 검사
    // 표시 이름과 휴대폰번호를 기준으로 ContactId 구하기
    public long
ContactsIDExistCheck(ContentResolver cr, String display_name, String number) {
        long rawContactId = -1;

        Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
        String[] projection = { PhoneLookup._ID, PhoneLookup.TYPE, PhoneLookup.DISPLAY_NAME };
        Cursor cursor = null;
        try {
            cursor = cr.query(contactUri, projection, null, null, null);
            if (cursor.moveToFirst()) {
                do {
                    String PhoneName = cursor.getString(cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME));
                    if (display_name.equals(PhoneName)) {
                        rawContactId = cursor.getLong(cursor.getColumnIndex(PhoneLookup._ID));
                        System.out.println(" ContactId = " + rawContactId + " 성명 : " + PhoneName);
                    }
                } while (cursor.moveToNext());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return rawContactId;
    }
   

    public void ContactsIDdelete(ContentResolver cr, Context context, Integer contactId){
        // 전화번호부 삭제
        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
           
        //remove contact from raw_contact table
        ops.add(ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI).
        withSelection(ContactsContract.RawContacts.CONTACT_ID + "=?", new String[]{String.valueOf(contactId)}).
        build());              
               
        try {
            cr.applyBatch(ContactsContract.AUTHORITY, ops);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void ContactsIDinsert(ContentResolver cr, Context context, String strName, String strMobileNO) {

        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.DISPLAY_NAME, strName)
                .build());

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

        try {
            cr.applyBatch(ContactsContract.AUTHORITY, ops);
            Toast.makeText(context, "연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }
           
    }
    
    public void ContactsIDinsert(ContentResolver cr, Context context, String strName, String strMobileNO, String strOfficeNO, String strEmail, String strPhoto) {

        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.DISPLAY_NAME, strName)
                .build());

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

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

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

        MemoryCache memoryCache = new MemoryCache();
        Bitmap cachedImage = memoryCache.get(strPhoto);
        System.out.println("cachedImage ===" + cachedImage);
        if (cachedImage != null) {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            cachedImage.compress(Bitmap.CompressFormat.JPEG, 60, bos);

            byte[] bytes = bos.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, bytes)
                    .build());
            try {
                bos.flush();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            cr.applyBatch(ContactsContract.AUTHORITY, ops);
            Toast.makeText(context, "연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (Exception e) {
            Log.e("ContactsAdder", "Exceptoin encoutered while inserting contact: " + e);
        }          
    }   
}


메모리 캐쉬 파일을 수정했다.

import android.graphics.Bitmap;
import android.util.Log;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class MemoryCache {
    private static final String TAG = "MemoryCache";
    public static  Map<String, Bitmap> cache= Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering
    private long size=0;//current allocated size
    private long limit=1000000;//max memory in bytes

    public MemoryCache(){
        //use 25% of available heap size
        setLimit(Runtime.getRuntime().maxMemory()/4);
    }

    public void setLimit(long new_limit){
        limit=new_limit;
        Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
    }

    public static Bitmap get(String id){
        try{
            if(!cache.containsKey(id))
                return null;
            return cache.get(id);
        }catch(NullPointerException ex){
            ex.printStackTrace();
            return null;
        }
    }

    public void put(String id, Bitmap bitmap){
        try{
            if(cache.containsKey(id))
                size-=getSizeInBytes(cache.get(id));
            cache.put(id, bitmap);
            size+=getSizeInBytes(bitmap);
            checkSize();
        }catch(Throwable th){
            th.printStackTrace();
        }
    }

    private void checkSize() {
        Log.i(TAG, "cache size="+size+" length="+cache.size());
        if(size>limit){
            Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated
            while(iter.hasNext()){
                Entry<String, Bitmap> entry=iter.next();
                size-=getSizeInBytes(entry.getValue());
                iter.remove();
                if(size<=limit)
                    break;
            }
            Log.i(TAG, "Clean cache. New size "+cache.size());
        }
    }

    public void clear() {
        try{
            //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78
            cache.clear();
            size=0;
        }catch(NullPointerException ex){
            ex.printStackTrace();
        }
    }

    long getSizeInBytes(Bitmap bitmap) {
        if(bitmap==null)
            return 0;
        return bitmap.getRowBytes() * bitmap.getHeight();
    }
}


MainActivity.java 파일 내에 ListViewAdapter 부분

 private class ListViewAdapter extends BaseAdapter {
    ImageLoader imageLoader;
    Context context;

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

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

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

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

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

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

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

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

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
            viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
            viewHolder.child_btn = (ImageView) convertView.findViewById(R.id.child_Btn);

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

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        final Address_Item addressItem = arrayList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        // 선택된 row의 데이터를 표시한다. 표시될 view는 address_itemm.xml 의 각 항목을 이용하여 표시한다.
        //System.out.println("imageurl==" + addressItem.getProfile_image());
        if(addressItem.getProfile_image().equals("")){
            final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
            viewHolder.profile_Image.setImageBitmap(Base_Profile);
        } else {
            String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
            imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
        }

        viewHolder.tv_name.setText(addressItem.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()));

        final String[] items ={"휴대폰 전화걸기","사무실전화 걸기", "연락처 저장"};
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("해당작업을 선택하세요");
        builder.setItems(items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show();
                switch (which){
                    case 0:
                        if(addressItem.getMobileNO().length() ==0){
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog1 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getMobileNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                   // TODO: Consider calling
                                                   if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                                                       requestPermissions(new String[]{Manifest.permission.CALL_PHONE},1000);
                                                   }

                                                   return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog1.show();
                        break;
                    case 1:
                        if(addressItem.getOfficeNO().length() ==0){
                            Toast.makeText(context, "전화걸 사무실 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog2 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getOfficeNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                    // TODO: Consider calling
                                                    return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog2.show();
                        break;
                    case 2:
                        // 연락처 저장 함수 호출
                        AlertDialog.Builder SaveContact = new AlertDialog.Builder(context);
                        SaveContact.setMessage("전화번호를 저장하시겠습니까?");
                        DialogInterface.OnClickListener save = new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                int rawContactId = 0;
                                Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                                ContentResolver cr = getContentResolver();
                                String strContactName = addressItem.getName();
                                String strMobileNO = addressItem.getMobileNO();
                                String strofficeNO =addressItem.getOfficeNO();
                                String strEmail ="";
                                String strPhoto ="";
                                if(addressItem.getProfile_image().length()>0){
                                    strPhoto =Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
                                }
                                //System.out.println("strPhoto ==="+ strPhoto);

                                rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName,strMobileNO);
                                if(rawContactId > 0){
                                    // 기존 전화번호가 존재하면 삭제하고 새로 입력
                                    phonebook.ContactsIDdelete(cr, context, rawContactId);
                                }
                                phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                            }
                        };

                        DialogInterface.OnClickListener cancel = new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        };
                        SaveContact.setPositiveButton("저장", save);
                        SaveContact.setNegativeButton("취소", cancel);
                        SaveContact.show();
                        break;
                }
            }
        });
        builder.create();

        viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                builder.show();
            }
        });

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ==="+ addressItem.getUid(), Toast.LENGTH_SHORT).show();
            }
        });

        return convertView;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
        Address_Item item = new Address_Item();
        item.setProfile_image(profile_image);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        arrayList.add(item);
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1000) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this, "권한 요청을 승인했습니다.", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "권한 요청을 거부했습니다.", Toast.LENGTH_SHORT).show();
            }
        }
    }

}


728x90
블로그 이미지

Link2Me

,
728x90

같은 PC에서 Android Studio 와 Eclipse 를 같이 사용하다보니 SDK 를 같이 사용하면 문제가 생기더라.

그래서 SDK 를 각각 설치해서 사용하는데 간혹 Android Studio 에서 만든 예제가 Eclipse 기반 SDK를 사용하는 것이 있는지 헷갈린다.

그래서 아예 경로를 변경해버렸다.





이렇게 변경하면 나중에 백업할 때 파일 용량이 커서 문제가 좀 되기도 한다.


Eclipse 나 Android Studio 모두 설치된 경로를 그대로 백업하고 나중에 같은 경로에 폴더를 설치하면 그냥 동작한다.


728x90
블로그 이미지

Link2Me

,
728x90

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

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


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

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

    }
});


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

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

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

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

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


728x90
블로그 이미지

Link2Me

,
728x90

Activity 화면 갱신처리를 하는 방법때문에 다양한 걸 검색해서 찾았다.


public void onClick (View v){
    Intent intent = getIntent();
    finish();
    startActivity(intent);
}


이 코드는 현재 화면을 다시 Refresh 하는 코드다.

따라서 검색어를 입력하고 검색결과를 찾는 경우에는 부적합하다.

데이터를 삭제하고 화면을 갱신해야 하는 경우에는 유용하다.


The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.

서버에서 데이터 및 사진이미지를 가져와서 ListView 에 갱신처리하는 걸 구현하는데 이런 메시지가 나오면서 간혹 앱이 강제종료된다.

무엇이 문제일까?

 AsyncTask.execute(new Runnable() 에서 처리한 결과가 listViewAdapter.notifyDataSetChanged(); 보다 늦게 나오면서 생기는 문제였다.

 결국 이런 로직을 사용하면 안된다는 것이다.

 처리순서가 순차적으로 되지 않고 아래 코드가 먼저 실행되면서 발생하는 문제였다.

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

            personDataItem.clear(); // 서버에서 가져온 데이터 초기화
            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 Bitmap myIcon = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);

                final String photoURL = Value.IPADDRESS + "/photos/" + uid + ".jpg";
                System.out.println("Server Photo URL ===" + photoURL);
                AsyncTask.execute(new Runnable() {
                    @Override
                    public void run() {
                        if(isExists(photoURL) == true){
                            Bitmap rBmp = loadWebImage(photoURL);
                            System.out.println("Server Photo Image ==="+ uid +" | "+ rBmp);
                            System.out.println("Photo Image height ==="+ rBmp.getHeight());
                            int width = (int)(rBmp.getWidth() * 160.0 / rBmp.getHeight());
                            rBmp = Bitmap.createScaledBitmap(rBmp, width, 160,true);

                            // 서버에서 가져온 데이터 저장
                            listViewAdapter.addItem(rBmp,uid,name,mobileNO,officeNO);
                        } else {
                            // 서버에 사진 이미지가 없으면 기본 이미지를 포함한 정보 저장
                            listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
                        }
                    }
                });

            }

            runOnUiThread(new Runnable() { // 화면에 반영하기 위하여 runOnUiThread()를 호출하여 실시간 갱신한다.
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });

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

    }


도움이 되는 내용이 있어서 적어둔다.

http://www.vogella.com/tutorials/AndroidListView/article.html




728x90
블로그 이미지

Link2Me

,
728x90

Custom ListViewAdapter 에서 세부적으로 처리할 여러가지 기능 등을 테스트하고 있다.

전화걸기 기능 추가했는데 eclipse 버전 낮은 것에서 동작하던 것이 Android Studio 버전이 높은 걸 선택하면서 Permission 체크하는 기능을 추가해야만 동작하는 거 같다.


import android.Manifest;

private class ListViewAdapter extends BaseAdapter {
    ImageLoader imageLoader;
    Context context;

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

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

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

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

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

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

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

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

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
            viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
            viewHolder.chid_btn = (Button) convertView.findViewById(R.id.child_Btn);

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

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        final PesonData pesonData = arrayList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        // 선택된 row의 데이터를 표시한다. 표시될 view는 person_item.xml 의 각 항목을 이용하여 표시한다.
        System.out.println("imageurl==" + pesonData.getProfile_image());
        if (pesonData.getProfile_image().equals("")) {
            final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
            viewHolder.profile_Image.setImageBitmap(Base_Profile);
        } else {
            final String photoURL = Value.IPADDRESS + "/photos/" + pesonData.getProfile_image();
            imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
        }

        viewHolder.tv_name.setText(pesonData.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

        viewHolder.chid_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 버튼이 눌렸을 때 다음 액션 처리를 여기에 코드를 적어서 처리한다.
                Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                vibe.vibrate(50);

                AlertDialog showdialog = new AlertDialog.Builder(MainActivity.this)
                        .setTitle(pesonData.getName())
                        .setMessage(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()) + " 통화하시겠습니까?")
                        .setPositiveButton("예",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog, int which) {

                                        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(pesonData.getMobileNO())));
                                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                        if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                            // TODO: Consider calling
                                            //    ActivityCompat#requestPermissions
                                            // here to request the missing permissions, and then overriding
                                            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                            //                                          int[] grantResults)
                                            // to handle the case where the user grants the permission. See the documentation
                                            // for ActivityCompat#requestPermissions for more details.
                                            return;
                                        }
                                        startActivity(intent);
                                    }
                                })
                        .setNegativeButton(
                                "아니오",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog,int which) {
                                        dialog.dismiss();
                                    }
                                }).create();
                showdialog.show();
            }
        });

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ==="+ pesonData.getUid(), Toast.LENGTH_SHORT).show();
            }
        });

        return convertView;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
        PesonData item = new PesonData();
        item.setProfile_image(profile_image);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        arrayList.add(item);
    }
}


2017.4.30 수정 보완

휴대폰 전화걸기, 사무실전화걸기, 연락처 저장 선택메뉴 추가

연락처 저장시 사진 이미지 저장은 아직 해결 못한 상태


class ViewHolder {
    public LinearLayout child_layout;
    public ImageView profile_Image;
    public TextView tv_name;
    public TextView tv_mobileNO;
    public TextView tv_officeNO;
    public ImageView child_btn;
}

private class ListViewAdapter extends BaseAdapter {
    ImageLoader imageLoader;
    Context context;

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

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

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

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

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

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

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

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

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
            viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
            viewHolder.child_btn = (ImageView) convertView.findViewById(R.id.child_Btn);

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

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        final Address_Item addressItem = arrayList.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        // 선택된 row의 데이터를 표시한다. 표시될 view는 address_itemm.xml 의 각 항목 이용 표시한다.
        System.out.println("imageurl==" + addressItem.getProfile_image());
        if(addressItem.getProfile_image().equals("")){
            final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
            viewHolder.profile_Image.setImageBitmap(Base_Profile);
        } else {
            final String photoURL = Value.IPADDRESS + "/photos/" + addressItem.getProfile_image();
            imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
        }

        viewHolder.tv_name.setText(addressItem.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()));

        final String[] items ={"휴대폰 전화걸기","사무실전화 걸기", "연락처 저장"};
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("해당작업을 선택하세요");
        builder.setItems(items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(context, items[which] + "선택했습니다.", Toast.LENGTH_SHORT).show();
                switch (which){
                    case 0:
                        if(addressItem.getMobileNO().length() ==0){
                            Toast.makeText(context, "전화걸 휴대폰 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog1 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getMobileNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getMobileNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                    // TODO: Consider calling
                                                    return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog1.show();
                        break;
                    case 1:
                        if(addressItem.getOfficeNO().length() ==0){
                            Toast.makeText(context, "전화걸 사무실 번호가 없습니다.",Toast.LENGTH_SHORT).show();
                            break;
                        }
                        AlertDialog dialog2 = new AlertDialog.Builder(context)
                                .setTitle(addressItem.getName())
                                .setMessage(PhoneNumberUtils.formatNumber(addressItem.getOfficeNO()) + " 통화하시겠습니까?")
                                .setPositiveButton("예",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog, int which) {

                                                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + PhoneNumberUtils.formatNumber(addressItem.getOfficeNO())));
                                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                                if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                                                    // TODO: Consider calling
                                                    return;
                                                }
                                                startActivity(intent);
                                            }
                                        })
                                .setNegativeButton(
                                        "아니오",
                                        new DialogInterface.OnClickListener() {
                                            public void onClick(DialogInterface dialog,int which) {
                                                dialog.dismiss();
                                            }
                                        }).create();
                        dialog2.show();
                        break;
                    case 2:
                        // 연락처 저장 함수 호출
                        AlertDialog.Builder SaveContact = new AlertDialog.Builder(context);
                        SaveContact.setMessage("전화번호를 저장하시겠습니까?");
                        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                int rawContactId = 0;
                                Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                                ContentResolver cr = getContentResolver();
                                String strContactName = addressItem.getName();
                                String strMobileNO = addressItem.getMobileNO();
                                String strofficeNO =addressItem.getOfficeNO();
                                String strEmail ="";
                                String strPhoto ="";

                                rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName);
                                if(rawContactId > 0){
                                    // 기존 전화번호가 존재하면 삭제하고 새로 입력
                                    System.out.println("ContactID is exist !! Need delete!!");
                                    phonebook.ContactsIDdelete(cr, context, rawContactId);
                                }
                                phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                            }
                        };

                        DialogInterface.OnClickListener cancel = new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        };
                        SaveContact.setPositiveButton("저장", listener);
                        SaveContact.setNegativeButton("취소", cancel);
                        SaveContact.show();
                        break;
                }
            }
        });
        builder.create();

        viewHolder.child_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 버튼이 눌렸을 때 다음 액션 처리를 여기에 코드를 적어서 처리한다.
                Vibrator vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
                vibe.vibrate(50);

                builder.show();
            }
        });

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "상세보기를 눌렀습니다 ==="+ addressItem.getUid(), Toast.LENGTH_SHORT).show();
            }
        });

        return convertView;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
        Address_Item item = new Address_Item();
        item.setProfile_image(profile_image);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        arrayList.add(item);
    }
}


연락처 저장 소스는 http://stackoverflow.com/questions/4744187/how-to-add-new-contacts-in-android 참조하면 나온다.

728x90
블로그 이미지

Link2Me

,
728x90

이미지 다운로드하는 파일을 구해서 테스트 해보니 사진 이미지 크기가 일정하지 않는 문제점이 있다.

서버에서 읽어들이는 파일 사이즈 크기를 알아보기 위해서

System.out.println("Original Photo Image width ==="+ width_tmp);

System.out.println("Original Photo Image height ==="+ height_tmp);

를 추가해서 size 를 확인한다.


 private Bitmap decodeFile(File f){
    try {
        //decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f),null,options);

        //Find the correct scale value. It should be the power of 2.
        final int REQUIRED_SIZE= 70;
        int width_tmp=options.outWidth;
        int height_tmp=options.outHeight;
        System.out.println("Original Photo Image width ==="+ width_tmp);
        System.out.println("Original Photo Image height ==="+ height_tmp);
        int scale=1;
        while(true){
            if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                break;
            width_tmp/=2;
            height_tmp/=2;
            scale*=2;
        }

        //decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize=scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}


수정한 코드

private Bitmap decodeFile(File f){
    try {
            //decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f),null,options);

            //Find the correct scale value. It should be the power of 2.
            // final int REQUIRED_SIZE= 70;
            int width_tmp=options.outWidth;
            int height_tmp=options.outHeight;
            System.out.println("Original Photo Image width ==="+ width_tmp);
            System.out.println("Original Photo Image height ==="+ height_tmp);

            /***********수정 코드 *********************************************/

            // 높이 160 은 원하는 크기로 수정 사용하면 됨
            int width = (int)(width_tmp * 160.0 / height_tmp);
            Bitmap orgImage = BitmapFactory.decodeStream(new FileInputStream(f));
            return  Bitmap.createScaledBitmap(orgImage, width, 160,true);
    } catch (FileNotFoundException e) {}
    return null;
}


이미지 처리 성능 향상을 시키기 위해 가장 먼저 이미지의 크기를 확인하자.
앱에서 사용자에게 보여주는 사진 이미지는 매우 작아 해상도가 좋을 필요가 없으므로 서버에서 크기를 작게 만들고 압축률을 변경하면 클라이언트로 전달되는 파일 크기가 작아져 전송속도가 빨라지고, 체감속도가 증가한다.


ImageView의 setImageResource() 메소드 사용을 피하자
이 메소드를 사용하면 이미지를 읽고 디코딩 하는 작업을 UI Thread에서 하기 때문에 응답시간이 느려진다.

setImageDrawable 메소드나 setImageBitmap 메소드를 사용하고, BitmapFactory 클래스를 사용하는 것이 좋다.



이미지 다루는 파일 전체 소스는 CustomListView 게시글(http://link2me.tistory.com/1255)에도 있고 http://www.androidbegin.com/tutorial/android-json-parse-images-and-texts-tutorial/ 에서도 구할 수 있음


이미지 로딩 라이브러리 Glide 사용하면 위와 같은 걸 고민할 필요가 없더라.

http://link2me.tistory.com/1498 참조


728x90
블로그 이미지

Link2Me

,
728x90
웹서버 상에 이미지를 다운로드하여 안드로이드 어플에서 사진정보를 보여주기 위한 기능을 테스트를 위해서 구글링을 열심히 하면서 이미지 처리하는 예제를 제대로 하나 찾았다.

http://www.androidbegin.com/tutorial/android-json-parse-images-and-texts-tutorial/

Eclipse 기반에서 만들어진 것이라서 Android Studio 로 Import 해서 기능이 잘 동작하는 걸 확인하고 나서 본격적인 분석에 들어갔다.

지금까지 내가 테스트하고 있는 것의 잘못된 부분이 뭔지 찾기 위해 좀 더 이것저것 테스트를 했고 이제 제대로 된 결과가 나오는 걸 확인했다.

아쉬운 점은 구글링으로 구한 이미지 축소하는 소스가 크기가 일정하지 못하다는 단점이 있다.


검색하면 이미지 캐싱 기능이 되는 소스는 많이 검색된다.

문제는 서버에 있는 이미지를 어떻게 가져다가 활용하는지에 대한 예재를 찾기가 어려웠다.

텍스트 정보만 가져와서 출력하는 기능 구현은 쉽게 가능했다.


PersonData.java

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

    public PesonData() {
    }

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

    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;
    }
}


MainActivity.java

 public class MainActivity extends AppCompatActivity {

    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;

    private ArrayList<PesonData> arrayList = null; // 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter
    ProgressDialog mProgressDialog;

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

        editText = (EditText) findViewById(R.id.et_text01);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        arrayList = new ArrayList<PesonData>(); // ArrayList 생성

        listView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅

        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                //System.out.println("search ==="+ editText.getText().toString().trim());
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","")
                final String postParams = builder.build().getEncodedQuery();

                new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
                hideSoftKeyboard();
            }
        });
    }

    private void hideSoftKeyboard(){ // 소프트 키보드 숨기기
        InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE );
        imm.hideSoftInputFromWindow( editText.getApplicationWindowToken(), 0 );
    }

    class getJSONData 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;
            showList();
            mProgressDialog.dismiss();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS="result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "name";
    private static final String TAG_MobileNO ="mobileNO";
    private static final String TAG_OfficeNO ="officeNO";

    private static final String TAG_Image = "photo"; // 이미지 필드

    JSONArray peoples = null;

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

            arrayList.clear(); // 서버에서 가져온 데이터 초기화
            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);


                // 서버에서 가져온 데이터 저장
                listViewAdapter.addItem(photoURL,uid,name,mobileNO,officeNO);
            }

            runOnUiThread(new Runnable() {

                // 화면에 반영하기 위하여 runOnUiThread()를 호출하여 실시간 갱신한다.
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });

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

    class ViewHolder {
        public LinearLayout child_layout;
        public ImageView profile_Image;
        public TextView tv_name;
        public TextView tv_mobileNO;
        public TextView tv_officeNO;
        public Button chid_btn;
    }

    private class ListViewAdapter extends BaseAdapter {
        ImageLoader imageLoader;
        Context context;

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

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

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

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

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

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

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

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

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.profile_Image = (ImageView) convertView.findViewById(R.id.profile_Image);
                viewHolder.tv_name = (TextView) convertView.findViewById(R.id.child_name);
                viewHolder.tv_mobileNO = (TextView) convertView.findViewById(R.id.child_mobileNO);
                viewHolder.tv_officeNO = (TextView) convertView.findViewById(R.id.child_officeNO);
                viewHolder.chid_btn = (Button) convertView.findViewById(R.id.child_Btn);

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

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            final PesonData pesonData = arrayList.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            // 선택된 row의 데이터를 표시한다. 표시될 view는 person_item.xml 의 각 항목을 이용 표시한다.

            System.out.println("imageurl==" + pesonData.getProfile_image());
            if(pesonData.getProfile_image().equals("")){
                final Bitmap Base_Profile = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);
                viewHolder.profile_Image.setImageBitmap(Base_Profile);
            } else {
                final String photoURL = Value.IPADDRESS + "/photos/" + pesonData.getProfile_image();
                imageLoader.DisplayImage(photoURL, viewHolder.profile_Image);
            }

            viewHolder.tv_name.setText(pesonData.getName());
            viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
            viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

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

                }
            });

            return convertView;
        }

        // 아이템 데이터 추가를 위한 메소드
        public void addItem(String profile_image, String uid, String name, String mobileNO, String officeNO){
            PesonData item = new PesonData();
            item.setProfile_image(profile_image);
            item.setUid(uid);
            item.setName(name);
            item.setMobileNO(mobileNO);
            item.setOfficeNO(officeNO);

            arrayList.add(item);
        }
    }
}


PHPComm.java

 public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {
            Thread.sleep(100);
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            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");
                if (cookieString != null) {
                   conn.setRequestProperty("Cookie", cookieString);
                   Log.e("PHP_getCookie", cookieString);
                 }
                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(postParams);
                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()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out : " + sb.toString());
            return sb.toString().trim();
            // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }
    }

    public static boolean isExists(String URLName) {
        // 서버에 파일의 존재 유무 파악
        try {
            HttpURLConnection.setFollowRedirects(false);
            HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
            con.setRequestMethod("HEAD");
            if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    public static Bitmap autoresize_decodeResource(Resources res, int resId, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInAutoSize(options, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInAutoSize(BitmapFactory.Options options, int reqHeight) {
        // 원본 이미지의 높이와 너비
        final int height = options.outHeight;
        final int width = options.outWidth;

        float ratio = width / height;
        int reqWidth = Math.round((float) ratio * width);

        int inSampleSize = 1;
        if (height > reqHeight) {
            final int halfHeight = height / 2;
            while ((halfHeight / inSampleSize) > reqHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}



728x90
블로그 이미지

Link2Me

,
728x90

웹서버 상에 이미지를 다운로드하여 안드로이드 어플에서 사진정보를 보여주기 위한 기능을 테스트 중이다.


먼저 웹서버에 이미지 파일이 존재하는지 유무를 체크하는 기능부터 테스트를 했다.


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

        personDataItem.clear(); // 서버에서 가져온 데이터 초기화
        for(int i=0;i<peoples.length();i++){
            JSONObject c = peoples.getJSONObject(i);
            final String uid = c.getString(TAG_UID);
            String name = c.getString(TAG_NAME);
            String mobileNO = c.getString(TAG_MobileNO);
            String officeNO = c.getString(TAG_OfficeNO);
            Drawable myIcon = getResources().getDrawable(R.mipmap.ic_launcher);

            final String photoURL = Value.IPADDRESS + "/photos/" + uid + ".jpg";
            System.out.println("Server Photo URL ===" + photoURL);
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    if(isExists(photoURL) == true){
                        System.out.println("Server Photo Image ===" + uid);
                    }
                }
            });

            // 서버에서 가져온 데이터 저장
            listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 갱신된 데이터 내역을 어댑터에 알려줌
                listViewAdapter.notifyDataSetChanged();
            }
        });

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

 private static boolean isExists(String URLName) {
    // 서버에 파일의 존재 유무 파악
    try {
        HttpURLConnection.setFollowRedirects(false);
        HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
        con.setRequestMethod("HEAD");
        if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
    return false;
}


for 문 내에서 순차적으로 결과가 나올 것 같지만 AsyncTask.execute(new Runnable() 에서 처리는 나중에 처리됨을 확인할 수 있다.


사진 이미지가 있는 경우와 없는 경우 결과가 false 로 처리됨을 확인할 수 있다.


코드 구현시 결과가 순차적으로 실행되지 않는다는 점이 중요한 이슈가 될 수도 있다.

순서대로 처리되어야 할 로직인 경우에는 구현 로직을 고민해봐야 한다.


이제 서버에서 파일을 안드로이드폰으로 다운로드하는 함수를 만들어야 한다.

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

        personDataItem.clear(); // 서버에서 가져온 데이터 초기화
        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 Bitmap myIcon = PHPComm.autoresize_decodeResource(getResources(), R.mipmap.photo_base, 160);

            final String photoURL = Value.IPADDRESS + "/photos/" + uid + ".jpg";
            System.out.println("Server Photo URL ===" + photoURL);
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    if(isExists(photoURL) == true){
                        Bitmap rBmp = loadWebImage(photoURL);
                        System.out.println("Server Photo Image ==="+ uid +" | "+ rBmp);
                        System.out.println("Photo Image height ==="+ rBmp.getHeight());
                        int width = (int)(rBmp.getWidth() * 160.0 / rBmp.getHeight());
                        rBmp = Bitmap.createScaledBitmap(rBmp, width, 160,true);

                        // 서버에서 가져온 데이터 저장
                        listViewAdapter.addItem(rBmp,uid,name,mobileNO,officeNO);
                    } else {
                        // 서버에 사진 이미지가 없으면 기본 이미지를 포함한 정보 저장
                        listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
                    }
                }
            });

            // 서버에서 가져온 데이터 저장
            //listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 갱신된 데이터 내역을 어댑터에 알려줌
                listViewAdapter.notifyDataSetChanged();
            }
        });

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

private static boolean isExists(String URLName) {
    // 서버에 파일의 존재 유무 파악
    try {
        HttpURLConnection.setFollowRedirects(false);
        HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
        con.setRequestMethod("HEAD");
        if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
    return false;
}

Bitmap mBmp = null;
// 서버에서 전달 받은 데이터를 Bitmap 이미지에 저장
public Bitmap loadWebImage(String URLName) {
    try {
        // 스트림 데이터를 Bitmap 에 저장
        InputStream is = new URL(URLName).openStream();
        mBmp = BitmapFactory.decodeStream(is);
        is.close();
    } catch(Exception e) {
        Log.d("tag", "Image Stream error.");
        return null;
    }
    return mBmp;
}

 public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {  
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            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");
                if (cookieString != null) {
                   conn.setRequestProperty("Cookie", cookieString);
                   Log.e("PHP_getCookie", cookieString);
                 }
                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(postParams);
                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()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out : " + sb.toString());
            return sb.toString().trim();  

        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }
    }

    public static Bitmap autoresize_decodeResource(Resources res, int resId, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInAutoSize(options, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInAutoSize(BitmapFactory.Options options, int reqHeight) {
        // 원본 이미지의 높이와 너비
        final int height = options.outHeight;
        final int width = options.outWidth;

        float ratio = width / height;
        int reqWidth = Math.round((float) ratio * width);

        int inSampleSize = 1;
        if (height > reqHeight) {
            final int halfHeight = height / 2;
            while ((halfHeight / inSampleSize) > reqHeight) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}


현재까지 테스트에 사용되었던 파일을 첨부한다.

이미지 테스트를 하면서 Bitmap 가져오기를 하면서 PersonData.java 파일 내용도 수정되었다.


json_demo_getImage.zip



이미지 사이즈를 줄이는 것까지는 성공했는데, 문제는 매번 접속할 때마다 새롭게 이미지 파일을 받아오는 문제점을 해결해야 한다.

http://theeye.pe.kr/archives/1309

http://www.androidbegin.com/tutorial/android-json-parse-images-and-texts-tutorial/

에 좋은 내용의 예제가 있다. 이것을 이용하여 코드를 다시 수정하고 테스트를 해봐야겠다.


728x90
블로그 이미지

Link2Me

,
728x90

ListViewAdapter 를 만드는 과정이다.

Android Studio 가 제공하는 자동완성 기능을 이용하여 기본적인 골격을 만들어보자.




private class ListViewAdapter extends BaseAdapter {

    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        return null;
    }
}


기본 코드가 만들어진다. 

연결된 정보들이 없어서 return null, return 0 를 반환하는 걸로 기본 코드가 만들어진 것을 확인할 수 있다.

이 코드에 살을 붙여서 완성된 코드를 만들어야 한다.


리스트뷰는 일반 위젯(TextView등)이 아니라 선택 위젯이기 때문에 직접 데이터를 설정할수가 없다.
Adapter를 이용해야 하고, 이 Adapter에서 만들어주는 getView()를 이용해서 아이템을 표시한다.
리스트뷰는 어댑터를 사용하여 데이터를 표시하는 View이다.




먼저 MainActivity Class 전역변수를 선언한다.

private ArrayList<PesonData> personDataItem = null; // 데이터 리스트
private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter


class ViewHolder {
    public LinearLayout child_layout;
    public ImageView personal_imageView;
    public TextView tv_name;
    public TextView tv_mobileNO;
    public TextView tv_officeNO;
    public Button chid_btn;
}

private class ListViewAdapter extends BaseAdapter {

    public ListViewAdapter() {
    }

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

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

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

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

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.person_item,viewGroup,false);

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

            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            viewHolder.personal_imageView = (ImageView) view.findViewById(R.id.personal_Image);
            viewHolder.tv_name = (TextView) view.findViewById(R.id.child_name);
            viewHolder.tv_mobileNO = (TextView) view.findViewById(R.id.child_mobileNO);
            viewHolder.tv_officeNO = (TextView) view.findViewById(R.id.child_officeNO);
            viewHolder.chid_btn = (Button) view.findViewById(R.id.child_Btn);

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

        // PersonData 에서 position 에 위치한 데이터 참조 획득
        PesonData pesonData = personDataItem.get(position);

        // 아이템 내 각 위젯에 데이터 반영
        viewHolder.personal_imageView.setImageDrawable(pesonData.getIcon());
        viewHolder.tv_name.setText(pesonData.getName());
        viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
        viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

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

            }
        });

        return view;
    }

    // 아이템 데이터 추가를 위한 메소드
    public void addItem(Drawable icon, String uid, String name, String mobileNO, String officeNO){
        PesonData item = new PesonData();
        item.setIcon(icon);
        item.setUid(uid);
        item.setName(name);
        item.setMobileNO(mobileNO);
        item.setOfficeNO(officeNO);

        personDataItem.add(item);
    }
}
 


이제 전체 코드를 보자.

안드로이드가 기본 제공하는 getView() 와 비교할 수 있는 부분은 별도 색상으로 표기를 했다.


import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.telephony.PhoneNumberUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

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

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;

    private ArrayList<PesonData> personDataItem = null; // 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter

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

        listView = (ListView) findViewById(R.id.my_listView);
        editText = (EditText) findViewById(R.id.et_text01);

        // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        personDataItem = new ArrayList<PesonData>(); // ArrayList 생성

        // Adapter 생성
        listViewAdapter = new ListViewAdapter();

        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
       
        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","")
                String postParams = builder.build().getEncodedQuery();
                new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
            }
        });
    }
   
    class getJSONData extends AsyncTask<String, Void, String> {
        @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;
            showList();
        }
    }

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

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

            personDataItem.clear(); // 서버에서 가져온 데이터 초기화
            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                String uid = c.getString(TAG_UID);
                String name = c.getString(TAG_NAME);
                String mobileNO = c.getString(TAG_MobileNO);
                String officeNO = c.getString(TAG_OfficeNO);
                Drawable myIcon = getResources().getDrawable(R.mipmap.ic_launcher); // 이미지는 임시처리

                // 서버에서 가져온 데이터 저장
                listViewAdapter.addItem(myIcon,uid,name,mobileNO,officeNO);
            }

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    listViewAdapter.notifyDataSetChanged();
                }
            });

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

    class ViewHolder {
        public LinearLayout child_layout;
        public ImageView personal_imageView;
        public TextView tv_name;
        public TextView tv_mobileNO;
        public TextView tv_officeNO;
        public Button chid_btn;
    }

    private class ListViewAdapter extends BaseAdapter {

        public ListViewAdapter() {
        }

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

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

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

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

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

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.person_item,viewGroup,false);

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

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.personal_imageView = (ImageView) view.findViewById(R.id.personal_Image);
                viewHolder.tv_name = (TextView) view.findViewById(R.id.child_name);
                viewHolder.tv_mobileNO = (TextView) view.findViewById(R.id.child_mobileNO);
                viewHolder.tv_officeNO = (TextView) view.findViewById(R.id.child_officeNO);
                viewHolder.chid_btn = (Button) view.findViewById(R.id.child_Btn);

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

            // PersonData 에서 position 에 위치한 데이터 참조 획득
            PesonData pesonData = personDataItem.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            viewHolder.personal_imageView.setImageDrawable(pesonData.getIcon());
            viewHolder.tv_name.setText(pesonData.getName());
            viewHolder.tv_mobileNO.setText(PhoneNumberUtils.formatNumber(pesonData.getMobileNO()));
            viewHolder.tv_officeNO.setText(PhoneNumberUtils.formatNumber(pesonData.getOfficeNO()));

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

                }
            });

            return view;
        }

        // 아이템 데이터 추가를 위한 메소드
        public void addItem(Drawable icon, String uid, String name, String mobileNO, String officeNO){
            PesonData item = new PesonData();
            item.setIcon(icon);
            item.setUid(uid);
            item.setName(name);
            item.setMobileNO(mobileNO);
            item.setOfficeNO(officeNO);

            personDataItem.add(item);
        }
    }

}
 


잘 정리된 블로그를 참조하면서 테스트 해보고 내 스타일로 수정하고 컴파일 하면서 완성도를 높여가며 테스트중이다. 실제 서버와 연동하여 실제 활용할 수 있는 코드 중심으로 작성해보는 중이다.


ListViewAdapter 를 Activity Class 단위로 생성해서 사용하는 것이 다른 Class 파일을 만드는 것보다 이해도가 좋은 거 같다.


추가 테스트가 필요한 사항

- 사진 이미지를 가져와서 보여주는 것 해결사항

- 버튼 이미지 또는 이미지를 누르면 Event 처리하는 기능


첨부 코드는 http://link2me.tistory.com/1250 파일에서 달라진 파일만 첨부했다.


json_demo_customListView.zip


728x90
블로그 이미지

Link2Me

,
728x90

simple_list_item_1 arrayAdapter는 아이템이 TextView만으로 구성되고 데이터가 String 배열이기 때문에 ArrayAdapter 만으로 그 기능을 제공할 수 있다.

Custom ListView 를 제공하기 위해서는 xml 을 정의하고 ArrayAdapter 기능을 확장해야 한다.


사용자 정의형 ListView 에 뿌려질 xml 를 정의하고, Inflation 해주어야 한다.

 item 을 위한 xml 정의(Layout)
 사용자에게 보여질 xml 를 만들고 나서, 실제 Data를 연결할 Class 를 정의한다.
 person_item.xml 과 PersonData.java 파일을 생성한다. 


먼저, layout 폴더에 person_item.xml 파일을 추가한다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/child_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp" >

    <ImageView
        android:id="@+id/mImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@mipmap/ic_launcher"
        android:layout_weight="1"
        />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_weight="4"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="2dip"
            android:text="이름"
            android:textSize="20sp"
            android:textStyle="bold"
            />

        <TextView
        android:id="@+id/mobileNO"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:text="휴대폰번호"
        android:textStyle="bold"
        />

        <TextView
            android:id="@+id/officeNO"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text="휴대폰번호"
            android:textStyle="bold"
            />

    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_weight="1"
        android:orientation="vertical" >

        <Button
            android:id="@+id/childListBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center_horizontal|center_vertical"
            android:text="Btn"
            android:textAllCaps="false"
            />
    </LinearLayout>
</LinearLayout>



PersonData.java Class 파일을 생성한다.

그리고 변수를 추가한다.

public class PesonData {
    // PersonData 정보를 담고 있는 객체 생성
    private int profile_image;
    private String uid;
    private String name;
    private String mobile;

    public PesonData(){  // 생성자
    }


변수가 private으로 되어있으므로 외부에서 접근할수있도록 메소드를 만들어주어야 한다.

데이터 없는 생성자 다음 빈공간에서 Alt + Insert 키를 누르고, Gatter and Setter 를 선택하면 자동으로 코드가 추가된다.

다음으로 Generator 를 생성하기 위해 Alt + Insert 키를 누르고 아래 그림처럼 따라 한다.


추가하면 위와 같이 나온다.

이 파일에 대한 전체 코드는 아래와 같다.


public class PesonData {
    // PersonData 정보를 담고 있는 객체 생성
    private int profile_image;
    private String uid;
    private String name;
    private String mobile;

    public PesonData(){  // 생성자
    }

    public PesonData(int profile_image, String uid, String name, String mobile) {
        this.profile_image = profile_image;
        this.uid = uid;
        this.name = name;
        this.mobile = mobile;
    }

    public int getProfile_image() {
        return profile_image;
    }

    public void setProfile_image(int 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 getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}
 


내용을 구성하는 PersonData Class까지 작성했다.

코드는 컴파일하면서 에러가 발생하면 다시 수정하면서 작성해서 명칭 등이 약간 변경되었다.

실제 코드를 ListViewAdapter 만드는 방법 게시글(http://link2me.tistory.com/1252)에 올려두었다.


728x90
블로그 이미지

Link2Me

,
728x90

서버에서 데이터를 가져오는 방법은 http://link2me.tistory.com/1247 참조하면 된다.

실제 파일을 연습해볼 수 있는 샘플을 첨부한다. 첨부파일은 링크 게시물에 올려진 파일과는 다르고 아래 기술되는 내용의 연속성 측면에서 새롭게 작성한 파일이다. 이 파일을 기반으로 수정해서 ListData 를 만들것이다.


json_demo_getServerData.zip

json_demo_server_PHP.zip



이제 서버에서 가져온 데이터를 ArrayList 에 추가하는 방법에 대해 알아보자.


private ArrayList<String> personData = new ArrayList<String>();

로 선언을 해준다.

그리고 기본 ArrayAdapter 를 통해 ListView 에 뿌려준다.


activity_main.xml

 <?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/et_text01"
            android:hint="검색어"
            android:layout_weight="8"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/btn_search"
            android:text="검색"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <ListView
        android:id="@+id/lv_listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

 MainActivity.java

 import android.app.Activity;
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.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

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

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    public SharedPreferences settings;
    private ListView listView; // 리스트뷰
    private EditText editText;
    private Button btn_search;

    private ArrayList<String> personData = null; // 데이터 리스트
    private ArrayAdapter<String> arrayAdapter = null; // 리스트뷰에 사용되는 ArrayAdapter

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

        listView = (ListView) findViewById(R.id.lv_listView);
        editText = (EditText) findViewById(R.id.et_text01);

        personData = new ArrayList<String>(); // ArrayList 생성

        // ArrayAdapter 생성
        arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,personData);

        listView.setAdapter(arrayAdapter); // 어댑터를 리스트뷰에 세팅

        btn_search = (Button) findViewById(R.id.btn_search);
        btn_search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
                Uri.Builder builder = new Uri.Builder()
                        .appendQueryParameter("search", editText.getText().toString().trim())
                        .appendQueryParameter("idx", "2"); // settings.getString("idx","")
                String postParams = builder.build().getEncodedQuery();
                new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
            }
        });
    }

    class getJSONData extends AsyncTask<String, Void, String> {
        @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;
            showList();
        }
    }

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

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

            personData.clear(); // 서버에서 가져올 때마다 초기화

            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                String uid = c.getString(TAG_UID);
                String name = c.getString(TAG_NAME);
                String mobile = c.getString(TAG_Mobile);

                String data = uid + " : " + name + " | " + mobile;
                personData.add(data); // 데이터 추가
            }

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // 갱신된 데이터 내역을 어댑터에 알려줌
                    arrayAdapter.notifyDataSetChanged();
                }
            });

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


위 코드는 ListView에 단순한 형태로 뿌려주는 기능만 테스트를 했다.


ListView Row를 눌렀을 때 이벤트를 거는 걸 처리해보자.

보통 AdapterView.onItemClickListener 를 이용하여, 이벤트 처리를 해준다.

onItemClickListener는 CallBack Listener 다. CallBack 이란 ListView의 한 Row(행)을 클릭했을 때, 시스템에서 클릭에 반응하여 다시 사용자에게 알려주는 것을 말한다.
ListView의 Row 를 클릭했을 때 그 Row에 해당하는 adapterView, view, position 등을 시스템에서 사용자에게 전달해주는 것이다.

listView.setOnItemClickListener(itemClickListener); // 이벤트 처리

// ListView에서 아이템을 누르면 반응하는 이벤트이기때문에 onItemClickListener를 ListView에 추가

private AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
        String textView = (String) adapterView.getAdapter().getItem(position);
        Toast.makeText(MainActivity.this, textView, Toast.LENGTH_SHORT).show();

        Toast.makeText(getApplicationContext(), "" + position, Toast.LENGTH_SHORT).show();
    }
};


position 은 선택한 Row의 index 값을 반환한다.



이미지, 텍스트, 버튼을 혼용하는 View 를 ListView 에 보여주는 방법은 많은 테스트를 해보고 작성하련다.

ArrayList<String> 형을 ArrayList<PersonData> 제네릭 형으로 변경하고, 데이터를 add 하면 에러가 발생한다.


private ArrayList<String> personData = null; // 데이터 리스트
private ArrayAdapter<String> arrayAdapter = null; // 리스트뷰에 사용되는 ArrayAdapter

 private ArrayList<PersonData> personData = null; // 데이터 리스트
private ArrayAdapter<PersonData> arrayAdapter = null; // 리스트뷰에 사용되는 ArrayAdapter


arrayAdapter = new ArrayAdapter<PersonData>(this,android.R.layout.simple_list_item_1,personData); AdapterView 에서 처리를 못해서 생기는 에러로 보인다.

728x90
블로그 이미지

Link2Me

,
728x90

비동기 처리를 하기 위해서는 별도의 Thread 를 생성하여 public void run()  메소드를 구현하면 되지만
안드로이드에서는 직접 Thread 를 생성하기 보다는 AsyncTask 를 사용하길 권장한다.

AsyncTask 는 execute( Runnable) 메소드도 제공하기 때문에 별도의 스레드 작업을 할때도 사용할 수 있다.

AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        try {
            getJson(Value.IPADDRESS + "/MyTask.php", params,context);
        } catch (Exception e) {
            new String("Exception: " + e.getMessage());
        }
    }
});


위 코드에서 context 가 들어간 이유는 public class Broadcast_Receiver extends BroadcastReceiver 에서 처리하는 코드이기 때문이다.

일반적인 AsyncTask 를 사용해서 처리하려고 하는데 코드 실행순서가 순차적으로 실행되지 않는 현상이 있었다.

아래 코드내에서는 순차적으로 실행되는데 PostExecute(String result) 에서 처리할 코드가 제대로 먹히지 않았다.

내가 아직 기본기가 부족해서 놓치는 부분이 있어서 인거 같다. (일반 AsyncTask  : http://link2me.tistory.com/1031)


서버에서 결과를 받아다가 처리한 이후에 코드가 실행될 줄 알았는데 비동기식이라 그런지 다음 코드가 먼저 실행되어 버리면서 전혀 기대하지 않았던 결과가 나왔다.

그래서 검색해서 찾아낸 코드가 AsyncTask.execute(new Runnable() 였고, 이 코드를 활용하여 원하는 결과를 얻을 수가 있었다.


@Override
public void onReceive(Context context, Intent intent) {
    // 네트워크 연결 체크
    NetworkChk(context, intent);

    // 잠든 화면 깨우기
    WakeLock(context, intent);
    
    // 전화 수신 체크
    CallReceivedChk(context, intent);
}

private void CallReceivedChk(final Context context, Intent intent) {

    if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {

        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    getJson(Value.IPADDRESS + "/MyTask.php",params,context);
                } catch (Exception e) {
                    new String("Exception: " + e.getMessage());
                }
            }
        });

}


이 코드가 메모리 문제까지 완벽하게 처리해주는지 여부는 아직 모른다.

병렬처리를 하는게 맞을 거 같아서 코드를 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() 로 수정 적용했다.

AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    @Override
    public void run() {
       
    }
});


충분한 테스트 이후에 문제가 없다면 결과를 다시 기록해두련다.



AsyncTask가 처음 도입되었을때는 순차적으로 하나의 백그라운드 쓰레드를 실행하였으나, DONUT(1.6버전)부터는 여러개의 태스크를 병렬적으로 처리하도록 변경이 되었고, 다시 HONEYCOMB(3.0버전)부터는 하나의 쓰레드만 실행하게끔 돌아왔다. 그리고 THREAD_POOL_EXECUTOR를 사용한 executeOnExecutor(Executor, Params...) 로 쓰레드를 병렬적으로 실행시킬수 있게 했다.

if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB) {
  myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
  myTask.execute();
}

AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
       
    }
});

허니콤의 3.2버전에서는 AsyncTask의 쓰레드는 순차적으로 실행되므로 이전 태스크가 종료되기를 기다려야 한다.
그러므로 대기하지 않고 바로 실행시켜주려면 별도의 스레드에서 실행해야한다.
멀티 쓰레드로 Concurrent하게 데이터를 가져오거나 동작하게 하기 위해서는, 아래와 같이 AsyncTask를 실행할 때, AsyncTask.THREAD_POOL_EXECUTOR 스케줄러를 지정해야 한다.
new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, “”);

AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
    @Override
    public void run() {
       
    }
});


참고하면 도움이 될 사이트

http://www.programing-style.com/android/android-api/android-asynctask-order-execute/

728x90
블로그 이미지

Link2Me

,
728x90

서버로부터 데이터를 가져오는 방법은 JSON, XML 두가지 방식을 사용한다.

정부가 제공하는 자료는 XML 로 되어 있는데, 변환처리가 까다롭기 때문에 JSON으로 변환해주는 라이브러리를 사용하여 변환처리하면 개발하기 편하다.

JSON 방식에 대해 단계별로 정리를 해두고자 구글링 및 타 블로그 자료를 참조하여 테스트를 했다.

단계별로 정리하는 이유는 사용할 용도가 배열 형태로 여러 데이터를 가져와야 할 경우도 있지만, 하나의 데이터만 전달받아서 가져올 경우도 있다.


먼저 JSON 의 형태부터 알고 넘어가자.


서버에서 가져온 데이터라고 가정하고 JSON 데이터(배열)을 분리해서 값을 추출하는 예제다.


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    String test =
            "[{'name':'배트맨','age':37,'address':'고담'},"+
             "{'name':'슈퍼맨','age':36,'address':'뉴욕'},"+
              "{'name':'앤트맨','age':28,'address':'LA'}]";

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

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

        try {
            parse();
        } catch (Exception e) {
        }
    }

    private void parse() throws Exception {

        StringBuffer sb = new StringBuffer();

        try {
            JSONArray jarray = new JSONArray(test); // JSONArray 생성
            for(int i=0; i < jarray.length(); i++) {
                JSONObject jsonObj= jarray.getJSONObject(i);  // JSONObject 추출
                String address = jsonObj.getString("address");
                String name = jsonObj.getString("name");
                int age = jsonObj.getInt("age");
                sb.append(
                        "주소:" + address +
                        " 이름:" + name +
                        " 나이:" + age + "\n"
                );
            }
            textView.setText(sb.toString());
        } catch (JSONException e){
            e.printStackTrace();
        }
    }
}
 


서버에서 넘어온 데이터가 아래와 같은 구조일 수도 있다.

String test =
    "{"result":[{'name':'배트맨','age':37,'address':'고담'},"+
               "{'name':'슈퍼맨','age':36,'address':'뉴욕'},"+
           "{'name':'앤트맨','age':28,'address':'LA'}]}";


중괄호{}로 둘러싸인 것을 볼 수 있다. 그러므로

JSONObject jsonObj = new JSONObject(test);

JSONArray jarray = jsonObj.getJSONArray("result");


JSONArray 를 뽑아낸 다음에 위에서 나온 과정을 수행하면 된다.

for(int i=0; i < jarray.length(); i++) {
    JSONObject jObj= jarray.getJSONObject(i);  // JSONObject 추출
    String address = jObj.getString("address");
    String name = jObj.getString("name");
    int age = jObj.getInt("age");
    sb.append(
            "주소:" + address +
            " 이름:" + name +
            " 나이:" + age + "\n"
    );
}


위 예제에서 다음 단계로 고려할 사항은

1. 서버에서 JSON 데이터를 가져오는 방법

2. 배열 데이터를 메모리에 저장하는 방법

이다.



1. 서버에서 JSON 데이터를 가져오는 방법

안드로이드 6.0부터 Apache HTTP API는 지원안한다.

HttpURLConnection 클래스를 이용하라고 하는데, 이유는 이게 더 효율적이라고 한다.

서버와 통신을 하는 함수를 하나 잘 만들어서 유용하게 사용하면 좋다.


대부분 개발자는 Volley 라이브러리, Retrofit2 라이브러리를 이용하여 서버 데이터를 가져와서 파싱처리한다.

이 블로그에 게재된 Volley 라이브러리 또는 Retrofit2 라이브러리를 예제를 이용하는 걸 권장한다.

앱 build.gradle 에서 androidx 로 된 라이브러리를 이용하는 게시글이 최신 자료이다.

아래 코드 HttpUrlConnection 코드는 이용하지 않아도 된다.

Volley 라이브러리, Retrofit2 라이브러리는 백그라운드 처리를 다하고 결과만 파싱하여 처리하면 된다.


*** 아래 코드 작성일자 : 2017.4.14 일 ******

아래 함수가 100% 만족스런 함수는 아니지만 이거 테스트하느라고 엄청 삽질을 했다.

세션처리 부분은 아직 제대로 이해를 못했다.

Eclipse 에서 세션처리가 문제 없이 동작되던게 Android Studio 에서 세션이 제대로 동작하지 않는다.

그래서 서버쪽에서 처리하는 로직을 변경했다.


 import android.app.Activity;
import android.util.Log;
import android.webkit.CookieManager;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {  
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            if(conn != null){ // 연결되었으면
                conn.setConnectTimeout(10000); // milliseconds 연결 타임아웃시간
                //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");
                if (cookieString != null) {
                   conn.setRequestProperty("Cookie", cookieString);
                   Log.e("PHP_getCookie", cookieString);
                 }
                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(postParams);
                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()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }           
            return sb.toString().trim();  

        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }   
 
    }
}


이 함수를 활용하는 방법은 아래와 같다.

// 버튼을 클릭하면 서버에서 결과를 가져온다.
btn_search.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // 세션이 제대로 동작되지 않아서 사용자별 idx 를 폰에 저장했다가 가져와서 키로 사용
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        // 검색어(search) 와 key(idx) 를 postParameter 로 넘김
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("search", editText.getText().toString().trim())
                .appendQueryParameter("idx", settings.getString("idx",""));
        String postParams = builder.build().getEncodedQuery();
        new getJSONData().execute(Value.IPADDRESS + "/get_json.php",postParams);
    }
});


class getJSONData extends AsyncTask<String, Void, String> {
    @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;
        showList();
    }
}

// 서버 정보를 파싱하기 위한 변수 선언
String searchJSON;
private static final String TAG_RESULTS="result";
private static final String TAG_UID = "uid";
private static final String TAG_NAME = "name";
private static final String TAG_Mobile ="mobile";
JSONArray peoples = null;

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

        for(int i=0;i<peoples.length();i++){
            JSONObject c = peoples.getJSONObject(i);
            String uid = c.getString(TAG_UID);
            String name = c.getString(TAG_NAME);
            String mobile = c.getString(TAG_Mobile);
            sb.append(
                    "idx:" + uid +
                            " 이름:" + name +
                            " 휴대폰:" + mobile + "\n"
            );
        }
        textView.setText(sb.toString());
    } catch (JSONException e) {
        e.printStackTrace();
    }
}


간단하게 PHP 서버에서 검색된 결과 데이터를 화면에 출력만 하는 로직이라 간단하게 처리했다.


2. 배열 데이터를 메모리에 저장하는 방법은

Custom ListView 를 사용하는 법을 알아야 되는 과정이라서 여기서는 생략한다.


서버 get_json.php 파일은 아래와 같다.

보다 자세한 사항은 http://link2me.tistory.com/1022 를 참조하면 된다.


 <?php
if(!isset($_SESSION)) {
    session_start();
}

@extract($_POST); // POST 전송으로 전달받은 값 처리
if(!(isset($idx) && !empty($idx))) {
    echo 0;
    exit;
}

require_once 'dbconnect.php';

// 화면에 출력할 칼럼 발췌
$sql = "select uid,name,mobile from Person ";
if(!empty($search)) {
    $sql .= "where name LIKE '%".$search."%' or mobile LIKE '%".$search."%'";
}
$R = array(); // 결과 담을 변수 생성
$result = mysqli_query($dbconn,$sql);
while($row = mysqli_fetch_object($result)) {
    array_push($R, $row);
}
echo json_encode(array('result'=>$R));

?>


테스트에 사용된 파일 부분만 발췌한 것이다.

내용이 보강되고 달라지면 파일 찾기도 힘들거 같아 첨부해둔다.


PHPComm.zip


도움이 되셨다면 000 해 주세요.

728x90
블로그 이미지

Link2Me

,
728x90

Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+ mData.mobile));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);


위 코드는 기존에는 문제없이 잘 동작했었는데, 안드로이드 6.0 이상에서는 에러가 발생한다.

아래 코드로 아직 해결이 안된 상태다.


if (ActivityCompat.checkSelfPermission(getBaseContext(), Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED)
{
    Intent calIntent = new Intent(Intent.ACTION_CALL);
    calIntent.setData(Uri.parse("tel:"+ mData.mobile));
    calIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(calIntent);
}
else
{
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
    {
        requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, 1);
    }
}



728x90
블로그 이미지

Link2Me

,
728x90

어플 기능을 추가하면서 메모리 누수 문제로 고생을 좀 했다.

그냥 무심코 넘긴 Activity 생명주기가 얼마나 중요한지에 대해 새삼 알게되었다.


Activity 생명주기

안드로이드 생명주기(수명주기)란 Activity 의 상태정보가 변하는 것이다.

안드로이드 응용 프로그램은 PC용과 달리 화면이 작으므로 동시에 여러개의 Activity(화면)이 나올 수 없다.

화면 하나만 활성화된 상태이고, 나머지는 모두 비활성화된 상태로 남게 된다.


- 응용 프로그램이 시작되면 onCreate(), onStart(), onResume()가 차례로 수행되고, MainActivity 화면이 나온다.


  홈 버튼을 눌러서 Activity를 백그라운드로 보내버리거나, NewActivity가 실행되어 화면 전환이 될 때

  onPause()  --> onStop() 이 두 메소드가 연달아 호출되며 백그라운드 스택으로 내려간다.

 

- NewActivity 를 요청하면 오른쪽 방향의 onPause(), onStop()이 수행되고, MainActivity는 중지되며 NewActivity 화면이 나온다.

- New Activity 의 사용을 종료하면 onRestart(), onStart(), onResume()가 수행되고 다시 MainActivity 화면이 나온다.


- MainActivity 를 끝내면 아래 방향의 onPause(), onStop(), onDestroy()가 차례로 수행되고 응응 프로그램이 종료된다.


상태 메소드

 설명

 onCreate()

 - Activity 가 처음 만들어졌을 때 호출됨

 - 화면에 보이는 뷰들의 일반적인 상태를 설정하는 부분

 - 이전 상태가 저장되어 있는 경우에는 번들 객체를 참조하여 이전 상태 회복 가능

 - 이 메소드 다음에는 항상 onStart() 메소드가 호출됨

 onStart()

 - Activity가 화면에 보이기 바로 전에 호출됨

 - Activity가 화면상에 보이면 이 메소드 다음에 onResume() 메소드가 호출됨

 - Activity가 화면에서 가려지게 되면 이 메소드 다음에 onStop() 메소드가 호출됨

 onResume()

 - Activity가 사용자와 상호작용하기 바로 전에 호출됨

 onRestart()

 - Activity가 중지된 이후에 호출되는 메소드로 다시 시작하기 바로 전에 호출됨

 - 이 메소드 다음에는 항상 onStart() 메소드가 호출됨

 onPause()

 - 또다른 Activity를 시작하려고 할 때 호출됨

 - 저장되지 않은 데이터를 저장소에 저장하거나 애니메이션 중인 작업을 중지하는 등의

   기능을 수행하는 메소드임

 - 이 메소드가 리턴하기 전에는 다음 Activity가 시작될 수 없으므로 이 작업은 매우 빨리

   수행된 후 리턴되어야 함.

 - Activity가 이 상태에 들어가면 시스템은 Activity를 강제 종료할 수 있음

 onStop()

 - Activity가 사용자에게 더 이상 보이지 않을 때 소멸됨
 -
Activity가 소멸되거나 또다른 Activity가 화면을 가릴 때 호출됨
 -
Activity가 이 상태에 들어가면 시스템은 Activity를 강제 종료할 수 있음

 onDestroy()

 - Activity가 소멸되어 없어지기 전에 호출됨

 - 이 메소드는 Activity가 받는 마지막 호출이 됨

 - Activity가 애플리케이션에 의해 종료(finish)되거나 시스템이 강제로 종료시키는 경우에

   호출될 수 있음.

 - 위의 두가지 경우를 구분할 때 isFinishing() 메소드를 이용함

 - Activity가 이 상태에 들어가면 시스템은 Activity를 강제 종료할 수 있음.



Activity stack 처리하는 방법

Activity를 그냥 intent생성해서 만들면 계속해서 새로 Activity를 start하고 스택으로 쌓이는 문제가 발생한다.
예를 들어서 A, B, C Activity를 A->B->C->A->C->B로 이동하고 뒤로가기를 누르면 다시 화면이 B->C->A->C->B->A 순으로 왔던 순서 반대로 나타난다.
문제점 검토
1. 뜨면 안될 화면도 뜨게 된다.(접근해선 안되는 이전화면으로 돌아가버린다)
2. 그냥 다시 뒤로가면 되는 화면인데 새로운 화면을 만들어서 그 위에 쌓아나간다.(이전 화면의 Activity를 새로 생성해서 위에 쌓는다)

해결방법
1. 접근해선 안되는 이전화면으로 돌아가버리는 문제
   해당 Activity 의 JAVA 소스 파일에서 intent를 생성한 바로 다음에
   intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); //기존에 쌓여있던 스택을 모두 없앤다.
   intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // task를 새로 생성한다
   를 추가해준다.

2. 이전 화면의 Activity로 돌아가야하는 문제
   intent.addFlags(intent.FLAG_ACTIVITY_CLEAR_TOP); // 현재 Activity 없애고 이전 화면을 새로운 화면으로 지정


3. 어플 실행도중 전화가 왔을 때의 처리

    onResume() 와 onPause() 에서 관련 코드 추가

    @Override
    public void onResume() {
        super.onResume();  // Always call the superclass method first
        if(mediaPlayer != null && mediaPlayer.isPlaying() == false){
            mediaPlayer.start();
        }
        // 전화통화가 끝나면 멈춤상태 음악이 다시 플레이된다.
    }

    @Override
    public void onPause() {
        super.onPause();  // Always call the superclass method first
        // 홈버튼을 누르면 백그라운드로 들어가면서 재생되는 음악이 멈춘다.
        // 전화를 받으면 재생되던 음악이 자동으로 멈춘다.
        if(mediaPlayer != null && mediaPlayer.isPlaying() == true){
            mediaPlayer.pause();
        }
    } 



※ 참고로 Activity 생성시에 사용되는 Intent Flag 정리는 아래 블로그에 잘 정리되어 있다.

    http://theeye.pe.kr/archives/1298


    http://androidhuman.com/260 Activity 생애주기 샘플코드 및 설명



아래 사항은 여기에 추가하는 것이 적절한 거 같아서 콘솔 앱을 만들면서 적용한 걸 적어둔다. (Updated 2019.5.17)


서비스에서 Activity를 띄울 때는 인텐트에 플래그를 주어야 하며, 메인 Activity가 이미 메모리에 만들어져 있는 경우에는 메인 Activity의 onNewIntent() 메서드로 데이터가 전달된다.


@Override
protected void onNewIntent(Intent intent) {
    // If the activity has already been created/instantiated, the event will arrive through the 'onNewIntent()' method
    super.onNewIntent(intent);
    connectUsb();
    LayoutMode();


Intent intent = new Intent(Setting.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);

FLAG_ACTIVITY_SINGLE_TOP 은 Activity를 재사용 하기 위해서, 필요없는 Activity를 제거하기 위해서 FLAG_ACTIVITY_CLEAR_TOP 을 같이 조합하였다.
이런경우 MainActivity 에서는 onNewIntent()를 override 해서 intent를 받아야 한다.
호출되는 순서는 onCreate() 대신에 onNewIntent()가 호출되고 다음으로 onResume() 이 호출된다.

처음 startActivity 호출시
1. onCreate  → 2. onStart
3. onResume

해당 Activity가 실행된 상태에서  FLAG를 추가하고 다시 startActivity 호출시
1. onNewIntent
2. onResume

728x90
블로그 이미지

Link2Me

,