728x90

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

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


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

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

    }
});


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

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

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

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

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


블로그 이미지

Link2Me

,
728x90

어제 TV로 KIA : NC 야구 시청을 했다.

한승혁은 시범경기에서는 시속 157km 까지 던지면서 잘 할 것처럼 보여주지만 정작 경기에 투입되면 새가슴이 된다. 어제 보니까 본인이 던진 공이 커트를 당하면서 던질 공이 없으면 볼넷으로 무너지고, 패스트볼로 무너진다.


경기에서 자신감을 가지고 경기를 임해야 하는데 상대방에게 지고 들어가는게 눈에 보인다.

작년도보다는 제구가 좋아졌지만 제구 부족은 여전하며 결정구로 사용할 공이 별로 없다.

그러다보니 빠른 볼로만 승부를 걸려고 한다.

다양한 변화구를 연마해서 여러가지 공을 던질 줄 알아야만 새가슴병을 고칠 수 있어 보인다.

한두가지 구종으로 빠르기만으로 승부를 볼려고 하니까 안된다.


진해수 투수가 KIA에서 진해수소폭탄이란 별명을 얻을 정도로 불안감을 주었지만, LG에 가서 자신감을 가지고 공격적으로 임하니까 완전 다른 투수가 되었다.

한승혁은 자신있게 던진 공이 홈런을 맞으면 바로 마음이 쪼그라드는 유형이다.

심장이 약한 투수라서 많은 구종은 연마해서 다양하게 수싸움을 할 줄 아는 투수가 되지 않으면 선발투수로서도, 불펜투수로서도 살아남기 힘들겠더라.

지금은 150km 공도 커트하는 선수들이 많다. 그러므로 자신감을 키우려면 강속구 외에 다양한 변화구를 연마하도록 2군, 3군에서 체계적으로 훈련이 필요하다고 본다.

패전처리용으로 올라와야 마음에 부담이 없는거 같은데, 패전처리용으로 활용하기에는 아깝다면 KIA 감독/코치는  결정을 해야 한다.

어제 같은 마지막 상황에서는 박지훈 투수가 더 적격이지 않았을까 싶다.

이대진 투수코치는 투구시에 편안하게 투구하라고 하는 것은 잘 하는거 같은데 선수를 제대로 잡아서 키우는 능력은 제로인거 같다.
이대진 코치는 미국, 일본 연수를 다녀오지 않고 코치생활을 해서 인듯하다.

2군에서 체계적으로 투수 육성을 하는 코치가 있어야 한다.

블로그 이미지

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




블로그 이미지

Link2Me

,
728x90

네이버지식인에 질의사항이 있어서 간단하게 테스트해본 예제를 첨부한다.

최신버전 다운로드는 https://github.com/stevenwanderski/bxslider-4 에서 한다.

당연히 jQuery도 시간이 지나면서 버전이 업그레이드 되어 왔고, 이에 맞춰 bxslider 역시 jQuery 버전에 맞춰 개발되었을 확률이 높다.

버전에 맞게 하려면 위 사이트에서 파일을 최신버전으로 받고, jQuery 버전은 https://www.w3schools.com/jquery/default.asp 에서 확인하시라. 


bxslider.zip


http://bxslider.com/ 사이트에 받은 파일중에서 필요한 파일만 js, css 폴더에 복사한다.

아래 코드 중에서 색상을 별도 표시한 부분을 중점적으로 보면 금방 이해할 수 있다.


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="pragma" content="no-cache" />
<!-- jQuery library (served from Google) -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<!-- bxSlider Javascript file -->
<script src="./js/jquery.bxslider.min.js"></script>
<!-- bxSlider CSS file -->
<link href="./css/jquery.bxslider.css" rel="stylesheet" />
<script>
$(document).ready(function(){
  $('.bxslider').bxSlider();
});
</script>
</head>
<body>
<ul class="bxslider">
  <li><img src="./images/img_01.jpg" /></li>
  <li><img src="./images/img_02.jpg" /></li>
  <li><img src="./images/img_03.jpg" /></li>
  <li><img src="./images/img_04.jpg" /></li>
  <li><img src="./images/img_05.jpg" /></li>
</ul>   
</body>
</html>


------------------------------------------------------------------------------------------------------------------------

  <li><img src="./images/img_01.jpg" /></li>
  <li><img src="./images/img_02.jpg" /></li>
  <li><img src="./images/img_03.jpg" /></li>
  <li><img src="./images/img_04.jpg" /></li>
  <li><img src="./images/img_05.jpg" /></li>
파일명을 일일이 적기가 귀찮다면 PHP 를 이용해서 해당 폴더 images 를 읽어서 파일 갯수만큼 표시되도록 코드를 만들어도 된다.


<body>
    <?php
    $path = "./images"; // 오픈하고자 하는 폴더
    function getFileNames($directory) {
        $results = array();
        $handler = opendir($directory);
        while ($file = readdir($handler)) {
            if ($file != '.' && $file != '..' && is_dir($file) != '1') {
                $results[] = $file;
            }
        }
        closedir($handler);
        return $results;
    }
?>
<ul class="bxslider">
<?php
    foreach(getFileNames($path) as $value){
        echo '<li><img src="./images/'.$value.'" /></li><\n>';
    }
?>
</ul>
</body>


로 코드를 대체하면 된다.

블로그 이미지

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 참조하면 나온다.

블로그 이미지

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 참조


블로그 이미지

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



블로그 이미지

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/

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


블로그 이미지

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


블로그 이미지

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)에 올려두었다.


블로그 이미지

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 에서 처리를 못해서 생기는 에러로 보인다.

블로그 이미지

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/

블로그 이미지

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 해 주세요.

블로그 이미지

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



블로그 이미지

Link2Me

,
728x90

안드로이드를 배우면서 알게된 사이트를 적어둔다.


Android 배우겠다고 도움 요청했더니 알려준 사이트가 여기다. 하지만 난 구글링으로 대부분의 문제를 해결해 오고 있다.

한국 안드로이드 사용자 및 개발자 커뮤니티

http://www.androidpub.com/


Stackoverflow

http://stackoverflow.com/


안드로이드 개발자 공식 사이트

https://developer.android.com/index.html


안드로이드 예제가 많은 사이트

http://www.coderzheaven.com/android/


http://www.androidhive.info/


개념 설명 잘된 영문 사이트

https://www.tutorialspoint.com/android/index.htm


https://www.tutorialspoint.com/java/


http://abhiandroid.com/


https://www.simplifiedcoding.net/



안드로이드 간단하게 보기좋게 알 수 있게 나온 사이트

https://kairo96.gitbooks.io/android/


어플 등록 준비

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


안드로이드 데이터베이스 교재 사이트

http://www.acornpub.co.kr/book/android-database

이곳에 가면 교재 파일을 받을 수 있다.


반응형 안드로이드 앱 UI 개발

http://www.acornpub.co.kr/book/android-ui-fragment



생능출판사 그림으로 쉽게 설명하는 안드로이드프로그래밍(개정3판)

http://www.booksr.co.kr/ 에서 파일을 다운로드할 수 있다.


자바의 정석 유투브 강좌

https://www.youtube.com/watch?v=oJlCC1DutbA&list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp


Do It 안드로이드 프로그래밍 사이트

https://www.youtube.com/watch?v=SCXQqo0ApKU&list=PLzkhjlqMgxvBxi3Wyak9NicQI7UwhFU2O

에 유투브 무료 동영상 강좌를 들을 수 있다.



인프런(Inflearn) (무료 + 유료) : https://www.inflearn.com/

ㅇ 안드로이드 스튜디오로 안드로이드 앱 만들기 무료 강좌 : 안경잡이 개발자

https://www.inflearn.com/course/%ec%95%88%eb%93%9c%eb%a1%9c%ec%9d%b4%eb%93%9c-%ec%8a%a4%ed%8a%9c%eb%94%94%ec%98%a4-%ec%95%88%eb%93%9c%eb%a1%9c%ec%9d%b4%eb%93%9c-%ec%95%b1-%eb%a7%8c%eb%93%a4%ea%b8%b0/


UDEMY (무료 + 유료) : https://www.udemy.com/

ㅇ Android Studio (안드로이드 스튜디오) 제대로 배우기 - 기초 : https://www.udemy.com/hdandroid-studio/

ㅇ Android Studio (안드로이드 스튜디오) 제대로 배우기 - 활용 : https://www.udemy.com/android-studio-s/


생활코딩(무료)

https://opentutorials.org/



추천 블로그

블로그 사이트에서는 질문을 해서 답을 구한다기 보다는 잘 정리된 내용을 여러 블로그를 보면서 내것으로 정리하는 것이 중요한 거 같다. 질문하면 블로거도 바쁜데 시간내서 정리한 거라 댓글에 일일히 답해줄 시간까지는 없을 것이다.

그리고 지식을 100% 나누기 위해서 정리한 블로그 찾기는 참 어렵다는 걸 알면 된다. 얻을 수 있는 만큼만 얻고, 고마우면 공감, 댓글 적어주면 되는 것이지 그 이상을 바라지 말자.


박상권의 삽질 블로그 : http://gun0912.tistory.com/


Android Developer88 : https://developer88.tistory.com/


멈춤보단 천천히라도 : https://webnautes.tistory.com/


http://kingorihouse.tumblr.com/tagged/Android-Weekly


http://unikys.tistory.com/


https://codingwithmitch.com/
간단하게 유투브 동영상 강좌로 보여준다.


아이콘 크기를 변환해주는 사이트

https://resizeappicon.com/



'자료 구하기 > 정보' 카테고리의 다른 글

이미지 해상도 낮춰주는 웹사이트  (0) 2018.09.29
오픈소스SW 라이선스 LGPL 바로알기  (0) 2018.04.24
[PHP] PHP, jQuery 추천 사이트  (0) 2016.12.31
C# 추천 사이트  (0) 2016.01.17
부트스트랩 사이트  (2) 2015.08.24
블로그 이미지

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

블로그 이미지

Link2Me

,
728x90

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





Updated 2020.4.4

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


블로그 이미지

Link2Me

,
728x90

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

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

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


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

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


이제 FCM 연동을 할 차례다.


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

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


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


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

build.gradle (Module:App) 에

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

}
라고 표시되었다.


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

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


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

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

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



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


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

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

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


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



블로그 이미지

Link2Me

,
728x90

Avtivity 화면에서
XML로 정의되어 있는 View 와 Layout을 객체화 시키는 메소드가 inflate 다.
inflate 메소드를 통해서 XML 리소스 정보를 해석하여 View를 생성하고 rootView를 리턴한다.
리턴된 rootView를 setContentView() 라는 메소드를 통해 보여줄 수 있다.
setContentView를 이용하면 XML 레이아웃을 인플레이션 한 후 화면에 보여주는 기능을 하기도 하고, 인플레이션 된 위젯을 화면에 구성하는 기능을 한다.

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



XML 로 작성된 메뉴(옵션 메뉴, 컨텍스트 메뉴)는 inflate(팽창 → 프로그래밍 객체로 변환) 하면 실제 메뉴가 생성된다.


기본적인 사용 패턴은

// 1. inflater 얻어오기

MenuInflater inflater = getMenuInflater();

LayoutInflater inflater = getLayoutInflater();

LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE );


// 2. View inflate 하기 (XML 파일과 연결 (R. 으로 시작되는 resource 파일들만 inflate 가능))

inflater.inflate(R.menu.mymenu, menu);

View v = (View) inflater.inflate( R.layout.inflate_example, parent, false );


// 3. 화면에 표시하기

setContentView( v );


다시 정리하면

LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE );
View v = (View) inflater.inflate( R.layout.inflate_example, parent, false );
setContentView(v);



Main View 에 부분 xml 을 가져와서 보여주는 것에 대한 예제를 보자.


LinearLayout add_layout = (LinearLayout) findViewById(R.id.addLayout);
// 인플레이션
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.add_layout,add_layout,true); 



=== activity_main.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"    >

    <TextView
        android:id="@+id/txt01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="부분 레이아웃"
        android:layout_marginTop="30dp"
        android:gravity="center_horizontal"
        android:textSize="20dp"
        android:textColor="#a88" />

    <Button
        android:id="@+id/btn1"
        android:layout_gravity="center_horizontal"
        android:text="Add"
        android:textAllCaps="false"
        android:textSize="20dp"
        android:onClick="btn_addlayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

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

    </LinearLayout>
</LinearLayout>


부분 Layout 을 추가할 add_layout.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">

    <Button
        android:id="@+id/btnTest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="동의 토글"
        android:textSize="20dp"
        android:textStyle="bold" />

    <RadioGroup
        android:id="@+id/rg1"
        android:orientation="horizontal"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:layout_marginRight="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <RadioButton
            android:id="@+id/rb1"
            android:text="독서"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_marginRight="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <RadioButton
            android:id="@+id/rb2"
            android:text="여행"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_marginRight="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <RadioButton
            android:id="@+id/rb3"
            android:text="스포츠"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_marginRight="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <RadioButton
            android:id="@+id/rb4"
            android:text="영화"
            android:textColor="#00f"
            android:textSize="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </RadioGroup>

    <LinearLayout
        android:gravity="center"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:text="동의"
            android:textSize="20dp"
            android:layout_margin="10dp"
            android:textColor="#f00"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <CheckBox
            android:id="@+id/agree"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>


이제 MainActivity.java 파일에 inflation 을 시킨다.

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

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

    // 버튼을 클릭했을 때 동작처리 메소드
    public void btn_addlayout(View view){
        addLayout();
    }

    // add_layout.xml 을 activity_main.xml 에 추가하는 메소드
    private void addLayout(){
        LinearLayout add_layout = (LinearLayout) findViewById(R.id.addLayout);

        // 인플레이션
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.add_layout,add_layout,true);

        // add_layout.xml 에 버튼 객체 참조
        Button btnTest = (Button) findViewById(R.id.btnTest);
        final CheckBox agree = (CheckBox) findViewById(R.id.agree);

        btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(agree.isChecked()){
                    agree.setChecked(false);
                } else {
                    agree.setChecked(true);
                }
            }
        });

    }
}



블로그 이미지

Link2Me

,
728x90

사용자가 버튼을 클릭하면, 클릭 이벤트가 발생한다.

Button 요소에 onClick 속성을 추가한다.

클릭 이벤트가 발생하면 onClick 속성에 저장된 메소드가 호출된다.

 

<Button
        android:id="@+id/btn1"
        android:layout_gravity="center_horizontal"
        android:text="Add"
        android:textAllCaps="false"
        android:textSize="20dp"
        android:onClick="btn_addlayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

 

 

MainActivity.java 에 클릭 이벤트를 처리하는 메소드를 정의한다.

onClick 명을 btn_addlayout 이라고 했으므로 클릭 이벤트 이름도 동일하게 해주어야 한다.

onClick 속성에 선언하는 메소드는 public 이어야 하고, void 형을 가지며, View 를 메소드의 인수로 가진다.

 

// 버튼을 클릭했을 때 동작처리 메소드
public void btn_addlayout(View view){
    addLayout();
}

 

 

다른 방법은 가장 흔하게 사용하는 방법이다.

이 방식으로 코딩하는 걸 추천한다.

Button button = (Button) findViewById(R.id.btn1);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        addLayout();
    }
});

 

코틀린 에서 처리 방법

val product_name = findViewById<TextView>(R.id.product_name)
val product_count = findViewById<TextView>(R.id.product_count)


product_name.setOnClickListener { _ ->
    
}

 

Android Studio 3.6 이상에서 ViewBinding 처리 방법을 권장하고 있다.

https://link2me.tistory.com/1974 을 참조하시라.

블로그 이미지

Link2Me

,