어제에 이어서
8) json으로 출력하는 Controller를 생성하고 메소드를 작성
=> itemDataController
http://localhost:8080/oracleserver/list?pageno=4 입력해보고 데이터가 나오는지 확인
http://localhost:8080/oracleserver/detail?itemid=4
** Android에서 JSON 파싱
1. JSON (JAvascript Object Notation)
=> 자바스크립트의 표현식으로 데이터를 표현하는 표준 포맷
=> 전에는 XML을 표준 포맷으로 많이 사용했는데 JSON이 XML보다는 가볍고 프로그래밍 언어들이 이해하기가 쉬워서 최근에는 JSON을 사용하는 경우가 많다.
1) 배열
[데이터, 데이터, 데이터...]
2) 객체
{속성이름:데이터, 속성이름:데이터..}
=> 속성이름은 반드시 문자열로 작성해야 하고 데이터는 null, 숫자, boolean, 문자열 그리고 다른 객체나 배열이 가능
3) 직접 서버를 구현해서 리턴하는 경우
=> 되도록이면 객체(Map)를 만들어서 리턴
=> List는 이름 대신에 인덱스를 사용해서 데이터를 표현한다.
=> Map은 이름을 이용해서 데이터를 구분
2. 안드로이드에서의 JSON 파싱
=> java에서는 JSON 파싱을 할려면 JSON라이브러리를 추가해야 하지만 Android에서는 그럴 필요가 없다.
=> 파싱에 사용되는 클래스는 : JSONArray, JSONObject
JSON 문자열의 구조를 보고 생성자를 이용해서 생성
=> 가져온 문자열을 보고 아래 중 1개로 생성
JSONArray 이름 = new JSONArray(JSON 문자열);
JSONObject 이름 = new JSONObject(JSON 문자열);
=> 2개의 클래스에는 각각의 데이터를 가져오는 메소드가 존재하는데 매개변수가 배열은 인덱스이고 객체는 속성 이름이다.
배열.get자료형(0) : 0번째 데이터를 자료형으로 가져오는 것
객체.get자료형("속성이름") : 속성이름에 해당하는 데이터를 자료형으로 가져온다.
=> item와 itemname 만 가져와서 출력하기
JSONObjec obj = new JSONObject(data); //데이터가 { 로 시작
JSONArray list = obj.getJSONArray("list"); // list에 해당하는 데이터를 배렬로 가져오기
for(int i =0; i<list.length(); i=i+1) {
JSONObject item = list.getJSONObject(i);
int item = item.getInt("itemid");
int itemname = item.getString("itemname");
}
** 서버의 데이터를 가져와서 출력하기 - Android
http://192.168.0.200:8080/list?pageno=페이지번호
http://192.168.0.200:8080/list?itemid=아이템아이디
1. Android Application Project 생성
2. 인터넷 사용 권한을 설정
=> 네트워크를 사용할 때는 INTERNET 권한이 있어야 한다.
=> 웹 서버에 접속할 때 http 프로토콘을 사용하는 경우에는 Application에 android:usesCleartextTraffic=true로 설정되어 있어야 한다.
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
버튼을 누르면 첫번째 페이지의 데이터를 가져와서 TextView를 출력하고 다시 누르면 다음 페이지의 데이터를 추가
3. activity_main.xml 파일에 화면 디자인
=> 버튼 1개와 TextView 1개를 배치 - TextView의 너비를 데이터가 초과?
=> 출력되는 데이터가 View의 크가보다 크면 View에 내용을 전부 출력할 수 없어서 이런 경우에는 ScrollView를 사용해야 한다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="데이터 가져오기"
android:id="@+id/btn"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/display"/>
</ScrollView>
</LinearLayout>
4. activity.java 파일에 작성
package com.example.androiddataparsing;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
private TextView display;
private Button btn;
//페이지 번호를 저장할 변수
int pageno = 1;
//텍스트뷰에 출력할 데이터를 저장할 변수
//ListView에 출력하는 경우라면 ArrayList, ListAdapter 생성
String msg = "";
//데이터를 다운로드 받을 스레드 클래스
class ThreadEx extends Thread{
@Override
public void run(){
try{
//다운로드 받을 URL 생성
URL url = new URL("http://192.168.0.200:8080/oracleserver/list?pageno=" + pageno);
//연결 객체를 생성하고 옵션 설정
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setConnectTimeout(30000);
con.setUseCaches(false);
//문자열 읽어오기
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
StringBuilder sb = new StringBuilder();
while(true){
String line = br.readLine();
if(line ==null){
break;
}
sb.append(line + "\n");
}
//읽어온 데이터를 msg에 추가
//msg = msg + sb.toString();
//읽어온 데이터를 파싱하기
JSONObject object = new JSONObject(sb.toString());
//list 키 안의 배열을 찾아오기
JSONArray list = object.getJSONArray("list");
//배열을 순회
for(int i=0; i<list.length(); i=i+1){
//배열에서 i번째 데이터 가져오기
JSONObject item = list.getJSONObject(i);
int itemid = item.getInt("itemid");
String itemname = item.getString("itemname");
msg = msg + itemid + ":" + itemname + "\n";
}
//핸들러에게 출력 요청
Message message = new Message();
message.obj= msg;
handler.sendMessage(message);
}catch(Exception e){
Log.e("다운로드 에러", e.getMessage());
}
}
}
//다운로드 받은 후 데이터를 재출력하는 핸들러
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message message){
String data = (String)message.obj;
display.setText(data);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
display = (TextView)findViewById(R.id.display);
btn = (Button)findViewById(R.id.btn);
//스레드를 생성해서 데이터를 출력
ThreadEx th = new ThreadEx();
th.start();
//버튼을 클릭했을 때 다음 페이지 데이터 추가하기
btn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
//페이지 번호 증가
pageno = pageno +1;
//스레드를 생성해서 데이터를 출력
ThreadEx th = new ThreadEx();
th.start();
}
});
}
}
5. 실행 가능한 Activity 추가(DetailActivity)
6. activity_layout.xml 파일에 화면 디자인
=> 텍스트뷰 1개와 이미지뷰 1개를 배치
=> 아이템이름과 이미지를 출력
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".DetailActivity"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/itemname"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/pictureurl"/>
</LinearLayout>
7. Server Application 의 servlet-context.xml 파일에 설정 추가
<!-- Controller가 처리하지 못하는 URL은 WAS가 처리하도록 하는 설정 -->
<default-servlet-handler/>
8. DetailActivity.java 파일에 코드 작성
package com.example.androiddataparsing;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
public class DetailActivity extends AppCompatActivity {
TextView itemname;
ImageView pictureurl;
//itemname에 출력할 텍스트
String name;
//ImageView에 출력할 이미지 url
String image;
class ThreadEx extends Thread{
public void run(){
//다운로드 받은 문자열을 저장할 변수
String result = null;
try{
URL url = new URL("http://192.168.0.200:8080/oracleserver/detail?itemid=" + 1);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setUseCaches(false);
con.setConnectTimeout(30000);
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
StringBuilder sb = new StringBuilder();
while(true){
String line = br.readLine();
if(line == null)
break;
sb.append(line + "\n");
}
result = sb.toString();
br.close();
con.disconnect();
}catch(Exception e){
Log.e("다운로드 예외", e.getMessage());
}
//가져온 데이터 파싱
try{
JSONObject object = new JSONObject(result);
JSONObject item = object.getJSONObject("item");
//itemname 과 pictureurl 만 가져와서 저장
name = item.getString("itemname");
image = item.getString("pictureurl");
//핸들러 호출
Message message = new Message();
handler.sendMessage(message);
}catch(Exception e){
Log.e("파싱 예외", e.getMessage());
}
}
}
Handler handler = new Handler(Looper.getMainLooper()){
public void handleMessage(Message message){
itemname.setText(name);
//이미지를 다운로드 받기 위해서 이미지 스레드를 실행
new ImageThread().start();
}
};
class ImageThread extends Thread{
public void run(){
try{
URL url = new URL("http://192.168.0.200:8080/oracleserver/img/" + image);
InputStream is = url.openStream();
Bitmap bitmap = BitmapFactory.decodeStream(is);
is.close();
//핸들러를 호출하기
Message message = new Message();
message.obj = bitmap;
imageHandler.sendMessage(message);
}catch(Exception e){
Log.e("이미지 다운로드 예외", e.getMessage());
}
}
Handler imageHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message message) {
Bitmap bitmap = (Bitmap)message.obj;
pictureurl.setImageBitmap(bitmap);
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
itemname = (TextView)findViewById(R.id.itemname);
pictureurl = (ImageView)findViewById(R.id.pictureurl);
new ThreadEx().start();
}
}
** 로그인 및 로그아웃
1. 회원테이블을 생성하고 샘플 데이터를 대입(오라클에서)
create table member(
id varchar2(100) primary key,
pw varchar2(100) not null);
insert into member(id, pw) values('lsb5212', '1234');
2. 서버 작업
=> domain/member.java 클래스 생성
2) Member 테이블의 SQL 작업을 위한 mapper 파일을 추가
=>src/main/resources/mybatis/mappers/member.xml
3) Member 테이블에 SQL을 수행할 MemberDAO 클래스를 생성하고
4) Member 테이블 요청을 위한 메소드를 소유한 MemberService인터페이스를 생성
package kr.co.nfl.quarterback.service;
import javax.servlet.http.HttpServletRequest;
public interface MemberService {
public void login(HttpServletRequest request);
}
5) Member 테이블 요청을 처리하기 위한 메소드를 구현한 MemberServiceImpl 클래스를 생성
package kr.co.nfl.quarterback.service;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.co.nfl.quarterback.dao.MemberDAO;
import kr.co.nfl.quarterback.domain.Member;
@Service
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberDAO memberDao;
@Override
public void login(HttpServletRequest request) {
//파라미터 읽기 - 다 못쓴 파라마터 나중에 꼭 추가해주자.
String email = request.getParameter("email");
String password = request.getParameter("password");
//DAO의 매개변수 만들기
Member member = new Member();
member.setEmail(email);
member.setPassword(password);
//DAO의 메소드를 호출
Member result = memberDao.login(member);
//result가 null이면 로그인 실패, 그렇지 않으면 로그인 성공
request.setAttribute("result", result);
}
}
6) Controller 클래스를 추가하고 로그인 처리 메소드를 생성
package kr.co.nfl.quarterback.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import kr.co.nfl.quarterback.domain.Member;
import kr.co.nfl.quarterback.service.MemberService;
@RestController
public class MemberDataController {
@Autowired
private MemberService memberService;
@RequestMapping(value="login")
public Map<String, Object> login(HttpServletRequest request){
memberService.login(request);
Member member = (Member)request.getAttribute("result");
Map<String, Object> map = new HashMap<String, Object>();
//어떤 작업 수행의 결과가 null 일 수 있는 경우는 다른 키를 하나 추가해서 null 여부를 판단할 수 도록 해준다.
if(member == null) {
map.put("login", false);
}else {
map.put("login", true);
map.put("result", member);
}
return map;
}
}
테스트
http://localhost:8080/oracleserver/login?email=lsb5212&password=1234
oracleserver는 내껄로 고쳐서해보자.
3. Android
1) 실행가능한 Activity 생성(LoginActivity)
2) 레이아웃 수정(activity_login.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".LoginActivity"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이메일을 입력하세요"
android:id="@+id/emailinput"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="비밀번를을 입력하세요"
android:id="@+id/passwordinput"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="로그인"
android:id="@+id/btnlogin"/>
</LinearLayout>
3) LoginActivity .java 파일에 작성
package com.example.androiddataparsing;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class LoginActivity extends AppCompatActivity {
private EditText emailinput, passwordinput;
private Button btnlogin;
class ThreadEx extends Thread{
@Override
public void run(){
String json = null;
try{
URL url = new URL(
"http://192.168.0.200:8080/oracleserver/login?"
+"email=" + emailinput.getText().toString() + "&password=" +
passwordinput.getText().toString());
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setUseCaches(false);
con.setConnectTimeout(30000);
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
StringBuilder sb = new StringBuilder();
while(true){
String line = br.readLine();
if(line == null)
break;
sb.append(line + "\n");
}
json = sb.toString();
Log.e("json", json);
}catch(Exception e){
Log.e("다운로드 에러", e.getMessage());
}
try{
JSONObject object = new JSONObject(json);
Message message = new Message();
message.obj = object;
handler.sendMessage(message);
}catch (Exception e){
Log.e("파싱 에러", e.getMessage());
}
}
}
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message message){
try {
JSONObject result = (JSONObject) message.obj;
boolean r = result.getBoolean("login");
if (result == null) {
Toast.makeText(LoginActivity.this, "로그인 실패", Toast.LENGTH_LONG).show();
} else {
ShareData.login = true;
//로그인 성공했을 때 email과 password를 파일에 저장하기
try{
//파일을 생성
FileOutputStream fos = openFileOutput("login.txt", Context.MODE_PRIVATE);
fos.write(emailinput.getText().toString().getBytes());
fos.write(":".getBytes());
fos.write(passwordinput.getText().toString().getBytes());
fos.close();
}catch (Exception e){}
Toast.makeText(LoginActivity.this, "로그인 성공", Toast.LENGTH_LONG).show();
}
}catch (Exception e){}
}
};
//액티비티를 터치했을 때 호출되는 메소드
@Override
public boolean onTouchEvent(MotionEvent event){
//키보드 내리기 - 이걸 안하면 키보드가 안없어지고 유지되어 버린다.
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(emailinput.getWindowToken(), 0);
imm.hideSoftInputFromWindow(passwordinput.getWindowToken(), 0);
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Toast.makeText(this, ShareData.login+"", Toast.LENGTH_LONG).show();
emailinput = (EditText)findViewById(R.id.emailinput);
passwordinput = (EditText)findViewById(R.id.passwordinput);
btnlogin = (Button)findViewById(R.id.btnlogin);
try{ //이렇게 파일에 저장하면 다시 로그인 할 필요가 없다.
FileInputStream fis = openFileInput("login.txt");
byte [] data = new byte[fis.available()];
fis.read(data);
Toast.makeText(this, new String(data), Toast.LENGTH_LONG).show();
}catch (Exception e){
Toast.makeText(this, "로그인 한 적 없음", Toast.LENGTH_LONG).show();
}
btnlogin.setOnClickListener(new Button.OnClickListener() {
public void onClick(View view) {
new ThreadEx().start();
//키보드 내리기 - 이걸 안하면 키보드가 안없어지고 유지되어 버린다.
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(emailinput.getWindowToken(), 0);
imm.hideSoftInputFromWindow(passwordinput.getWindowToken(), 0);
}
});
}
}
** 로그인 처리를 할 때 웹에서는 세션이나 데이터베이스를 이용해서 로그인 정보를 저장해서 로그인 여부를 판정하게 된다.
모바일에서는 세션이 없기 때문에 다른 방법을 생각해야 한다.
1. 메모리 상에서의 공유 : 애플리케이션이 동작 중인 동안만 데이터가 유지
=> public class 에 static 변수를 생성해서 이용
=> sington 패턴으로 디자인하고 인스턴스 변수를 만들어서 사용
2. 반영구적으로 저장하기
=> 파일에 저장
=> sqlite에 저장
=> 환경설정에 저장
3. 서버에 저장
=> 네트워크가 안될 때 서버의 데이터를 가져올 수가 없다.