public void setFileName(String fileName) { this.fileName = fileName; }
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public String getCurPath() { return curPath; }
public void setCurPath(String curPath) { this.curPath = curPath; }
public String getIsFile() { return isFile; }
public void setIsFile(String isFile) { this.isFile = isFile; } }
Gson 라이브러리를 사용하지 않고 서버 데이터를 가져와서 RecyclerView 에 보여주는 코드중에서 비교할 수 있는 것만 발췌했다.
Gson 라이브러리 사용 이전 코드
RecyclerView audiolistView; // 리스트뷰 (ListView 대신 RecyclerView) private ArrayList<FileList_Item> fileList = new ArrayList<>(); // 스트리밍 전체 리스트 RecyclerView.Adapter listViewAdapter; // ListViewAdapter 대신 RecyclerView.Adapter
// 서버 정보를 파싱하기 위한 변수 선언 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;
fileList.clear(); // 서버에서 가져온 데이터 초기화 if(!TextUtils.isEmpty(currentPath)){ int i = currentPath.lastIndexOf("/"); FileAllList("상위폴더", currentPath, currentPath.substring(0,i), "2"); } 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); }
listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성 audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
코드 부분을 onCreate 에 추가하고 listViewAdapter.notifyDataSetChanged(); 를 하면 JSON Array 데이터가 RecyclerView 에 잘 보인다.
Gson 라이브러리 사용 코드
RecyclerView audiolistView; // 리스트뷰 (ListView 대신 RecyclerView) private ArrayList<FileList_Item> fileList = new ArrayList<>(); // 스트리밍 전체 리스트 RecyclerView.Adapter listViewAdapter; // ListViewAdapter 대신 RecyclerView.Adapter
// 서버 정보를 파싱하기 위한 변수 선언 private static final String TAG_RESULTS="result"; JSONArray audiofiles = null;
fileList.clear(); // 서버에서 가져온 데이터 초기화 // 상위폴더 인식을 별도로 만들어준 ArrayList 가 전혀 먹히지 않는다. if(!TextUtils.isEmpty(currentPath)){ int i = currentPath.lastIndexOf("/"); FileAllList("상위폴더", currentPath, currentPath.substring(0,i), "2"); }
Gson gson = new Gson(); Type listType = new TypeToken<ArrayList<FileList_Item>>(){}.getType(); fileList = gson.fromJson(audiofiles.toString(), listType);
// 어댑터 생성 및 리스트뷰에 세팅을 여기에 추가해야만 정상적으로 RecyclerView에서 보이더라. listViewAdapter = new ListViewAdapter(this,fileList); // Adapter 생성 audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
}
// 파일 리스트 추가를 위한 메소드 // 이건 무용지물이더라. 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 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;
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;
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);
- Windows10 기반 Autoset9 (PHP 5.6.0, MySQL 5.6.20) : 정상
- Windows10 기반 Autoset10(Apache 2.4.17, PHP 7.0, MariaDB 10.1.9 ) : 정상
리눅스 기반 PHP 5.6.30, MySQL 5.6.30 에서 테스트를 하다보니 일부 함수가 문제가 발생하는 걸 알았다.
mysqlnd 가 설치되지 않아서였더라.
phpize로 추가 설치하는 방법을 검색해봤는데 아직 해결책을 못찾았다.
정 안되면 APM(Apache + PHP + MySQL) 소스 설치를 다시 해볼 생각이다.
- SHA1 패스워드를 적용한 알고리즘이 좋더라. 그래서 이걸 수정/추가해서 코드를 보완
- 아이폰/안드로이드폰에서 접속시 폰의 고유장치번호를 인식하여 최초 등록 단말이 아닌 경우 로그인 불가처리
- 테이블명을 변경하고 칼럼을 추가하고 user 권한을 설정하는 걸 추가
테이블 구조
- phpMyAdmin SQL에서 아래코드를 붙여넣기 하면 자동으로 DB명과 테이블을 생성한다.
- DB명과 user 권한을 수정해서 이용하면 된다.
create database android default character set utf8; use android; CREATE TABLE IF NOT EXISTS members( idx int(11) primary key auto_increment, userID varchar(60) not null unique, userNM varchar(50) not null, email varchar(60) default null, passwd varchar(80) not null, salt varchar(10) not null, telNO varchar(16) default null, mobileNO varchar(34) default null, phoneSE varchar(50) default null, OStype tinyint(2) NOT NULL DEFAULT '0', created_at datetime, regdate char(8) default null );
use mysql; create user android@localhost; grant all privileges on android.* to android@localhost identified by '!android@'; flush privileges;
DB_Functions 클래스 및 DB 접속정보는 첨부파일 참조
- DB_Functions 클래스가 핵심 내용이다.
<?php
class DB_Functions { private $conn;
// 생성자 function __construct() { require_once 'dbconnect.php'; // DB 연결 $db = new Db_Connect(); $this->conn = $db->connect(); }
// 소멸자(destructor) function __destruct() {
}
// 회원 정보 신규 입력 public function storeUser($userID, $name, $email, $password, $telNO, $mobileNO) { $hash = $this->hashSSHA($password); $encrypted_password = $hash['encrypted']; // encrypted password $salt = $hash['salt']; // salt
// check for successful store if ($result) { $stmt = $this->conn->prepare("SELECT * FROM members WHERE userID = ?"); $stmt->bind_param("s", $userID); $stmt->execute(); $user = $stmt->get_result()->fetch_assoc(); $stmt->close();
return $user; } else { return false; } }
// 로그인 체크 public function getUser($userID, $password) { $stmt = $this->conn->prepare("SELECT * FROM members WHERE userID = ?"); $stmt->bind_param("s", $userID);
if ($stmt->execute()) { $user = $stmt->get_result()->fetch_assoc(); $stmt->close();
// verifying user password $salt = $user['salt']; $encrypted_password = $user['passwd']; $hash = $this->checkhashSSHA($salt, $password); // check for password equality if ($encrypted_password == $hash) { // user authentication details are correct return $user; } } else { return NULL; } }
if ($result->num_rows > 0) { // user existed $stmt->free_result(); $stmt->close(); return true; } else { // user not existed $stmt->free_result(); $stmt->close(); return false; } }
// 회원 정보 삭제 public function deleteUser($userID){ $stmt = $this->conn->prepare("delete FROM members WHERE userID = ?"); $stmt->bind_param("s", $userID); $stmt->execute(); $stmt->close(); }
public function checkhashSSHA($salt, $password) { $hash = base64_encode(sha1($password . $salt, true) . $salt); return $hash; }
}
?>
- user 권한 및 패스워드 설정을 다르게 한 경우에는 해당 파일을 수정해야 한다.
register.php
- 안드로이드에서 전송된 데이터라고 가정하고 값을 직접 입력하여 DB에 잘 저장되는지 테스트했다.
<?php // 안드로이드에서 넘어온 데이터라고 가정하고 직접 DB에 데이터 저장 테스트 // 제대로 저장되는지 확인했으면 아래 4줄은 주석처리 또는 삭제해야 함 $_POST['userID'] = 'jsk005@gmail.com'; $_POST['name'] = '홍길동'; $_POST['email'] = 'jsk005@gmail.com'; $_POST['password'] = 'sk1234!';
extract($_POST);
require_once 'config/db_functions.php'; $db = new DB_Functions();
if (isset($_POST['userID']) && isset($_POST['name']) && isset($_POST['email']) && isset($_POST['password'])) {
// POST 배열로 받은 데이터 ==> extract($_POST); 이 한줄로 아래 코드 불필요 $userID = $_POST['userID']; $name = $_POST['name']; $email = $_POST['email']; $password = $_POST['password']; $telNO = '02-1234-4567'; $mobileNO = '010-1234-2580';
// 동일한 userID 등록되어 있는지 체크 if ($db->isUserExisted($userID)) { // E-Mail 이 key value // user already existed $response["error"] = TRUE; $response["error_msg"] = "User already existed with " . $userID; echo json_encode($response); } else { // 사용자 등록 $user = $db->storeUser($userID, $name, $email, $password, $telNO, $mobileNO); if ($user) { // 사용자 등록 성공 $response['error'] = FALSE; $response['user']['userID'] = $user['userID']; $response['user']['name'] = $user['userNM']; $response['user']['email'] = $user['email']; $response['user']['created_at'] = $user['created_at']; $response['user']['updated_at'] = $user['regdate']; echo json_encode($response); } else { // 사용자 등록 실패 $response['error'] = TRUE; $response['error_msg'] = "Unknown error occurred in registration!"; echo json_encode($response); } } } else { // 입력받은 데이터에 문제가 있을 경우 $response['error'] = TRUE; $response['error_msg'] = "Required parameters (name, email or password) is missing!"; echo json_encode($response); } ?>
json 으로 넘길 데이터 형태는 개발자가 변경하고 싶은데로 변경하여 사용하면 된다.
login.php
<?php // 안드로이드에서 넘어온 데이터라고 가정하고 직접 DB에 데이터 저장 테스트 // 제대로 저장되는지 확인했으면 아래 3줄은 주석처리 또는 삭제해야 함 $_POST['userID'] = 'jsk005@gmail.com'; $_POST['password'] = 'sk1234!'; $_POST['deviceID'] ='355500071663786';
extract($_POST); require_once 'config/db_functions.php'; $db = new DB_Functions();