728x90

네이버 지식인 질문에 있는 예제를 거의 그대로 테스트한 걸 적어둔다.

volley 라이브러리를 사용해서 하려고 했더니 Jsoup 자체가 네트웍 통신을 하는 기능이 있는거 같다.

단지 AsyncTask 를 이용하여 백그라운드 처리만 하도록 해주면 OK다.

더 간단하게는 PHP 에서 파싱처리한 결과를 Android 에서 JSON으로 가져오는 것도 방법일 거 같다.

 

테스트한 소스코드 파일

weather_src.zip
다운로드
weather_src_new.zip
다운로드

 

수정 일자 : 2020.2.29

 

AndroidManifest.xml

파싱과는 무관한 권한도 추가되어 있는데

위험권한 설정하는 라이브러리를 이용하는 법 알려주려고 추가한 것이다.

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

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"  <!-- 안드로이드 9에서는 https 통신이 기본이라 http 통신 가능하게-->
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

 

 

앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.link2me.android.jsoup"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0' // ListView 개선 버전
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'org.jsoup:jsoup:1.11.3'
    implementation 'gun0912.ted:tedpermission:2.0.0'
}

recyclerview 는 실제 사용을 하지는 않았지만, androidx 에서 버전 표기를 찾기 쉽게 적어둔다.

 

Layout : activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/weather_rs1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.057" />

    <TextView
        android:id="@+id/weather_rs2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.127" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.java

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;

import com.gun0912.tedpermission.PermissionListener;
import com.gun0912.tedpermission.TedPermission;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

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

public class MainActivity extends AppCompatActivity {
    Context context;
    TextView textView1, textView2;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(MainActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

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

        // 네트워크 연결상태 체크
        if (NetworkConnection() == false) NotConnected_showAlert();
        checkPermissions();
    }

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23) { // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("앱을 이용하기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[]{
                            android.Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
                            android.Manifest.permission.READ_EXTERNAL_STORAGE,
                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
                    })
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        textView1 = findViewById(R.id.weather_rs1);
        textView2 = findViewById(R.id.weather_rs2);

        String path1 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT007007";
        String path2 = "https://weather.naver.com/rgn/cityWetrCity.nhn?cityRgnCd=CT008008";
        new getData1().execute(path1);
        new getData2().execute(path2);
    }

    private class getData1 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get(); // Web에서 내용을 가져온다.
                Elements elements = document.select("em"); // 내용중에서 원하는 부분을 가져온다.
                Element targetElement1 = elements.get(1);//1.현시간 2.온도 3.미세먼지
                Element targetElement2 = elements.get(2);//1.현시간 2.온도 3.미세먼지
                Element targetElement3 = elements.get(3);//1.현시간 2.온도 3.미세먼지
                String text1 = targetElement1.text();
                String text2 = targetElement2.text();
                String text3 = targetElement3.text();
                String text = "시간 : "+text1 + " ,날씨 : " + text2 + " ,미세먼지 : " + text3;
                System.out.println("대구날씨 : "+text);
                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            textView1.setText("대구 : "+result);
        }
    }

    private class getData2 extends AsyncTask<String, Void, String> {
        // String 으로 값을 전달받은 값을 처리하고, Boolean 으로 doInBackground 결과를 넘겨준다.
        @Override
        protected String doInBackground(String... params) {
            try {
                Document document = Jsoup.connect(params[0].toString()).get();
                Elements elements = document.select("em");
                Element targetElement1 = elements.get(1);//1.현시간 2.온도 3.미세먼지
                Element targetElement2 = elements.get(2);//1.현시간 2.온도 3.미세먼지
                Element targetElement3 = elements.get(3);//1.현시간 2.온도 3.미세먼지
                String text1 = targetElement1.text();
                String text2 = targetElement2.text();
                String text3 = targetElement3.text();
                String text = "시간 : "+text1 + " ,날씨 : " + text2 + " ,미세먼지 : " + text3;
                System.out.println("부산날씨 : "+text);
                return text;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            textView2.setText("부산 : "+result);
        }
    }

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

    }

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

 

https://partnerjun.tistory.com/42 에 Jsoup 설명이 잘 되어 있는 편이다.

 

PHP 를 활용한 파싱 코드는 https://link2me.tistory.com/1604 참조하라.

 

 

 

블로그 이미지

Link2Me

,
728x90

경기버스 도착정보 서비스 API 를 http://www.data.go.kr 사이트에서 받아서 XML 데이터를 JSON 으로 파싱하는 예제를 테스트하고 적어둔다.



신청하면 2년간 사용할 수 있게 되어 있다.


프로젝트 build.gradle

allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io" }
    }
}


앱 build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    implementation 'com.android.support:recyclerview-v7:27.1.0' // ListView 개선 버전
    implementation 'com.android.support:cardview-v7:27.1.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.github.smart-fun:XmlToJson:1.1.1'
    implementation 'com.android.volley:volley:1.1.0'
}


라이브러리 최신 정보는 https://github.com/smart-fun/XmlToJson 에서 확인해서 최신버전으로 적는게 좋다.



 public class MainActivity extends AppCompatActivity {
    Context context;
    public static final String TAG = MainActivity.class.getSimpleName();
    public EditText edit;
    public Button send;
    TextView status1;
    // http://www.data.go.kr 에서 로그인 후 API 조회하여 신청 승인받은 key 값
    String key = "iLOTMTnmawGIweE3%2F4DFwv98g0F3Vv7iJA%3D%3D"; // 실제 키값이 아님 (변경처리함)

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

        initView();
    }

    private void initView() {
        status1 = (TextView)findViewById(R.id.result); //파싱된 결과확인!
        edit = (EditText) findViewById(R.id.message) ;
        send = (Button)findViewById(R.id.send);

        // message send action
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!edit.getText().toString().isEmpty()){
                    BusArriveTask(edit.getText().toString());
                    edit.setText(" ");
                }
            }
        });
    }

    private void BusArriveTask(String search){
        RequestQueue requestQueue = Volley.newRequestQueue(context);

        String StationId = null; // 정류소 ID
        try {
            StationId = URLEncoder.encode(search,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 버스 도착정보 목록 조회
        String url = "http://openapi.gbis.go.kr/ws/rest/busarrivalservice/station?serviceKey="+key+"&stationId="+StationId+"";
        Log.d(TAG, "URL:"+url);

        StringRequest request= new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        XMLtoJSONData(response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                });

        requestQueue.add(request);
    }

    private void XMLtoJSONData(String xml){
        // https://androidfreetutorial.wordpress.com/2016/11/28/how-to-convert-xml-to-json-for-android/
        XmlToJson xmlToJson = new XmlToJson.Builder(xml).build();
        // convert to a JSONObject
        JSONObject jsonObject = xmlToJson.toJson();
        Log.d(TAG, "jsonObject:"+jsonObject);

        // JSON 에서 배열은 [] 대괄호 사용, Objext 는 {} 중괄호 사용
        try {
            JSONObject response = jsonObject.getJSONObject("response");
            JSONObject msgHeader = response.getJSONObject("msgHeader");
            String resultCode = msgHeader.optString("resultCode");
            Log.d(TAG, "String resultCode :"+resultCode);

            if(resultCode.equals("0")){
                JSONObject msgBody = response.getJSONObject("msgBody");
                Log.d(TAG, "jsonObject msgBody :"+msgBody);

                JSONArray array = msgBody.getJSONArray("busArrivalList");
                for(int i=0; i < array.length();i++){
                    JSONObject obj = array.getJSONObject(i);
                    // optString which returns the value mapped by name if it exists
                    String plateNo1 =obj.optString("plateNo1"); // 첫번째 차량 번호
                    String locationNo1 =obj.optString("locationNo1"); // 첫번째 차량 위치 정보
                    String plateNo2 =obj.optString("plateNo2"); // 두번째 차량 번호
                    String locationNo2 =obj.optString("locationNo2"); // 두번째 차량 위치 정보
                    Log.d(TAG, "jString plateNo1 :"+plateNo1);
                    Log.d(TAG, "jString plateNo2 :"+plateNo2);
                    Log.d(TAG, "jString locationNo1 :"+locationNo1);
                    Log.d(TAG, "jString locationNo2 :"+locationNo2);
                }
            } else if(resultCode.equals("1")){
                Toast.makeText(context, "시스템 에러가 발생하였습니다", Toast.LENGTH_SHORT).show();
            } else if(resultCode.equals("4")){
                Toast.makeText(context, "결과가 존재하지 않습니다", Toast.LENGTH_SHORT).show();
            } else if(resultCode.equals("8")){
                Toast.makeText(context, "요청 제한을 초과하였습니다", Toast.LENGTH_SHORT).show();
            } else if(resultCode.equals("23")){
                Toast.makeText(context, "버스 도착 정보가 존재하지 않습니다", Toast.LENGTH_SHORT).show();
            }

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

}


로그캣에만 찍어보도록 코드를 구현한 상태다.




XML 메시지를 JSON 으로 변환하면 아래와 같이 된다.

중괄호를 JSONObject response = jsonObject.getJSONObject("response"); 로 파싱하고

로그캣을 찍어서 메시지를 확인하면서 범위를 좁혀 나가면 된다.

[] 대괄호가 나오는 부분에서는 JSONArray array = msgBody.getJSONArray("busArrivalList"); 로 파싱하고 for 문을 돌려서 원하는 결과를 추출할 수 있다.





공공데이터 파싱하는데 사용한 샘플 코드를 첨부 (RecyclerView 를 이용하여 화면에 뿌리는 예제로 구현)

BusInfo_sample.zip



제대로 만들어 보려고 한다면

부산버스 어플 소스 https://github.com/kmshack/Busanbus-Android 공개된 것으로 공부하면 많은 도움이 될 거 같다.

관련 기사  : http://www.bloter.net/archives/151935

블로그 이미지

Link2Me

,
728x90

네이버 지도 API 기능을 학습하려고 구글링하다보니 Gson 라이브러리를 사용한 코드가 있어서 이참에 Gson 라이브러리를 테스트하고 정리해둔다.


1. Gson 라이브러리 최신버전 구하는 곳 : https://github.com/google/gson

   에 가면 최신버전의 정보가 나온다.

   단순하게 toJson() and fromJson() 메소드로 Java 객체를 JSON으로 변환하고

   SON 을 Java 객체로 변환하는 라이브러리 를 목표로 한다고 나온다.


2. 안드로이드 스튜디오에서 Gson 라이브러리를 이용하려면

   앱 build.gradle 파일내 dependencies 에 코드 한줄 추가하면 된다.

   compile 'com.google.code.gson:gson:2.8.2'



FileList_Item Class

- 변수명을 서버 JSON 변수명과 동일하게 처리하라.



Gson 라이브러리를 사용하지 않고 서버 데이터를 가져와서 RecyclerView 에 보여주는 코드중에서 비교할 수 있는 것만 발췌했다.


Gson 라이브러리 사용 이전 코드


listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성
audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅

코드 부분을 onCreate 에 추가하고 listViewAdapter.notifyDataSetChanged(); 를 하면 JSON Array 데이터가 RecyclerView 에 잘 보인다.


Gson 라이브러리 사용 코드



Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType();
fileList = gson.fromJson(audiofiles.toString(), listType);


검색해보면 위 코드 3줄이면 해결된다고 모두 답변이 되어 있다. 그런데도 불구하고 RecyclerView 에 출력되지 않았다.


서버에서 가져온 JSON Array 데이터를 ArrayList 에 추가하는 코드가 매우 간단해진다는 건 알았다.

상위폴더 ArrayList 를 별도로 만들 필요가 없는 경우에는 매우 유용할 거 같다.

Stackoverflow 에 질문한 내용을 검색해보니 RecyclerView 에 데이터 보이지 않는다고 질의한 사항이 몇건 있었다.  System.out.println 으로 찍어보면 데이터가 ArrayList에 정상적으로 추가되어 있다. 그러나 화면에 보이지 않았다.


Android RecyclerView JSON Parsing using Retrofit 게시글을 보니까 ArrayList 데이터를 추가하고 난 후 바로 다음 줄에 아래 2줄을 적어준 걸 보고 시도했더니 폰 화면에 정상적으로 출력된다.

listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성
audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅

Gson 라이브러리를 사용하면 JSON Array 데이터를 String으로 하나하나 분리할 필요없이 ArrayList에 바로 추가할 수 있어 코드가 심플해지는거 같다.

단, Class 에서 생성한 변수명과 서버(PHP)에서 json_encode로 생성한 변수명이 동일해야 하는거 같다.

서버에서 생성한 데이터가 JSON Object 가 아니라 JSON Array 면 코드가 더 심플해질 수 있는거 같다.


상위폴더를 안드로이드 Java 코드에서 생성한 것을 없애고, 서버 PHP 코드에서 구현하는 것으로 변경하고 테스트를 해보니 정상적으로 잘 처리된다.


코드 줄어든 것만 발췌하여 비교한다.

=== Gson 라이브러리 미사용 코드 ===

// 서버 정보를 파싱하기 위한 변수 선언
private static final String TAG_RESULTS="result";
private static final String TAG_fileNAME = "fileName";
private static final String TAG_filePATH ="filePath";
private static final String TAG_curPATH ="curPath";
private static final String TAG_isFile ="isFile";
JSONArray audiofiles = null;

protected void showList(String jsonData) {
    try {
        JSONObject jsonObj = new JSONObject(jsonData);
        audiofiles = jsonObj.getJSONArray(TAG_RESULTS);

        fileList.clear(); // 서버에서 가져온 데이터 초기화
        for(int i = 0; i < audiofiles.length(); i++){
            JSONObject c = audiofiles.getJSONObject(i);
            final String fileName = c.getString(TAG_fileNAME);
            final String filePath = c.getString(TAG_filePATH);
            final String curPath = c.getString(TAG_curPATH);
            final String isFile = c.getString(TAG_isFile);

            // 서버에서 가져온 데이터 저장
            FileAllList(fileName, filePath, curPath, isFile);
        }

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

}

// 파일 리스트 추가를 위한 메소드
public void FileAllList(String fileName, String filePath, String curPath, String isFile){
    FileList_Item item = new FileList_Item();
    item.setFileName(fileName);
    item.setFilePath(filePath);
    item.setCurPath(curPath);
    item.setIsFile(isFile);
    fileList.add(item);
}

=== Gson 라이브러리 사용한 코드 ====

 // 서버 정보를 파싱하기 위한 변수 선언
private static final String TAG_RESULTS="result";
JSONArray audiofiles = null;

protected void showList(String jsonData) {
    JSONObject jsonObj = null;
    try {
        jsonObj = new JSONObject(jsonData);
        audiofiles = jsonObj.getJSONArray(TAG_RESULTS);
    } catch (JSONException e) {
        e.printStackTrace();
    }

    fileList.clear(); // 서버에서 가져온 데이터 초기화
    Gson gson = new Gson();
    Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType();
    fileList = gson.fromJson(audiofiles.toString(), listType);
    listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성
    audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
}



정리 핵심

Gson gson = new Gson();
JSONArray jsonArray = new JSONArray(JSONdata);
Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType();
ArrayList<FileList_Item> myFileList = gson.fromJson(jsonArray.toString(), listType);


블로그 이미지

Link2Me

,
728x90

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

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




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


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

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


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

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

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


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

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

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

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


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




블로그 이미지

Link2Me

,
728x90

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


activity_main.xml

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

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


Student.java 클래스를 만든다.




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

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

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

    public String getAddress() {
        return address;
    }

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


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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

        XmlPullParserFactory factory = null;
        XmlPullParser xmlParser = null;

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

            int eventType = xmlParser.getEventType();

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


결과화면


샘플 코드 파일 첨부

xml_internaldataParsing.zip


블로그 이미지

Link2Me

,
728x90

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

=== student.xml ===

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


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

파싱하는 소스 코드

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

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

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


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

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

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

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

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

        parser();
    }

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

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

        XmlPullParserFactory factory = null;
        XmlPullParser xmlParser = null;

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

            int eventType = xmlParser.getEventType();

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


    }
}


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






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

블로그 이미지

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

,