이번에는 파일을 분석해서 이 파일이 어느 종류의 파일인지를 알 수 있는 방법에 대해 알아보겠습니다.

사실 대부분의 경우 확장자가 잘 되어 있기 때문에 이 과정이 필요 없을 수 있습니다.
확장자가 jpg, png bmp 인 경우 그림파일, mp3 wav 인 경우 오디오 파일 avi mp4 인 경우 동영상 파일 등...
컴퓨터를 사용하다 보면 자연스럽게 알게 되는 정보이기 때문이죠.

그런데 흔하지는 않지만 실수 또는 잘못된 방법으로 파일의 확장자가 마음대로 변경된 경우를 경험하신 적 있으신가요?
저도 거의 없었지만 이전 휴대폰의 파일을 백업하고 오랜 기간이 지난 후에 보니 그림과 동영상 파일의 확장자가 001 002 같이 마음대로 바껴있더라고요.

물론 대략 폴더의 위치를 보고 확장자를 jpg 나 mp4로 바꿔서 실행해 보는 방법이 있습니다만
요즘 프로그래밍을 공부하다 보니 근본적으로 이 파일이 무슨 파일인지 알아내는 방법이 있는지, 이를 프로그래밍 할 수 있는 방법이 있을 지 궁금해 졌습니다.

그래서 구글로 검색을 해 본 결과 다음과 같은 프로그램을 찾았습니다.

TrID - File Identifier
http://mark0.net/soft-trid-e.html

위 프로그램은 파일의 내부를 분석해서 파일의 확장자가 무엇인지와 무슨 파일인지를 찾아주는 프로그램입니다. 실행 방식은 윈도우 cmd에서 실행해서 결과를 텍스트로 뿌려줍니다. 내 컴퓨터에서 실행하기 위해서는 아래 다운로드 링크 중에서 실행 파일과 TrIDDefs.TRD package 2개를 다운받은 후 압축을 풀면 됩니다.

저는 아래 그림과 같이 "C:\trid_w32" 폴더에 trid.exe, triddefs.trd 파일을 복사했습니다. 그리고 테스트를 위해 그림파일을 하나 가져와서 파일 이름을 unknownfile 이라고 하고 확장자를 지워버렸습니다.

그럼 cmd에서 실행해 볼까요? 윈도우키+R을 누르면 실행창이 뜨는데요 "cmd"라고 입력한 후에 확인을 클릭하면 됩니다. "C:\trid_w32" 폴더로 이동해서 명령어를 "trid unknownfile"라고 입력하면 아래 그림과 파일 분석 후에 결과가 나오게 됩니다.

이 프로그램의 결과를 visual studio 로 가져와서 원하는 형식으로 출력할 수 있을까요? 검색을 해 본 결과 가능한 것을 확인했습니다.

Process.start: how to get the output?
https://stackoverflow.com/questions/4291912/process-start-how-to-get-the-output

이제 프로그래밍을 해보겠습니다.

Visual Studio에서 새로운 프로젝트를 아래 그림과 같이 생성합니다. 프로젝트 이름은 "WindowsFormFileIdentifier" 로 지정하였습니다.

그리고 폼을 꾸며보겠습니다. 우선 기본 폼의 속성을 몇가지 변경합니다.

(Name): Form1 - Text: 파일형식 찾기 / Size: 400, 400

이제 도구들을 추가하고 속성을 변경하겠습니다.

라벨(Label) - (Name): label1 / Location: 20, 20 / AutoSize: True / Size: 137, 12(자동으로 설정됨) / Text: 파일 형식 찾기 프로그램
라벨(Label) - (Name): label2 / Location: 20, 60 / AutoSize: True / Size: 231, 12(자동으로 설정됨) / Text: 1. 오른쪽 버튼을 눌러 파일을 선택합니다.
버튼(Button) - (Name): button1 / Location: 260, 60 / AutoSize: False / Size: 75, 23 / Text: 파일 선택
라벨(Label) - (Name): label3 / Location: 20, 100 / AutoSize: True / Size: 73, 12(자동으로 설정됨) / Text: 선택한 파일:
라벨(Label) - (Name): label4 / Location: 100, 100 / AutoSize: True / Size: 29, 12(자동으로 설정됨) / Text: 없음
라벨(Label) - (Name): label5 / Location: 20, 140 / AutoSize: True / Size: 109, 12(자동으로 설정됨) / Text: 선택한 파일의 정보
자료보기창(DataGridView) - (Name): dataGridView1 / Location: 20, 180 / AutoSize: False / Size: 350, 160

꾸며진 결과는 아래 그림과 같습니다.

외형을 완성했으면 이제 프로그래밍을 해보겠습니다.
파일 선택 버튼을 더블클릭하면 자동으로 이벤트가 추가되고 코드보기 화면으로 전환됩니다.

코드를 작성하기 전에 bin\debug 폴더에 위에서 실행했던 trid.exe, triddefs.trd 2개의 파일을 붙여넣기 해야 합니다.
코드 왼쪽에 있는 솔루션 탐색기 메뉴에서 아이콘 중 모든파일 표시 아이콘을 클릭합니다. 기존에는 프로젝트와 관련이 없는 폴더나 파일은 숨겨져 있는데 이 아이콘을 클릭하면 모든 파일이 나타납니다. bin 폴더 안에 있는 debug 폴더에 커서를 놓고 오른쪽 마우스 버튼을 클릭한 후에 "파일 탐색기에서 폴더 열기" 메뉴를 클릭하면 해당 폴더 파일 탐색기가 열리게 됩니다.

이제 위에서 설명한 2개의 파일을 붙여넣기 하면 아래 그림과 같이 파일이 복사된 것을 확인할 수 있습니다.

이제 프로그래밍으 해보겠습니다. 매서드를 2개 만들겠습니다.

매서드1. 실행파일과 분석할 파일 경로를 활용하여 cmd에서 나왔던 정보를 datatable 형식으로 변환하여 출력해 주는 매서드 입니다.

        /// <summary>
        /// 선택한 파일에 대한 정보를 DataTable 형식으로 내보냅니다.
        /// </summary>
        /// <param name="FileName">선택한 파일의 전체 경로</param>
        /// <returns></returns>
        private DataTable GetResultsFromTridCMDStrings(string FileName)
        {
            // executableLocation 은 프로젝트 폴더\bin\debug 까지의 폴더입니다.
            string executableLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            // exefileLocation 는 executableLocation 안에 있는 실행파일 trid.exe 의 전체 경로입니다.
            string exefileLocation = Path.Combine(executableLocation, "trid.exe");

            // cmd에서 실행했던 것 처럼 프로세스를 실행하기 위한 설정값이 들어간 process 클래스를 proc라는 변수명으로 생성합니다.
            var proc = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = exefileLocation,
                    Arguments = FileName,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true
                }
            };

            // 출력을 위한 datatable을 생성합니다.
            DataTable dt = new DataTable();
            dt.Columns.Add("%", typeof(string)); // 확률
            dt.Columns.Add("Ext", typeof(string)); // 확장자
            dt.Columns.Add("Desc", typeof(string)); // 설명

            proc.Start(); // 위에서 생성한 proc를 실행합니다.
            while (!proc.StandardOutput.EndOfStream)
            {
                string line1 = proc.StandardOutput.ReadLine(); // cmd에서 나왔던 결과를 1줄씩 가져옵니다.

                if (line1.Contains("%")) // 라인에 %가 있을 경우에만 동작하게 했습니다.
                {
                    string dtcol1 = line1.Split('%')[0]; // 확률정보만 가져옵니다.
                    dtcol1 += "%";
                    string dtcol2 = line1.Split('(')[1]; // 확장자(점포함) 정보만 가져옵니다.
                    dtcol2 = dtcol2.Split(')')[0];
                    string dtcol3 = line1.Split(')')[1]; // 설명부분만 가져옵니다.
                    dtcol3 = dtcol3.Split('(')[0];

                    dt.Rows.Add(dtcol1, dtcol2, dtcol3); // 위의 3가지 정보가 포함된 DataRow를 추가합니다.
                }
            }

            return dt;
        }

매서드2. 위에서 제공된 결과를 DataGridView 컨트롤에서 볼 수 있도록 가져옵니다.

        /// <summary>
        /// 선택한 폴더의 파일 목록을 가져와서 DataGridView 도구에 보여줍니다.
        /// </summary>
        /// <param name="dt1">선택한 폴더의 파일 목록이 들어있는 DataTable을 입력합니다.</param>
        /// <param name="dgv1">결과를 출력할 DataGridView를 선택합니다.</param>
        private void ShowDataFromDataTableToDataGridView(DataTable dt1, DataGridView dgv1)
        {
            dgv1.Rows.Clear(); // 이전 정보가 있을 경우, 모든 행을 삭제합니다.
            dgv1.Columns.Clear(); // 이전 정보가 있을 경우, 모든 열을 삭제합니다.

            foreach (DataColumn dc1 in dt1.Columns) // 선택한 파일 목록이 들어있는 DataTable의 모든 열을 스캔합니다.
            {
                dgv1.Columns.Add(dc1.ColumnName, dc1.ColumnName); // 출력할 DataGridView에 열을 추가합니다.
            }

            int row_index = 0; // 행 인덱스 번호(초기 값)
            foreach (DataRow dr1 in dt1.Rows) // 선택한 파일 목록이 들어있는 DataTable의 모든 행을 스캔합니다.
            {
                dgv1.Rows.Add(); // 빈 행을 하나 추가합니다.
                foreach (DataColumn dc1 in dt1.Columns) // 선택한 파일 목록이 들어있는 DataTable의 모든 열을 스캔합니다.
                {
                    dgv1.Rows[row_index].Cells[dc1.ColumnName].Value = dr1[dc1.ColumnName]; // 선택 행 별로, 스캔하는 열에 해당하는 셀 값을 입력합니다.
                }
                row_index++; // 다음 행 인덱스를 선택하기 위해 1을 더해줍니다.
            }

            foreach (DataGridViewColumn drvc1 in dgv1.Columns) // 결과를 출력할 DataGridView의 모든 열을 스캔합니다.
            {
                drvc1.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; // 선택 열의 너비를 자동으로 설정합니다.
            }
        }

위에서 만든 2개의 매서드를 활용하여 버튼클릭 이벤트를 아래와 같이 수정합니다.

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog1 = new OpenFileDialog(); // OpenFileDialog 생성합니다.
            openFileDialog1.Multiselect = false; // 여러개 선택 못하게 합니다.
            if (openFileDialog1.ShowDialog() == DialogResult.OK) // 파일이 선택된 경우에만 실행되게 합니다.
            {
                label4.Text = openFileDialog1.FileName; // 선택한 폴더 이름을 label4에 출력합니다.
                DataTable dt_filelistinfo = GetResultsFromTridCMDStrings(openFileDialog1.FileName);
                ShowDataFromDataTableToDataGridView(dt_filelistinfo, dataGridView1);
            }
        }

전체 코드는 다음과 같습니다.

using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System; using System.Windows.Forms; using System.Data; // DataTable 사용 using System.IO; // Path 사용 using System.Reflection; // Assembly 사용 using System.Diagnostics; // Process 사용 namespace WindowsFormFileIdentifier { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog1 = new OpenFileDialog(); // OpenFileDialog 생성합니다. openFileDialog1.Multiselect = false; // 여러개 선택 못하게 합니다. if (openFileDialog1.ShowDialog() == DialogResult.OK) // 파일이 선택된 경우에만 실행되게 합니다. { label4.Text = openFileDialog1.FileName; // 선택한 폴더 이름을 label4에 출력합니다. DataTable dt_filelistinfo = GetResultsFromTridCMDStrings(openFileDialog1.FileName); ShowDataFromDataTableToDataGridView(dt_filelistinfo, dataGridView1); } } private DataTable GetResultsFromTridCMDStrings(string FileName) {             생략(매서드1) } private void ShowDataFromDataTableToDataGridView(DataTable dt1, DataGridView dgv1) {             생략(매서드2) } } }

이제 실행해 보겠습니다.
실행하면 다음과 같이 나타납니다.

파일 선택 버튼을 클릭하고 예제 파일에 있던 "unknownfile" 을 선택하면 다음과 같이 결과가 출력됩니다.

이제 위의 내용을 활용해서 확장자가 변경되었거나 확실하지 않은 파일이 있다면 이 프로그램으로 찾아서 바꿔주는 프로그래밍을 할 수 있습니다.

감사합니다.

안녕하세요. 저는 맥북프로 레티나 2013 late 버전을 사용하고 있습니다.
SSD 용량은 256G 버전을 구매했었습니다. 256G 용량 내에서 음악 프로그램 및 개발 도구, 부트캠프까지 사용하려다 보니 용량이 너무 부족했습니다.

그래서 초반에 생각해 낸 방법은 SD 카드에 JetDrive를 구매해서 용량을 늘리기로 하고 구매해서 오랜기간 사용했었습니다. 제가 구매한 제품 정보는 아래와 같습니다.

트랜센드 JetDrive Lite 330 128GB

https://search.shopping.naver.com/detail/detail.nhn?nv_mid=8493757187&cat_id=50004625&frm=NVSCTAB&query=jet+drive+sd

하지만 이 곳에 프로그램을 설치하기에는 외장 메모리라는 리스크가 존재하였습니다. 그래서 이 공간에는 다운로드 파일, 음악. 영상 등 프로그램이 아닌 독립된 파일만 저장하는 목적으로 사용되어왔습니다. 오랜 고민 끝에 최근 SSD를 업그레이드 하기로 결심하고 공부를 좀 했습니다. 용량은 고민 끝에 1TB(1000GB) 수준으로 

첫번째로 가장 중요한 SSD를 구하기 위해 제품을 구글에서 찾았습니다. 가장 쉬운 방법입니다.
제품 정보는 아래와 같습니다.

트랜센드 JETDRIVE 820 960GB

https://search.shopping.naver.com/detail/detail.nhn?nv_mid=12792420928&cat_id=50001617&frm=NVSCPRO&query=JetDrive%E2%84%A2+820

비용은 2018년 6월 기준으로 73만원 수준입니다. 비용 상관 없으신 분은 위 제품을 구매하시는 것이 정신건강에 좋습니다.

좀 더 싸게 업그레이드 할 수 있는 방법은 없을까? 하고 더 검색을 해 봤습니다. 그러다가 M.2 (2280) 방식을 맥북에서 사용가능하도록 하는 Convert를 발견했습니다. M.2 (2280) 방식의 SSD가 JETDRIVE 820 보다 20만원 정도 저렴하기 때문에 아답터 비용을 추가한다고 해도 훨씬 저렴하게 업그레이드가 가능합니다. Convert 제품 정보는 아래와 같습니다.

2014 2015 Macbook 12+16pin to M.2 NGFF M-Key SSD Convert Card for A1493 A1502
https://www.ebay.com/itm/2014-2015-Macbook-12-16pin-to-M-2-NGFF-M-Key-SSD-Convert-Card-for-A1493-A1502/263255987900?ssPageName=STRK%3AMEBIDX%3AIT&_trksid=p2060353.m2749.l2649

이 제품의 존재를 믿고 M.2 (2280) 방식의 SSD를 찾아보았습니다. 그 결과 가격을 고려해서 2개의 제품으로 압축되었습니다. 두 제품의 정보는 아래와 같습니다.

삼성전자 970 EVO M.2 2280 (1TB)

http://prod.danawa.com/info/?pcode=6095758&cate=112760
2018년 6월 기준으로 오픈마켓에서 57만원 정도에 구매 가능합니다. 

ADATA XPG GAMMIX S11 M.2 2280 (960GB)

http://prod.danawa.com/info/?pcode=6125741&cate=112760
2018년 6월 기준으로 오픈마켓에서 46.5만원 정도에 구매 가능합니다.

스팩은 제가 봤을 땐 큰 차이가 없어 보입니다. 그래서 전 ADATA XPG GAMMIX S11 M.2 2280 (960GB) 제품을 구매했습니다. 개인적으로 SSD를 AS 받은 경험이 없기 때문에 약간의 리스크를 안고 좀 더 저렴한 제품을 선택하였습니다.

두 제품을 결제를 했는데 SSD는 국내에서 구매했기 때문에 이틀 뒤 바로 도착하였습니다. 하지만 해외에서 구매한 Convert가 생각보다 늦게 오는 바람에 설치가 많이 늦어졌습니다. 2018년 5월 28일에 구매했는데 2018년 6월 14일에 도착했네요... 2주가 좀 넘게 걸렸죠...ㅜㅜ

조심하세요!!
Convert의 경우 다른 사이트에서 훨씬 전(2018년 5월 14일 구매)에 구매를 했는데 2주가 넘게 배송 추적이 되지 않아서 ebay에서 구매하게 되었습니다. 혹시 아래 제품을 찾으신 분이 있다면 구매하지 마세요. 제품 가격은 ebay 보다 저렴해서 2개를 주문했는데 1달이 지난 지금까지 도착이 안되고 있습니다. 배송 추적도 안되네요.
(판매자가 발송을 했다고 처리해서 취소도 안됩니다... 메시지도 보냈지만 답변을 주지도 않네요)

CY SA-213 12+16pin to M.2 NGFF M-Key SSD Convert Card
http://www.dx.com/p/cy-sa-213-12-16pin-to-m-2-ngff-m-key-ssd-convert-card-479010#.WyPsbaczYuU
(!위 링크에서 구매하지 마세요!)

드디어 준비는 끝났습니다. 여기서 잠깐! 추가로 준비해야 할 도구가 있습니다. 바로 드라이버인데요. 맥북을 분해하기 위해서는 일반 십자나 일자 드라이버가 아닌 P5, T5 드라이버가 필요합니다. 없으시다면 아래 링크를 클릭해서 구매해 주세요.
주의할 점: JETDRIVE 820을 구매하셨을 경우 구성에서 드라이버도 포함되어있을 수도 있습니다. 이 점 잘 확인해서 중복구매를 피해주세요.

맥북 분해용 6각 5각 별드라이버 별나사 T6 5P

http://itempage3.auction.co.kr/DetailView.aspx?ItemNo=A671523055&frm3=V2

저 같은 경우 샤오미 전동 드라이버를 구매했는데 거기에 운 좋게 P5와 T5가 들어있었습니다. 제가 구매한 제품의 정보는 아래와 같습니다.

샤오미 전동드라이버 와우스틱 / 1fs 18bits

(네이버에서 검색해서 구매하세요. 링크를 안 넣은 이유는 제가 구매했을때 가격보다 많이 비싸져서서 특정 링크를 알려드리기 곤란하네요.)
이 제품을 구매할 때 주의할 점은 6bits를 구매하게 되면 P5, T5가 없을 수도 있습니다 꼭 18bits를 구매하세요.

이제 모든 준비가 끝났습니다. 제가 참고한 SSD 교체 과정은 아래 링크를 참고해 주세요.

MacBook Pro 13" Retina Display Late 2013 SSD Replacement
https://ko.ifixit.com/Guide/MacBook+Pro+13-Inch+Retina+Display+Late+2013+SSD+Replacement/26811

여기서 부터는 제가 구매한 제품 사진 및 교체 과정을 사진과 간다한 설명으로 진행하겠습니다.

1. 준비물1: Macbook 12+16pin to M.2 NGFF M-Key SSD Convert Card, 해외에서 구매한 Convert 입니다. 포장 및 개봉 사진입니다.

 

2. 준비물2: ADATA XPG GAMMIX S11 M.2 2280 (960GB), M.2 2280 방식 SSD 사진입니다. 개봉 전에 뚜껑을 열어 제품 확인이 가능합니다. 

 

3. SSD에 Conver를 장착한 화면입니다.

4. 맥북프로레티나를 분해하기 위해 뒤집어 놓았습니다. 10개의 P5 나사를 분해합니다. 샤오미 전동드라이버 와우스틱에서 P5 드라이버 끝부분를 찾아서 결합 후 작업하였습니다.

 

 

5. 10개의 나사를 분해한 후에는 윗부분을 들어주면 아래 그림과 같이 분해가 됩니다. 저는 베터리 분해는 안했습니다. 별 문제 없었습니다. 확실하게 해 보실 분은 베터리 분해도 설명에 나와있는대로 해 보세요.

6. SSD 끝 부분에 있는 T5 나사 1개를 분해합니다. 기존 SSD를 제거하고 3번에서 작업한 내용물을 장착하고 다시 T5나사를 조립합니다.

 

저렇게 사진으로 보면 별 문제 없어보이지만 완전히 딱 맞지가 않습니다. 너비와 길이가 기존 SSD와 동일한데 기존 SSD를 낄 때보다 미세하게 덜 들어간 느낌이었습니다. 뚜껑도 조립은 되지만 처음보다 약간 튀어나온다는 느낌을 받았습니다. 이 원인은 방열판 때문에 기존 SSD보다 두꺼워서 발생하는 문제 같습니다. 그래도 장착이 불가능한 수준은 아닙니다. 미세한 차이에도 민감하게 느끼시느 분이라면 비용을 들여서라도 JetDrive를 구매해 주세요.

7. 뚜껑을 닫고 10개의 P5 나사를 조립합니다.

이제 하드웨어 조립은 완료되었습니다. 잘 인식되길 바래야겠네요.

이제는 새로운 SSD에 설치하기 위해 설치 USB를 만들고 디스크 유틸리티에서 SSD를 초기화 해주고 macOS를 설치해 주면 됩니다. 이와 관련된 내용은 아래 블로그에 상세하게 나와있기 때문에 설명을 생략합니다. 아래 2개의 글을 읽고 잘 따라해 주세요.

macOS 클린 설치 하기 (1) _ 설치 USB 만들기
http://macclub.tistory.com/112

macOS 클린 설치 하기 (2) _ macOS Sierra 설치하기
http://macclub.tistory.com/114

아래 사진은 조립한 하드가 초기화 되기 전의 사진입니다. 960GB의 용량은 잘 표시되네요.

초기화를 완료하면 아래 그림과 같이 나타납니다. 이제 대용량 SSD에 macOS를 설치하면 되겠네요.

감사합니다.

주) 현재 동작하지 않습니다.

이 글을 읽기 전에 다음 3개의 글을 읽어주세요.
"안드로이드 DB CRUD(쓰기, 읽기, 수정, 삭제) 1 - 프로세스" - http://ilbbang.tistory.com/34
"안드로이드 DB CRUD(쓰기, 읽기, 수정, 삭제) 2 - MariaDB 설정하기" - http://ilbbang.tistory.com/45
"안드로이드 DB CRUD(쓰기, 읽기, 수정, 삭제) 3 - PHP로 MySQL에 접근하기" - http://ilbbang.tistory.com/47
제가 따라해 볼 원문을 다시 확인해 보겠습니다.(출처)
http://freeprojectcode.com/android/android-mysql-basic-crud-operation-tutorial-for-android-studio-project/
(글 게시자가 홈페이지를 초기화 해서 해당 글을 더이상 볼 수 없습니다.)
이제 안드로이드에서 화면 및 구동이 가능하도록 프로그래밍을 해보겠습니다.
화면은 3개를 만들 예정입니다.
1. 쓰기 - 이름, 직책, 급여 입력란에 값을 입력하고 버튼을 클릭하면 DB에 작성 후에 "2. 읽기"으로 이동합니다. 입력하지 않아도 "2. 읽기" 화면으로 이동할 수도 있습니다.
2. 읽기 - 전체 목록을 출력합니다. 특정 목록을 클릭하면 "3.수정 및 삭제" 화면으로 이동합니다. "1. 쓰기" 화면으로 이동할 수도 있습니다.
3. 수정 및 삭제 - "2. 읽기"에서 출력된 목록 중 선택된 항목에 대한 정보를 수정할 수 있는 페이지 입니다. 내용을 변경 후에 버튼을 클릭하면 수정된 내용으로 DB를 update한 후에 "2. 읽기" 화면으로 이동합니다.. 수정하지 않고 "2. 읽기" 화면으로 이동할 수도 있습니다. 삭제 버튼을 클릭하면 선택된 항목을 삭제할 수도 있습니다.
그리고 구동에 필요한 별도의 자바 클래스를 4개 만들 예정입니다.
1. Constant - PHP 파일 주소 목록을 설정합니다.
2. PostRequestHandler - Post 방식의 Requset를 보내기 위한 클래스 입니다. 네트워크 관련 처리기 때문에 별도의 스래드(3. BackgroundWorker)를 생성하여 수행합니다.
3. BackgroundWorker - Background 에서 수행할 매서드가 있는 클래스입니다. Post로 보내기 및 받기 매서드가 있습니다.
4. JsonParser - Json으로 받은 내용을 안드로이드 화면에 출력하기 위해 변환하는 매서드가 있습니다.
안드로이드 스튜디오에서 새로운 프로젝트를
우선 클래스 부터 만들어 보겠습니다. 클래스를 만드는 방법은 아래 그림과 같이 탐색기에서 app 폴더에 마우스를 올린 후에 오른쪽 마우스 버튼을 클릭하고 New - Java Class를 클릭하고 클래스 이름을 입력하고 OK 버튼을 클릭하면 됩니다. 그럼 파일 별 코드를 보겠습니다.

1. Constant.java

public class Constant {      private static final String BASE_PATH = "php파일이있는폴더(마지막에/으로끝냄)";      public static final String CREATE_URL = BASE_PATH + "addEmp.php";     public static final String READ = BASE_PATH + "getAllEmp.php";     public static final String UPDATE = BASE_PATH + "updateEmp.php";     public static final String DELETE = BASE_PATH + "deleteEmp.php";      public static final String GET_METHOD = "GET";     static final String POST_METHOD = "POST"; } 

2. PostRequestHandler.java

import android.os.AsyncTask;  import java.io.UnsupportedEncodingException; import java.util.HashMap;  public class PostRequestHandler extends AsyncTask<Void, Void, String> {     // php URL 주소     String url;     // Key, Value 값     HashMap<String, String> requestedParams;      PostRequestHandler(String url, HashMap<String, String> params){         this.url = url;         this.requestedParams = params;     }      @Override     protected void onPreExecute() {         super.onPreExecute();     }      @Override     protected String doInBackground(Void... voids) {          // post request 보냄         BackgroundWorker backgroundWorker = new BackgroundWorker();         try {             String s = backgroundWorker.postRequestHandler(url, requestedParams);             return s.toString();         } catch (UnsupportedEncodingException e) {             e.printStackTrace();         }          return null;     }      @Override     protected void onPostExecute(String s) {         super.onPostExecute(s);     } } 

3. BackgroundWorker.java

import android.util.Log;  import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map;  public class BackgroundWorker {      // Make a POST Request Handler     public String postRequestHandler(String requestUrl, HashMap<String, String> requestedDataParams) throws UnsupportedEncodingException {          // Set an Empty URL obj in system         URL url;           // Set a String Builder to store result as string         StringBuilder stringBuilder = new StringBuilder();          try {             // Now Initialize URL             url = new URL(requestUrl);              // Make a HTTP url connection             HttpURLConnection connection = (HttpURLConnection) url.openConnection();              // Set Method Type             connection.setRequestMethod(Constant.POST_METHOD);             // Set Connection Time             connection.setConnectTimeout(10000);             connection.setReadTimeout(10000);             // set Input output ok             connection.setDoInput(true);             connection.setDoOutput(true);             // Remove Caches             //connection.setUseCaches(false);             //connection.setDefaultUseCaches(false);               // Creating a url as String with params             StringBuilder url_string = new StringBuilder();              boolean ampersand = false;             for (Map.Entry<String, String> params : requestedDataParams.entrySet() ){                 if (ampersand)                     url_string.append("&");                 else                     ampersand = true;                  url_string.append(URLEncoder.encode(params.getKey(), "UTF-8"));                 url_string.append("=");                 url_string.append(URLEncoder.encode(params.getValue(), "UTF-8"));             }             Log.d("Final Url===", url_string.toString());                //Creating an output stream             OutputStream outputStream = connection.getOutputStream();              // Write Output Steam             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));             bufferedWriter.write(url_string.toString());              bufferedWriter.flush();             bufferedWriter.close();             outputStream.close();              //        Log.d("Response===", connection.getResponseMessage());              if (connection.getResponseCode() == HttpURLConnection.HTTP_OK){                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));                  // Local String                 String result;                 while ((result = bufferedReader.readLine()) != null) {                     stringBuilder.append(result);                 }                 //            Log.d("Result===", result);              }         } catch (MalformedURLException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }          return stringBuilder.toString();     }      // Get Request Handler     public String getRequestHandler(String requestUrl){         // To Store response         StringBuilder stringBuilder = new StringBuilder();          try {             URL url = new URL(requestUrl);             // Open Connection             HttpURLConnection connection = (HttpURLConnection) url.openConnection();             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));              // Local             String result;             while ((result = bufferedReader.readLine()) != null) {                 stringBuilder.append(result + "\n");             }          } catch (MalformedURLException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }          return null;     }  } 

4. JsonParser.java

import android.util.Log;  import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL;  public class JsonParser {     private static final String TAG = JsonParser.class.getSimpleName();      public String convertJson(String reqUrl) {         String response = null;         try {             URL url = new URL(reqUrl);             HttpURLConnection conn = (HttpURLConnection) url.openConnection();             conn.setRequestMethod("GET");             // read the response             InputStream in = new BufferedInputStream(conn.getInputStream());             response = convertStreamToString(in);         } catch (MalformedURLException e) {             Log.e(TAG, "MalformedURLException: " + e.getMessage());         } catch (ProtocolException e) {             Log.e(TAG, "ProtocolException: " + e.getMessage());         } catch (IOException e) {             Log.e(TAG, "IOException: " + e.getMessage());         } catch (Exception e) {             Log.e(TAG, "Exception: " + e.getMessage());         }         return response;     }      private String convertStreamToString(InputStream is) {         BufferedReader reader = new BufferedReader(new InputStreamReader(is));         StringBuilder sb = new StringBuilder();          String line;         try {             while ((line = reader.readLine()) != null) {                 sb.append(line).append('\n');             }         } catch (IOException e) {             e.printStackTrace();         } finally {             try {                 is.close();             } catch (IOException e) {                 e.printStackTrace();             }         }         return sb.toString();     } } 

이제 위 클래스를 포함한 화면을 만들어 보겠습니다. 추가 화면을를 만드는 방법은 아래 그림과 같이 탐색기에서 app 폴더에 마우스를 올린 후에 오른쪽 마우스 버튼을 클릭하고 New - Java Class를 클릭하고 클래스 이름을 입력하고 OK 버튼을 클릭하면 됩니다. 그럼 파일 별 코드를 보겠습니다.

위 방법으로 "ViewActivity"와 "EditActivity"를 프로젝트에 추가합니다.
이제부터 Activity의 외형 및 코드를 아래와 같이 작성합니다.
화면1. activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.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"     android:padding="22dp"     tools:context=".MainActivity">      <LinearLayout         android:layout_width="match_parent"         android:layout_height="match_parent"         android:orientation="vertical"         android:padding="11dp">          <TextView             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:text="Android PHP MySQL CRUD"             android:textAlignment="center"             android:textColor="#e21ace41"             android:textSize="22dp" />          <TextView             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:text="FreeProjectCode.com"             android:textAlignment="center"             android:textColor="#e2f209df"             android:textSize="20dp" />          <EditText             android:id="@+id/name"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:hint="Name" />          <EditText             android:id="@+id/designation"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:hint="Designation" />          <EditText             android:id="@+id/salary"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:hint="Salary" />          <Button             android:id="@+id/btn_add"             android:onClick="createEmployee"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:textColor="#fff"             android:background="#e21ace41"             android:text="Add Employee"/>         <Button             android:id="@+id/btn_list"             android:onClick="employeeList"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:textColor="#fff"             android:layout_marginTop="11dp"             android:background="#e2152099"             android:text="Employee List"/>     </LinearLayout>  </android.support.constraint.ConstraintLayout> 

java코드1. MainActivity.java

import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast;  import java.util.HashMap;  public class MainActivity extends AppCompatActivity {      private EditText mName, mDesignation, mSalary;     private Button mBtnAdd;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          // Initialize EditText View         mName = (EditText) findViewById(R.id.name);         mDesignation = (EditText) findViewById(R.id.designation);         mSalary = (EditText) findViewById(R.id.salary);          mBtnAdd = (Button) findViewById(R.id.btn_add);     }      // Create     public void createEmployee(View view){          String name = mName.getText().toString();         String designation = mDesignation.getText().toString();         String salary = mSalary.getText().toString();          HashMap<String, String> requestedParams = new HashMap<>();         requestedParams.put("name", name);         requestedParams.put("designation", designation);         requestedParams.put("salary", salary);         Log.d("HashMap", requestedParams.get("name"));         Toast.makeText(getApplicationContext(), "Success!!! Employee Added Name: " + requestedParams.get("name"), Toast.LENGTH_LONG).show();          PostRequestHandler postRequestHandler = new PostRequestHandler(Constant.CREATE_URL, requestedParams);         postRequestHandler.execute();          employeeList(view);     }      public void employeeList(View view) {         Intent intent = new Intent(MainActivity.this, ViewActivity.class);         startActivity(intent);     }  } 

화면2. activity_view.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout  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=".ViewActivity">  <RelativeLayout     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical">     <Button         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:layout_marginBottom="11dp"         android:layout_marginTop="11dp"         android:background="#e21bd721"         android:onClick="addEmployee"         android:text="Add New Employee"         android:layout_alignParentBottom="true"         android:textColor="#fff" />      <ListView         android:id="@+id/list"         android:layout_width="match_parent"         android:layout_height="wrap_content" />  </RelativeLayout>  </RelativeLayout > 

여기서 ListView에 단순하게 택스트로 보여줄 수도 있지만 보기 좋은 형식으로 보여주려면 별도의 레이아웃 xml 파일을 만들어 줘야 합니다. 레이아웃을 만드는 방법은 아래 그림과 같이 탐색기에서 app 폴더에 마우스를 올린 후에 오른쪽 마우스 버튼을 클릭하고 New - Android Resource File을 클릭하고 Resource type를 Layout으로 선택한 다음 파일이름, Root element 등을 입력하고 OK 버튼을 클릭하면 됩니다.

위 방법으로 list_item.xml 파일를 프로젝트에 추가합니다.

화면2-1. list_item.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/id"         android:layout_width="0dp"         android:layout_height="0dp"         android:visibility="invisible"/>      <TextView         android:id="@+id/name"         android:layout_width="fill_parent"         android:layout_height="wrap_content"         android:paddingBottom="2dip"         android:paddingTop="6dip"         android:textColor="@color/colorPrimaryDark"         android:textSize="16sp"         android:textStyle="bold" />      <TextView         android:id="@+id/designation"         android:layout_width="fill_parent"         android:layout_height="wrap_content"         android:paddingBottom="2dip"         android:textColor="@color/colorAccent" />      <TextView         android:id="@+id/salary"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:textColor="#5d5d5d"         android:textStyle="bold" />  </LinearLayout> 

java코드2. ViewActivity.java

import android.app.ProgressDialog; import android.content.Intent; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; import android.widget.Toast;  import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject;  import java.util.ArrayList; import java.util.HashMap;  public class ViewActivity extends AppCompatActivity {      private String TAG = MainActivity.class.getSimpleName();      private ProgressDialog pDialog;     private ListView lv;      // URL to get contacts JSON     //private static String url = "http://shapon.website/android/CRUD/getAllEmp.php";      ArrayList<HashMap<String, String>> contactList;      private String id, name, designation, salary;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_view);          contactList = new ArrayList<>();          lv = (ListView) findViewById(R.id.list);          new Handler().execute();          // OnItem Click         lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {             @Override             public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {                 //Employee employee = (Employee) adapterView.getItemAtPosition(i);                 Intent intent = new Intent(ViewActivity.this, EditActivity.class);                  id = ((TextView) view.findViewById(R.id.id)).getText().toString();                 name = ((TextView) view.findViewById(R.id.name)).getText().toString();                 designation = ((TextView) view.findViewById(R.id.designation)).getText().toString();                 salary = ((TextView) view.findViewById(R.id.salary)).getText().toString();     //                String id = employee.getId(); //                String name = employee.getName(); //                String designation = employee.getDesignation(); //                String salary = employee.getSalary();                  intent.putExtra("ID", id);                 intent.putExtra("NAME", name);                 intent.putExtra("DESIGNATION", designation);                 intent.putExtra("SALARY", salary);                  startActivity(intent);             }         });     }      public void addEmployee(View view) {         Intent intent = new Intent(ViewActivity.this, MainActivity.class);         startActivity(intent);     }      /**      * Async task class to get json by making HTTP call      */     private class Handler extends AsyncTask<Void, Void, Void> {          private ListAdapter adapter;          @Override         protected void onPreExecute() {             super.onPreExecute();             // Showing progress dialog             pDialog = new ProgressDialog(ViewActivity.this);             pDialog.setMessage("Please wait...");             pDialog.setCancelable(false);             pDialog.show();          }          @Override         protected Void doInBackground(Void... arg0) {             JsonParser sh = new JsonParser();              // Making a request to url and getting response             String jsonStr = sh.convertJson(Constant.READ);              Log.e(TAG, "Response from url: " + jsonStr);              if (jsonStr != null) {                 try {                     JSONObject jsonObj = new JSONObject(jsonStr);                      // Getting JSON Array node                     JSONArray employeeArray = jsonObj.getJSONArray("result");                      // looping through All Contacts                     for (int i = 0; i < employeeArray.length(); i++) {                         JSONObject c = employeeArray.getJSONObject(i);                          String id = c.getString("id");                         String name = c.getString("name");                         String designation = c.getString("designation");                         String salary = c.getString("salary");                          // Phone node is JSON Object //                        JSONObject phone = c.getJSONObject("phone"); //                        String mobile = phone.getString("mobile"); //                        String home = phone.getString("home"); //                        String office = phone.getString("office");                          // tmp hash map for single contact                         HashMap<String, String> employee = new HashMap<>();                          // adding each child node to HashMap key => value                         employee.put("id", id);                         employee.put("name", name);                         employee.put("designation", designation);                         employee.put("salary", salary);                          // adding contact to contact list                         contactList.add(employee);                     }                 } catch (final JSONException e) {                     Log.e(TAG, "Json parsing error: " + e.getMessage());                     runOnUiThread(new Runnable() {                         @Override                         public void run() {                             Toast.makeText(getApplicationContext(),                                     "Json parsing error: " + e.getMessage(),                                     Toast.LENGTH_LONG)                                     .show();                         }                     });                  }             } else {                 Log.e(TAG, "Couldn't get json from server.");                 runOnUiThread(new Runnable() {                     @Override                     public void run() {                         Toast.makeText(getApplicationContext(),                                 "Couldn't get json from server. Check LogCat for possible errors!",                                 Toast.LENGTH_LONG)                                 .show();                     }                 });              }              return null;         }          @Override         protected void onPostExecute(Void result) {             super.onPostExecute(result);             // Dismiss the progress dialog             if (pDialog.isShowing())                 pDialog.dismiss();             /**              * Updating parsed JSON data into ListView              * */             ListAdapter adapter = new SimpleAdapter(                     ViewActivity.this, contactList,                     R.layout.list_item, new String[]{"id", "name", "designation",                     "salary"}, new int[]{R.id.id, R.id.name,                     R.id.designation, R.id.salary});              lv.setAdapter(adapter);           }      }  } 

화면3. activity_edit.xml

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.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=".EditActivity">      <LinearLayout         android:layout_width="match_parent"         android:layout_height="match_parent"         android:orientation="vertical"         android:padding="11dp">          <TextView             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:text="Android PHP MySQL CRUD"             android:textAlignment="center"             android:textColor="#e21ace41"             android:textSize="22dp" />          <TextView             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:text="FreeProjectCode.com"             android:textAlignment="center"             android:textColor="#e2f209df"             android:textSize="20dp" />           <EditText             android:id="@+id/id"             android:layout_width="0dp"             android:layout_height="0dp"             android:hint="Id"             android:visibility="invisible" />          <EditText             android:id="@+id/name"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:hint="Name" />          <EditText             android:id="@+id/designation"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:hint="Designation" />          <EditText             android:id="@+id/salary"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:hint="Salary" />          <Button             android:id="@+id/btn_update"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:background="#e21ace41"             android:onClick="updateEmployee"             android:text="Edit Employee"             android:textColor="#fff" />          <Button             android:id="@+id/btn_list"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:layout_marginTop="11dp"             android:background="#c28c27a6"             android:onClick="listEmployee"             android:text="Show Employee List"             android:textColor="#fff" />          <Button             android:id="@+id/btn_delete"             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:layout_marginTop="11dp"             android:background="#e2d11527"             android:onClick="deleteEmployee"             android:text="Delete Employee"             android:textColor="#fff" />        </LinearLayout>  </android.support.constraint.ConstraintLayout> 

java코드3. EditActivity.java

import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast;


import java.util.HashMap; public class EditActivity extends AppCompatActivity { private EditText mId, mName, mDesignation, mSalary; private Button mBtnAdd; //private String mId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit); // Initialize EditText View mId = (EditText) findViewById(R.id.id); mName = (EditText) findViewById(R.id.name); mDesignation = (EditText) findViewById(R.id.designation); mSalary = (EditText) findViewById(R.id.salary); mBtnAdd = (Button) findViewById(R.id.btn_add); Intent intent = getIntent(); Bundle bundle = intent.getExtras(); if (bundle != null){ mId.setText(bundle.getString("ID")); mName.setText(bundle.getString("NAME")); mDesignation.setText(bundle.getString("DESIGNATION")); mSalary.setText(bundle.getString("SALARY")); } } public void updateEmployee(View view) { String id = mId.getText().toString(); String name = mName.getText().toString(); String designation = mDesignation.getText().toString(); String salary = mSalary.getText().toString(); HashMap<String, String> requestedParams = new HashMap<>(); requestedParams.put("id", id); requestedParams.put("name", name); requestedParams.put("designation", designation); requestedParams.put("salary", salary); Log.d("HashMap", requestedParams.get("id")); Toast.makeText(getApplicationContext(), "Success!! Employee Updated ID : " + requestedParams.get("id"), Toast.LENGTH_LONG).show(); PostRequestHandler postRequestHandler = new PostRequestHandler(Constant.UPDATE, requestedParams); postRequestHandler.execute(); listEmployee(view); } public void deleteEmployee(View view) { String id = mId.getText().toString(); // String name = mName.getText().toString(); // String designation = mDesignation.getText().toString(); // String salary = mSalary.getText().toString(); HashMap<String, String> requestedParams = new HashMap<>(); requestedParams.put("id", id); // requestedParams.put("name", name); // requestedParams.put("designation", designation); // requestedParams.put("salary", salary); Log.d("HashMap", requestedParams.get("id")); Toast.makeText(getApplicationContext(), "Success!! Employee Deleted ID : " + requestedParams.get("id"), Toast.LENGTH_LONG).show(); PostRequestHandler postRequestHandler = new PostRequestHandler(Constant.DELETE, requestedParams); postRequestHandler.execute(); listEmployee(view); } public void listEmployee(View view) { Intent intent = new Intent(EditActivity.this, ViewActivity.class); startActivity(intent); } }

이제 빌드 후 앱을 실행해 보겠습니다.
안드로이드 DB CRUD 1 - 쓰기
앱을 실행하면 처음 화면이 나타납니다. 이름. 직책, 급여를 입력하고 ADD EMPLOYEE 버튼을 클릭하면 DB에 저장되고 전체 목록을 보여줍니다.

 

안드로이드 DB CRUD 2 - 읽기
DB에 저장된 전체 목록을 불러온 화면입니다. 항목을 클릭하면 해당 항목만 불러와 수정, 삭제를 할 수 있습니다.

안드로이드 DB CRUD 3 - 수정
알바생의 급여를 수정한 후에 EDIT EMPLOYEE 버튼을 클릭하면 DB에 내용이 수정되고 수정된 내용이 포함된 전체 목록을 출력합니다.

 

안드로이드 DB CRUD 4 - 삭제
알바생을 선택한 후에 DELETE EMPLOYEE 버튼을 클릭하면 DB에서 알바생을 삭제하고 업데이트 된 전체 목록을 출력합니다.

 

감사합니다.


이번에 제가 소개할 제품은 RAVPower 사의 "FileHub Plus" 라는 제품입니다.

□ 제품의 주요기능

이 제품을 활용하면 와이파이를 활용해서 다양한 기기에서 자료를 공유할 수 있습니다.
앱을 활용하면 사진 및 음악, 동영상의 실시간 스트리밍도 가능합니다.
유선 랜선을 연결할 경우 라우터 기능도 수행할 수 있어서 와이파이가 멀리 떨어져 있을 경우 아주 유용합니다.

출처 및 제품 소개링크: https://www.ravpower.com/rp-wd03-filehub-6000mah-power-bank-portable-wireless-router.html

□ 이 제품을 찾게 된 계기

처음 컴퓨터를 사용하면서 파일을 휴대하고 다닐 수 있는 방법은 "USB 메모리"로 파일을 옮겨서 가지고 다녔습니다.
하지만 컴퓨터 뿐만 아니라 스마트폰 태블릿 등의 기기와 파일을 공유하는 방법을 생각하게 되었습니다.
안드로이드는 OTG를 활용하면 공유가 가능하지만 안드로이드는 변환잭과 파일 이동을 도와주는 앱이 필요합니다.
그리고 결정적으로 연결은 1대만 가능하기 때문에 여러 기기에 동시에 공유가 가능한 제품이 필요했습니다.
이 대안으로 최근에는 클라우드 서비스 또는 NAS를 활용한 공유가 있지만 와이파이 환경이 아닌 경우에는 데이터에 대한 부담이 발생하게 됩니다.
이러한 고민을 해결하기 위해 찾은 제품이 RAVPower 사의 "FileHub Plus" 입니다. 2017년 5월에 아마존으로 구매해서 필요할 때 사용하고 있습니다.

□ 이 제품의 특징1 - 와이파이로 파일 공유

"FileHub Plus"에 연결된 저장장치를 무선으로 공유할 수 있습니다. 연결 가능한 장치는 SD카드, USB(메모리, 하드디스크) 입니다. 대용량 전원이 내장되어 있어서 하드디스크도 공유 가능합니다. 휴대폰에 있는 micro SD 카드의 경우에는 SD 카드 아답터를 활용해서 장착이 가능합니다. 스마트폰에서의 파일 접근은 "RAVPower"사에서 제공하는 "FileHub Plus" 앱을 설치해서 접근할 수 있습니다. 윈도우나 맥 등 컴퓨터에서도 네트워크 설정을 통해 파일탐색기를 활용하여 네트워크 드라이브 처럼 접근이 가능합니다. 공유 역할만 하기 때문에 "FileHub Plus"에서 생성한 와이파이에 접속할 경우에는 인터넷 사용은 불가능합니다.

"FileHub Plus" 와이파이에 접속된 상태에서 인터넷을 사용하려면 두가지 방법이 있습니다. 첫번째 방법은 인터넷이 가능한 유선랜을 연결합니다. 그러면 "FileHub Plus" 와이파이에 접속된 경우에도 인터넷 사용이 가능합니다.

두번째 방법은 "FileHub Plus"를 인터넷 가능한 와이파에 접속하는 방법입니다. 주변에 와이파이가 없는 경우 스마트폰에서 테더링을 활성화 하고 "FileHub Plus"를 테더링에 접속해도 됩니다.

□ 이 제품의 특징2 - 라우터 기능

이 기능은 호텔이나 병원 등 유선랜이 제공되는 객실에서 사용할 경우 유용한 기능입니다. 유선으로 인터넷을 제공하고 와이파이도 제공하지만 와이파이 신호는 약한 경우가 많습니다. 이때 랜선과 "FileHub Plus"를 연결 후 와이파이를 활성화 할 경우 강한 와이파이 신호를 잡아주기 때문에 다른 스마트폰이나 태블릿, 노트북 등의 무선인터넷을 더욱 빠르게 사용할 수 있습니다.

□ 이 제품의 특징3 - 미디어 서버, DLAN

미디어 서버나 DLAN을 지원하기 때문에 "FileHub Plus" 와이파이 내에서 미디어서버나 DLAN 기능을 지원하는 앱을 활용해서 미디어파일에 쉽게 접근할 수 있습니다.

□ 아쉬운점

스마트폰에서는 와이파이를 하나만 잡을 수 있기 때문에 유선랜이 없는 환경에서 인터넷과 파일공유를 즐기려면 테더링을 필수로 켜서 "FileHub Plus"에 인터넷을 사용할 수 있도록 해줘야 합니다. 아니면 우선 인터넷이 안되는 "FileHub Plus" 와이파이에 접속한 후에 앱으로 인터넷이 가능한 와이파이신호를 찾아서 연결해 줘야 합니다.

속도도 모바일에서 감상 가능한 미디어파일은 무리없지만 대용량 파일의 전송 또는 스트리밍에는 한계가 있습니다.

□ 이 점이 개선된다면 좋겠습니다.

와이파이도 블루투스 처럼 여러개의 신호를 잡을 수 있다면 좋겠네요. 기존 와이파이 접속을 유지하고 "FileHub Plus"를 켰을 때 2개의 와이파이가 동시에 동작을 한다면 더욱 자주 활용할 것 같습니다. 자체적인 터치스크린이 내장되어 다른기기의 도움 없이 와이파이 접속 등이 가능하다면 더할나위 없는 제품이 될것입니다.

감사합니다.


이 글을 읽기 전에 다음 2개의 글을 읽어주세요.

"안드로이드 DB CRUD(쓰기, 읽기, 수정, 삭제) 1 - 프로세스" - http://ilbbang.tistory.com/34
"안드로이드 DB CRUD(쓰기, 읽기, 수정, 삭제) 2 - MariaDB 설정하기" - http://ilbbang.tistory.com/45

제가 따라해 볼 원문을 다시 확인해 보겠습니다.(출처)
http://freeprojectcode.com/android/android-mysql-basic-crud-operation-tutorial-for-android-studio-project/

이전단계에서 MariaDB 설정을 완료했으면 다음 단계는 PHP 서버 프로그래밍으로 쿼리문을 실행할 수 있게 하겠습니다. 안드로이드에서는 서비스DB에 직접 접근할 수 없습니다. 이전에는 Mysql에서 제공하는 dbconnector를 import 해서 접근이 가능했지만 이 기능은 더이상 사용이 불가능하다고 합니다. 그래서 중간에 PHP 또는 Node.js 와 같은 서버 프로그래밍을 활용하여 간접적으로 DB에 접근이 가능합니다. Node.js가 더 신기술이긴 소규모 서비스를 위한 비용을 고려했을 때 PHP가 더 저렴하다고 판단하였기 때문에 PHP를 선택하였습니다.

저는 cafe24 사이드를 사용중입니다. node.js가 더 저렴한 곳이 있다면 댓글 남겨주세요.

이번 단계에서 만들 php 파일은 총 6개 입니다. (테스트용 파일 제외)

파일1. db_config.php
파일2. addEmp.php
파일3. getEmp.php
파일4. getAllEmp.php
파일5. updateEmp.php
파일6. deleteEmp.php

파일 별 설명 및 코드를 살펴보겠습니다.

파일1은 Maria DB를 접속하기 위한 파일입니다. 이 파일이 없다면 나머지 파일에 동일하게 접속할 수 있는 코드를 만들면 되지만, 이 경우 정보가 변경되었을 때 번거롭기 때문에 하나의 파일로 관리합니다.

파일1. db_config.php
<?php

 //DB 정보를 입력합니다
 define('HOST','IP또는HOSTNAME:PORT(생략시3306)');
 define('USER','사용자ID');
 define('PASS','암호');
 define('DB','DB이름');
 
 //DB에 접속합니다.
 $con = mysqli_connect(HOST,USER,PASS,DB) or die('DB에 연결할 수 없습니다.');
?>
이 파일을 다른 파일에서 require_once 명령어로 불러오면 $con 변수에 접속 스크립트가 실행되여 DB에 접속이 가능합니다. 접속이 안 될 경우에는 'DB에 연결할 수 없습니다.' 문구를 출력하고 접속이 되지 않습니다.

파일2는 DB에 자료를 입력하기 위한 파일입니다. 이름(name), 직함(designation), 급여(salary) 정보를 post 방식으로 보내면 이 파일에서 쿼리문을 실행해 DB에 입력합니다.

파일2. addEmp.php
<?php 
        // POST 방식일 경우에만 코드가 실행됩니다.
	if($_SERVER['REQUEST_METHOD']=='POST'){
		
		//POST로 보낸 값을 받아서 변수에 입력합니다.
		$name = $_POST['name'];
		$desg = $_POST['designation'];
		$sal = $_POST['salary'];
		
		//받아온 값을 입력 값으로 지정한 INSERT 쿼리문을 작성합니다.
		$sql = "INSERT INTO employee (name, designation, salary) VALUES ('$name','$desg','$sal')";
		
		//DB 접속 스크립트를 불러옵니다.
		require_once('db_config.php');
		
		//쿼리문을 실행합니다.
		if(mysqli_query($con,$sql)){

// 입력 성공 시 아래 내용을 출력합니다. echo '종업원 정보가 성공적으로 입력되었습니다.';

}else{

// 입력 실패 시 아래 내용을 출력합니다. echo '종업원 정보를 입력할 수 없습니다.'; } //접속을 종료합니다. mysqli_close($con); }else{

// POST 방식이 아니면 아래 내용을 출력합니다. echo 'Post Request 가 아닙니다.'; } ?>

파일3과 파일4는 DB의 자료를 읽어오기 위한 파일입니다. 읽어온 자료를 json 형식으로 변환해서 화면에 출력해 줍니다.
파일3은 단일 레코드의 정보를 가져옵니다. 검색 조건은 id 입니다. 파일4는 모든 레코드의 정보를 가져옵니다.

파일3. getEmp.php

<?php 
	
	//POST로 보낸 id 값을 받아서 변수에 입력합니다.
	$id = $_POST['id'];
	
	//DB 접속 스크립트를 불러옵니다.
	require_once('db_config.php');
	
	//받아온 id를 검색 조건으로 특정 종업원의 정보를 불러오는 쿼리문을 작성합니다.
	$sql = "SELECT * FROM employee WHERE id=$id";
	
	//쿼리문을 실행합니다. 결과를 r 변수에 저장합니다.
	$r = mysqli_query($con,$sql);
	
	// r 변수의 내용을 array 형식으로 내보내 result 변수에 저장합니다.
	$result = array();
	$row = mysqli_fetch_array($r);
	array_push($result,array(
			"id"=>$row['id'],
			"name"=>$row['name'],
			"desg"=>$row['designation'],
			"salary"=>$row['salary']
		));

	//저장된 result 변수를 json 형식으로 출력합니다.
	echo json_encode(array('result'=>$result), JSON_PRETTY_PRINT+JSON_UNESCAPED_UNICODE);
	

//접속을 종료합니다. mysqli_close($con); ?>

파일4. getAllEmp.php

<?php 
	//DB 접속 스크립트를 불러옵니다.
	require_once('db_config.php');
	
	//전체 종업원의 정보를 불러오는 쿼리문을 작성합니다.
	$sql = "SELECT * FROM employee";
	
	//쿼리문을 실행합니다. 결과를 r 변수에 저장합니다.
	$r = mysqli_query($con,$sql);
	
	// result 변수의 유형을 array 로 정의합니다.
	$result = array();
	
	// 모든 레코드에 대하여 while 문으로 반복 처리를 수행합니다.
	while($row = mysqli_fetch_array($r)){
		
		// r 변수의 내용을 array 형식으로 내보내 result 변수에 저장합니다.
		array_push($result,array(
			"id"=>$row['id'],
			"name"=>$row['name'],
                        "designation"=>$row['designation'],
                        "salary"=>$row['salary']
		));
	}
	
	//저장된 result 변수를 json 형식으로 출력합니다.
	echo json_encode(array('result'=>$result), JSON_PRETTY_PRINT+JSON_UNESCAPED_UNICODE);
	

//접속을 종료합니다. mysqli_close($con); ?>

파일5는 DB의 자료를 수정하기 위한 파일입니다. 검색 조건은 id 입니다. id, 이름(name), 직함(designation), 급여(salary) 정보를 post 방식으로 보내면 이 파일에서 쿼리문을 실행해 자료를 수정합니다.

파일5. updateEmp.php

<?php 
	if($_SERVER['REQUEST_METHOD']=='POST'){
		//POST로 보낸 값을 받아서 변수에 입력합니다.
		$id = $_POST['id'];
		$name = $_POST['name'];
		$desg = $_POST['designation'];
$sal = $_POST['salary']; //DB 접속 스크립트를 불러옵니다. require_once('db_config.php'); //특정 종업원의 정보를 수정하는 쿼리문을 작성합니다. $sql = "UPDATE employee SET name = '$name', designation = '$desg', salary = '$sal' WHERE id = $id;"; //Updating database table if(mysqli_query($con,$sql)){ // 수정 성공 시 아래 내용을 출력합니다. echo '종업원 정보가 성공적으로 수정되었습니다.'; }else{ // 수정 실패 시 아래 내용을 출력합니다. echo '종업원 정보를 수정할 수 없습니다.'; } //접속을 종료합니다. mysqli_close($con); } ?>

파일6은 DB의 자료를 삭제하기 위한 파일입니다. 검색조건은 id 입니다. id 정보를 post 파일로 보내면 이 파일에서 쿼리문을 실행해 해당 자료를 삭제합니다.

파일6. deleteEmp.php

<?php 
   // POST 방식일 경우에만 코드가 실행됩니다.
   if(isset($_POST['id'])){
	//POST로 보낸 id 값을 받아서 변수에 입력합니다.
	$id = $_POST['id'];
	
	//DB 접속 스크립트를 불러옵니다.
	require_once('db_config.php');
	
	//특정 종업원의 정보를 삭제하는 쿼리문을 작성합니다.
	$sql = "DELETE FROM employee WHERE id=$id;";
	
	//쿼리문을 실행합니다.
	if(mysqli_query($con,$sql)){
                // 삭제 성공 시 아래 내용을 출력합니다. 
		echo '종업원 정보가 성공적으로 삭제되었습니다.';
	}else{
                // 삭제 실패 시 아래 내용을 출력합니다. 
		echo '종업원 정보를 삭제할 수 없습니다.';
	}
	
	//접속을 종료합니다.
	mysqli_close($con);
   }else{
        // POST 방식이 아니면 아래 내용을 출력합니다.
        echo 'Post Request 가 아닙니다.';
   }
?>

예제 파일이어서 그대로 진행하지만 post 로 보낼때 변수 값을 더 지정한다면 파일을 여러개 만들 필요가 없다고 생각합니다.
시간이 되신다면 dbconfig.php, crudEmp.php 파일 두개로 진행할 수 있는 방법을 생각해 보세요.

이제 안드로이드에서 php 파일에 접속하여 MariaDB에 CRUD(쓰기, 읽기, 수정, 삭제)를 수행해 보겠습니다. 아래 링크를 클릭해 주세요

"안드로이드 DB CRUD(쓰기, 읽기, 수정, 삭제) 4 - Android 화면 및 구현하기" - http://ilbbang.tistory.com/52

감사합니다.

+ Recent posts