-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 512 KB
/
content.json
1
{"meta":{"title":"Lazy Rodriguez","subtitle":null,"description":null,"author":"Lazy Rodriguez","url":"http://lazyrodi.github.io"},"pages":[{"title":"","date":"2016-08-14T05:51:08.817Z","updated":"2016-08-14T05:51:08.817Z","comments":false,"path":"keywords/index.html","permalink":"http://lazyrodi.github.io/keywords/index.html","excerpt":"","text":"A ADT: Abstract Data Type D DRM: Digital Rights Management. 디지털 콘텐츠의 저작권을 보호하기 위한 기술 및 서비스 DRY: “Don’t Repeat Yourself” principle. DSCM: Distributed Source Code Management P POSIX: Portable Operating System Interface. UNIX 기반의 OS를 위해 구현된 표준의 집합. S SCM: Source Code Management SEO: Search Engine Optimization T TOC: Table Of Contents TTFB: Time To First Byte. HTTP request 시 처음 byte가 browser에 도달한 후 browser에서 처리가 시작되는 시간을 측정한 것."},{"title":"","date":"2016-08-21T13:50:10.829Z","updated":"2016-08-21T13:50:10.829Z","comments":false,"path":"misc/DocumentWordParser.html","permalink":"http://lazyrodi.github.io/misc/DocumentWordParser.html","excerpt":"","text":"DocumentWordParser function clickSubmit() { var result_canvas = document.getElementById('result-canvas'); var doc = document.getElementById('doc-textarea').value; if (doc === '') { result_canvas.innerHTML = ''; result_canvas.style.display = \"none\"; } else { result_canvas.innerHTML = fill_data(parse_doc(doc)); result_canvas.style.display = \"block\"; } }; function fill_data(objarr) { var ret = \"WordFrequency\"; objarr.sort(function(a, b) { return b.value - a.value; }); for (var i = 0; i < objarr.length; i++) { ret += \"\" + objarr[i].key + \"\" + objarr[i].value + \"\"; } return ret + \"\"; } function parse_doc(doc) { var obj = {}; var tmp = []; var objarr = []; doc = doc.toLowerCase().replace(/\\,|\\.|\\?|\\!|\\'|\\\"|\\[|\\]|\\(|\\)|\\{|\\}/gi,\"\"); tmp = doc.split(/ |\\n|\\t/); for(var i = 0; i < tmp.length; i++) { var item = tmp[i]; if (item !== '') { if (obj.hasOwnProperty(item)) { obj[item]++; } else { obj[item] = 1; } } } for (var pr in obj) { objarr.push({key:pr, value:obj[pr]}); } return objarr; } window.onload = function () { document.getElementById('result-canvas').style.display = \"none\"; } body { font-family: consolas; background-color: #607D8B; } textarea, li { font-family: consolas; font-size: 10pt; } header { color: #FFFFFF; height: 70px; } footer { clear: both; height: 70px; font-size: 9pt; color: #FFFFFF; margin-top: 5px; } p { margin-left: 5px; } #readme, #doc-textarea, #result-canvas { border: 1px solid #aaaaaa; border-radius: 3px; } #content-area { background-color: #F7F7F7; padding: 10px; overflow: hidden; } #readme { background-color: #607D8B; color: #FFFFFF; padding: 1px; margin-bottom: 10px; } #column-left { width: 49%; float: left; } #column-right { width: 50%; float: right; } #doc-textarea { width: 100%; height: 300px; resize: none; } #btn-submit { width: 100px; height: 30px; margin-top: 5px; background-color: #4CAF50; color: #FFFFFF; border: none; cursor: pointer; float: right; } #result-canvas { font-size: 10pt; overflow-x: auto; } #table-result { margin: 5px; } Document Word Parser Parse the document word by word. Input document in textarea left side. Run You can see word-frequency on the right side. Ignore uppercase/lowercase and some characters-, . ' \" ( ) { } [ ] ? !-. [email protected], 2016"},{"title":"","date":"2016-08-21T13:50:09.621Z","updated":"2016-08-21T13:50:09.621Z","comments":false,"path":"misc/MyFavoriteColors.html","permalink":"http://lazyrodi.github.io/misc/MyFavoriteColors.html","excerpt":"","text":"My Favorite Colors window.onload = function () { } #bg-black { background-color: #000000; } #rodicolor-1-level-1 { background-color:#607D8B; color: #EEEEEE; } #rodicolor-1-level-2 { background-color:#90ADBB; color: #EEEEEE; } #rodicolor-1-level-3 { background-color:#C0DDEB; color: #333333; } #rodicolor-2-level-1 { background-color:#FFA000; color: #777777; } #rodicolor-2-level-2 { background-color:#FFC107; color: #777777; } #rodicolor-2-level-3 { background-color:#FFECB3; color: #777777; } #rodicolor-3-level-1 { background-color:#212121; color: #FFFFFF; } #rodicolor-3-level-2 { background-color:#616161; color: #FFFFFF; } #rodicolor-3-level-3 { background-color:#9E9E9E; color: #FFFFFF; } #rodicolor-4-level-1 { background-color:#303F9F; color: #EEEEEE; } #rodicolor-4-level-2 { background-color:#2F61E5; color: #EEEEEE; } #rodicolor-4-level-3 { background-color:#428AFF; color: #EEEEEE; } #rodicolor-5-level-1 { background-color:#243141; color: #EEEEEE; } #rodicolor-5-level-2 { background-color:#3E5165; color: #EEEEEE; } #rodicolor-5-level-3 { background-color:#5B6F83; color: #EEEEEE; } #rodicolor-6-level-1 { background-color:#E0225F; color: #EEEEEE; } #rodicolor-6-level-2 { background-color:#F93C78; color: #EEEEEE; } #rodicolor-6-level-3 { background-color:#FC6595; color: #EEEEEE; } #rodicolor-7-level-1 { background-color:#D8D380; color: #303F9F; } #rodicolor-7-level-2 { background-color:#E8E4A7; color: #303F9F; } #rodicolor-7-level-3 { background-color:#F3F2CC; color: #303F9F; } I love this color 1 bg: #607D8B, font: #EEEEEE bg: #90ADBB, font: #EEEEEE bg: #C0DDEB, font: #333333 2 bg: #FFA000, font: #777777 bg: #FFC107, font: #777777 bg: #FFECB3, font: #777777 3 bg: #212121, font: #FFFFFF bg: #616161, font: #FFFFFF bg: #9E9E9E, font: #FFFFFF 4 bg: #303F9F, font: #EEEEEE bg: #2F61E5, font: #EEEEEE bg: #428AFF, font: #EEEEEE 5 bg: #243141, font: #EEEEEE bg: #3E5165, font: #EEEEEE bg: #5B6F83, font: #EEEEEE 6 bg: #E0225F, font: #EEEEEE bg: #F93C78, font: #EEEEEE bg: #FC6595, font: #EEEEEE 7 bg: #D8D380, font: #EEEEEE bg: #E8E4A7, font: #EEEEEE bg: #F4F2CC, font: #EEEEEE [email protected] © 2016"},{"title":"","date":"2016-08-21T15:10:59.978Z","updated":"2016-08-21T15:10:59.978Z","comments":false,"path":"misc/UhConverter.html","permalink":"http://lazyrodi.github.io/misc/UhConverter.html","excerpt":"","text":"Uh... Converter function clickSubmit() { var inputData = document.getElementById(\"input-text\").value; if (inputData == '') { document.getElementById('result-canvas').innerHTML = \"No Input Data.\"; document.getElementById('result-canvas').style.display = \"block\"; return; } var dataArr = strToArr(inputData); // input text data to Array. var dataType = getCheckedRadioValue(\"datatype\"); // ex. '1':bin, '2':dec, '3':oct, '4':hex, '5':ascii if (dataType != '10') { dataArr = toDec(dataArr, dataType); } drawTable(dataArr); } function strToArr(inputData) { var arr = []; if (inputData[0] == '{' && inputData[inputData.length - 1] == '}') { arr = inputData.substring(1, inputData.length - 1).replace(/ /gi, '').split(','); } else { arr[0] = inputData; } return arr; } function getCheckedRadioValue(name) { var e = document.getElementsByName(name); for (var i = 0; i < e.length; i++) { if (e[i].checked) { return e[i].value; } } } function toDec(dataArr, dataType) { var arr = []; if (dataType == 'a') { for (var i = 0; i < dataArr.length; i++) { arr[i] = dataArr[i].charCodeAt(0); } } else { for (var i = 0; i < dataArr.length; i++) { arr[i] = parseInt(dataArr[i], dataType).toString(); } } return arr; } function drawTable(dataArr) { var resultCanvas = document.getElementById('result-canvas'); var bgColor = ['#FAFAFA', '#EAEAEA']; var ret = \"\" + \"\" + \"Binary\" + \"Decimal\" + \"Octal\" + \"Hexadecimal\" + \"ASCII\" + \"\"; for (var row = 0; row < dataArr.length; row++) { ret += \"\"; for (var col = 1; col < 6; col++) { ret += \"\" + convertData(col, dataArr[row]) + \"\"; } ret += \"\"; } ret += \"\"; resultCanvas.innerHTML = ret; resultCanvas.style.display = \"block\"; } function convertData(type, data) { switch (type) { case 1: // binary return parseInt(data).toString(2); break; case 2: // decimal return data; break; case 3: // octal return parseInt(data).toString(8); break; case 4: // hexadecimal return toHexadecimal(data); break; case 5: // ascii return String.fromCharCode(parseInt(data)); break; default: return 'input error'; } } function toHexadecimal(data) { var t = parseInt(data).toString(16); if (t.length % 2 == 1) { t = '0' + t; } return '0x' + t; } window.onload = function () { document.getElementById('result-canvas').style.display = \"none\"; } body { font-family: consolas; background-color: #607D8B; } textarea, li { font-family: consolas; font-size: 10pt; } header { color: #FFFFFF; height: 70px; } footer { clear: both; height: 70px; font-size: 9pt; color: #FFFFFF; margin-top: 5px; } p { margin-left: 5px; } #readme, #input-text, #result-fieldset, #radio-area { border: 1px solid #aaaaaa; border-radius: 3px; } #content-area, #radio-area { background-color: #F7F7F7; padding: 10px; overflow: hidden; } #readme { background-color: #607D8B; color: #FFFFFF; padding: 1px; margin-bottom: 10px; } #column-input { width: 100%; } #column-output { width: 100%; margin-top: 50px; clear: both; } #input-area { padding: 2px; margin: 10px 0px 5px 0px; } #input-text { width: 100%; height: 30px; padding: 0px 5px 0px 5px; font-size: 12pt; resize: none; -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ -moz-box-sizing: border-box; /* Firefox, other Gecko */ box-sizing: border-box; /* Opera/IE 8+ */ } #btn-submit { width: 100px; height: 30px; background-color: #4CAF50; color: #FFFFFF; border: none; cursor: pointer; float: right; } #column-output { font-size: 12pt; overflow-x: auto; } #table-result th, #table-result td { border: 1px solid #000000; padding: 0px; margin: 0px; } #table-result th { background-color: #607D8B; color: #FFFFFF; font-size: 13pt; } #example-table { margin: 0px 0px 10px 40px; } #example-table th, #example-table td { font-size: 10pt; border: 1px solid #FFFFFF; padding: 2px 5px 2px 5px; } .tr-custom:hover { background-color: #B0CDDB; } .highlight { color: #102D3B; } Uh... Converter Uh... Converter. Select Input Data Type. Input code. You can input single data or array.i.e. array format is {1, 2, 3, 4, 5}. MUST input '{', '}' and seperator ',' Run You can see converted results. Example Single Array Binary 100011 {100011, 100100, 111010} Decimal 35 {35, 36, 58} Octal 43 {43, 44, 72} Hexadecimal 23 {23, 24, 3a} ASCII # {#, $, :} Select Data Type Binary Decimal Octal Hexadecimal ASCII Result [email protected], 2016"},{"title":"","date":"2017-02-06T11:36:54.118Z","updated":"2017-02-06T11:36:54.118Z","comments":false,"path":"bookmark/index.html","permalink":"http://lazyrodi.github.io/bookmark/index.html","excerpt":"","text":"Acronyms & Standard Acronym finder Gartner IT Glossary ISO Algorithm Algospot - Algospot Algorithms - @tutorialhorizon Dovelet - www.Dovelet.com Leetcode Project Euler - Project Euler Android Android Design Guide Learn Android - Tutorials Point Architecture (not Dev) Blog_Jacoby - 멋진 건축물들의 사진을 공유해 주심 CharacterSet Ascii Table - Ascii 7 Table KSC5601 - tapito - KSC5601 Table에 대한 tapito님의 자료 KSC5601 - jchern - KSC5601 Table에 대한 jchern님의 자료 UCS-2 - Columbia Univ.에서 제공하는 UCS-2 Table GSM Character Set - Wiki : GSM-7bit Table 및 GSM-8bit에 대해서도 설명 Community and Portal Devpia Clien Parkoz KLDP Stack overflow Source Forge OKKY GSM Arena Ruliweb C++ cplusplus.com C++ Coding Standard Database SQL Style Deep Learning Deep-learning papers Design materialpalette - Android view를 template으로 Material color를 적용해볼 수 있음 pantone color finder - PANTONE Color를 검색해볼 수 있음 IconFinder Google Art Project - 구글에서 제공하는 온라인 가상 미술관 서비스 Blog_MONODREAM Economy DART - 대한민국 전자공시스템 Font Google Fonts - Google에서 제공하는 폰트들. 웹 폰트로도 제공. DaFont Font Awesome Naver Seoul GIT GIT GIT 브랜치 배우기 - GIT을 쉽게 배울수 있는 Site GERRIT Progit - GIT에 대한 교과서(?), https://git-scm.com/book/ko/v2 에서 PDF를 Download받을 수도 있음 REPO Hardware 코코아팹 - 오픈소스 하드웨어에 대해 다루는 커뮤니티. 아두이노 교육. HTTP HTTP - Hypertext Transfer Protocol - HTTP/1.1 IDE, Editor ATOM - ATOM SublimeText3 - Sublime Text 3 VIM - Joins WIKI에서 설명한 VIM 사용법 Visual Studio - Microsoft Visual Studio EditPlus Eclipse - Eclipse Stan4j Memory Analyzer Internet IETF - The Internet Engineering Task Force IETF Tools - RFC 문서를 편하게 찾을 수 있음 Java Oracle - JDK Download JavaDoc 7 - Java 7 Document. http://docs.oracle.com/javase/7/docs 에서 숫자만 바꾸면 다른 버전도 확인 가능. 남궁성의 코드초보스터디 - JAVA의 정석 저자이신 남궁성님의 카페 강이의 JAVA 강좌 Learn Java - Tutorials Point JavaScript JavaScript Pattern - shichuan.github.io : JavaScript Pattern 모음 JavaScript 재입문하기 - Mozilla 재단에서 제공하는 JavaScript 강의. State of the JavaScript Landscape: A Map for Newcomers 한글 번역 Language 621+ Programming Resources - 각종 언어의 Reference 및 학습 자료를 제공. Linux ask ubuntu Marketing Blog_개발마케팅연구소 Mobile communications 3GPP - 3GPP (GSM) 3GPP2 - 3GPP2 (CDMA) TIA - 미국통신산업협회(Telecommunication Industry Association) OMA - Open Mobile Alliance: MMS, WAP Push 등의 규격을 정의 MCC/MNC - Wiki: MCC/MNC를 확인할 수 있음 MOOC 생활코딩 Coursera Udacity KhanAcademy KMooc WikiDocs - 온라인 책을 제작 공유하는 플랫폼 서비스 Operation 한국저작권위원회 Organization 정보통신정책연구원 - KISDI (정보통신정책연구원) 한국관광공사 Perl 한국 펄 사용자 모임 - Perl 강좌 및 커뮤니티 Python 점프 투 파이썬 왕초보를 위한 Python 2.7 Think Python QA STEN ISTQB KSTQB ISO/IEC/IEEE 29119 Software Testing Regular Expression REGEXPER Ruby on rails Ruby guide Ruby tutorial book Integrating rails and bootstrap SMS (Short Message Service) PDU Parser - JavaScript로 만든 Raw PDU Parsing tool. SW Engineering Popular Coding Convention Swift Swift 언어 개발문서 Utility 반디집 - 압축 프로그램 꿀뷰 - 이미지 뷰어 Cygwin Putty Virtual Box Daemon Zoomit draw.io - UML 그리기 Favicon Generator UX Blog_UXD Trend vim Vim Colorscheme Gallery Web apache - Apache getBootstrap - Bootstrap bootstrap-material-design jQuery - jQuery nodejs - nodejs w3schools - HTML, CSS, JavaScript, SQL, PHP, jQuery, Bootstrap JavaEE 6 Tutorial Jonathan Suh - 이 분 대박… Web Plugins DISQUS - Comment plugin InterestCycling 도싸 - 도시의 싸이클 Endomondo Strava VeloViewer - 1년치 Strava 기록을 정산하여 한 화면으로 볼 수 있게 해줌 ProbikeKit - 쇼핑몰. ca, uk 등 잘 선택할 것. Wiggle - 쇼핑몰. 주로 DHB 제품 구매 시 사용 Drama / Movie DramaNote - 일본 드라마 정보 Jamak.kr - 드라마 자막 제공 Food Lovecook - 조리사 자격증 관련 정보 Health Monsterzym Blog_흑자쇠질 - 비속어가 섞여있으므로 주의. Blog_트레이너 이우제 Music 지음아이 - 노래 가사 등 제공 Snow Boarding 헝그리보더 - Facebook : hungryboarders 정보처리 기술사 Blog_Withme DocumentsETC Valve 입사 핸드북 Github Bootstrap Awesome interview"},{"title":"","date":"2017-08-30T14:56:58.309Z","updated":"2017-08-30T14:56:58.309Z","comments":false,"path":"about/index.html","permalink":"http://lazyrodi.github.io/about/index.html","excerpt":"","text":"Location Seoul, South Korea Experience Software engineer LG Electronics Mobile Communications (01/2010 ~ current) Main Android Java Sub C JavaScript Ruby on Rails Certifications Engineer Information Processing (Korea) TRIZ Level 1 ISTQB CTFL Like Cycling Snowboarding"}],"posts":[{"title":"[Security] RSA Encryption","slug":"2017-11-04-rsa_encryption","date":"2017-11-04T03:00:00.000Z","updated":"2017-11-04T13:49:21.765Z","comments":true,"path":"2017/11/04/2017-11-04-rsa_encryption/","link":"","permalink":"http://lazyrodi.github.io/2017/11/04/2017-11-04-rsa_encryption/","excerpt":"","text":"RSA (Rivest Shamir Adleman)공개키와 개인키를 세트로 만들어서 암호화와 복호화를 하는 인터넷 암호화 및 인증 시스템 중 하나. http://terms.naver.com/entry.nhn?docId=1221325&cid=40942&categoryId=32853 Java를 이용한 코드 Server에서 Public Key, Private Key 두 가지를 모두 생성 Client쪽에 Public Key를 제공 Client는 Public Key를 가지고 암호화 Client에서 생성한 암호화 데이터를 Server쪽에서 Private Key를 이용하여 복호화 즉, Key 생성과 복호화 API는 Server쪽에만 있으면 된다. 아래 사이트들 참조. http://swlock.blogspot.kr/2016/01/rsa-java-2-3.html https://gist.github.com/dmydlarz/32c58f537bb7e0ab9ebf http://www.java2s.com/Code/Java/Data-Type/hexStringToByteArray.htm Main.java12345678910111213141516171819202122232425package com.lazyrodi.rsa;import java.security.KeyPair;public class Main { public static void main(String[] args) { KeyPair keyPair = RSA.makeKeyPair(); String publicKey = RSA.byteArrToHexStr(keyPair.getPublic().getEncoded()); String privateKey = RSA.byteArrToHexStr(keyPair.getPrivate().getEncoded()); String text = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry.\"; String longText = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\"; String krText = \"가나다라마\"; String ucsText = \"#&*@§※☆★○☎♡♥\"; byte[] encrypted = RSA.encryptRSA(text, publicKey); byte[] decrypted = RSA.decryptRSA(RSA.byteArrToHexStr(encrypted), privateKey); System.out.println(\"Plain Text = \" + text); System.out.println(\"Encrypted Text = \" + RSA.byteArrToHexStr(encrypted)); System.out.println(\"Decrypted Text = \" + RSA.byteArrToHexStr(decrypted)); System.out.println(\"Decrypted Text String = \" + new String(decrypted)); }} RSA.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136package com.lazyrodi.rsa;import java.security.InvalidKeyException;import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.NoSuchAlgorithmException;import java.security.PrivateKey;import java.security.PublicKey;import java.security.SecureRandom;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;public class RSA { private final static int KEY_SIZE = 2048; public static KeyPair makeKeyPair() { KeyPairGenerator keyPairGenerator = null; try { keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\"); keyPairGenerator.initialize(KEY_SIZE, new SecureRandom()); return keyPairGenerator.genKeyPair(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } public static byte[] encryptRSA(String plainText, String strKey) { if (plainText == null || strKey == null) { System.out.println(\"Log : plainText or strKey is null.\"); return null; } try { Cipher cipher = Cipher.getInstance(\"RSA\"); PublicKey publicKey = getPublicKey(strKey); if (cipher == null || publicKey == null) { System.out.println(\"Log : cipher or publicKey is null.\"); return null; } cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(plainText.getBytes()); } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { e.printStackTrace(); } return null; } public static byte[] decryptRSA(String encryptedText, String strKey) { if (encryptedText == null || strKey == null) { System.out.println(\"Log : encryptedText or strKey is null.\"); return null; } Cipher cipher; try { cipher = Cipher.getInstance(\"RSA\"); if (cipher == null) { System.out.println(\"Log : cipher is null.\"); return null; } cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(strKey)); return cipher.doFinal(hexStrToByteArr(encryptedText)); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } return null; } private static PublicKey getPublicKey(String strKey) { X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStrToByteArr(strKey)); try { KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\"); return keyFactory.generatePublic(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); } return null; } private static PrivateKey getPrivateKey(String strKey) { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(hexStrToByteArr(strKey)); try { KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\"); return keyFactory.generatePrivate(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); } return null; } public static byte[] hexStrToByteArr(String hex) { if (hex == null || hex.length() == 0) { return null; } byte[] byteArr = new byte[hex.length() / 2]; for (int i = 0; i < byteArr.length; i++) { byteArr[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); } return byteArr; } public static String byteArrToHexStr(byte[] byteArr) { if (byteArr == null || byteArr.length == 0) { return null; } StringBuilder hexString = new StringBuilder(byteArr.length + 2); for (int i = 0; i < byteArr.length; i++) { String hexNum = \"0\" + Integer.toHexString(0xff & byteArr[i]); hexString.append(hexNum.substring(hexNum.length() - 2)); } return hexString.toString(); }} 문제 발생문제 1 - Encrypt 시간이 오래 걸림Main.java의 RSA.encryptRSA() 호출부의 앞뒤에 System.currentTimeMillis를 넣어 시간을 체크해 보았더니 약 270 ms 가 소요되었다. 단발성 데이터라면 별로 상관 없지만… 나에게는 조금 문제가 된다. 시험 1 - 소요시간 확인long start = System.currentTimeMillis();byte[] encrypted = RSA.encryptRSA(text, publicKey);long end = System.currentTimeMillis();System.out.println(\"encrypted time = \" + (end - start)); 결과 1encrypted time = 260 시험 2 - 반복 시 소요시간 확인아래 코드를 넣어 100회 반복해 보았다. long start = 0;long end = 0;byte[] encrypted = null;for (int i = 0; i < 100; i++) { start = System.currentTimeMillis(); encrypted = RSA.encryptRSA(text, publicKey); end = System.currentTimeMillis(); System.out.println(\"encrypted time = \" + (end - start));} 결과 2처음 실행될 때에만 시간이 오래 소요되고 그 이후부터는 금방 encrypt 작업이 진행되는 것을 확인할 수 있었다. encrypted time = 273encrypted time = 0encrypted time = 1encrypted time = 0encrypted time = 1encrypted time = 0 문제 1의 해결책javax.crypto.Cipher 객체를 사용할 때 시간이 소요되는 것 같아서 확인하여보니 Cipher.getInstance("RSA") 로 객체를 가져올 때 대부분의 시간이 소요되었다. 내부 소스는 안 찾아봤지만 init하는 과정이 긴 것으로 추측된다. init 메서드를 생성해본다. init 시에 시간이 대부분 소요되고, 실제 암호화를 진행할 때는 더 이상 추가시간이 필요하지 않게 되었다. public class RSA { private final static int KEY_SIZE = 2048; private static Cipher mCipher = null; public static void init() { if (mCipher == null) { try { mCipher = Cipher.getInstance(\"RSA\"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { e.printStackTrace(); } } } ... 문제 2 - 245 byte 길이까지만 지원함시험 1 - 다양한 String 시험몇 가지 시험을 더 해보기 위해 다양한 String으로 시험을 진행했다. String text = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry.\";String longText = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\";String krText = \"가나다라마바사아자차카타파하가나다라마바사아자차카타파하\";String ucsText = \"#&*@§※☆★○☎♡♥Æ£¤¥\"; 결과 1아래와 같은 Exception이 발생하였다. javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344) at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389) at javax.crypto.Cipher.doFinal(Cipher.java:2165) at com.lazyrodi.rsa.RSA.encryptRSA(RSA.java:64) at com.lazyrodi.rsa.Main.main(Main.java:23) 문제 2의 해결책위에서 String을 쪼개느냐 RSA Class에서 String을 쪼개느냐 고민을 하다가 편하게 위에서 쪼개기로 한다. 내가 사용하는 데이터는 복호화는 수동으로 하여 확인만 하면 되는 것이므로… 암호화 하는 쪽에만 적용한다. 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package com.lazyrodi.rsa;import java.security.KeyPair;public class Main { private static final int MAX_LEN = 245; public static void main(String[] args) { KeyPair keyPair = RSA.makeKeyPair(); String publicKey = RSA.byteArrToHexStr(keyPair.getPublic().getEncoded()); String privateKey = RSA.byteArrToHexStr(keyPair.getPrivate().getEncoded()); String longText = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\"; byte[] encrypted = null; RSA.init(); String[] textSegments = getTextSegments(longText); for(String text : textSegments) { encrypted = RSA.encryptRSA(text, publicKey); ... } } private static String[] getTextSegments(String longText) { int segments = calcSegments(longText.length()); String[] textSegments = new String[segments]; for (int i = 0; i < segments - 1; i++) { textSegments[i] = longText.substring(i * MAX_LEN, (i * MAX_LEN) + MAX_LEN); } textSegments[segments - 1] = longText.substring((segments - 1) * MAX_LEN, longText.length()); return textSegments; } private static int calcSegments(int len) { if (len == 0) { return 0; } if (len % MAX_LEN == 0) { return len / MAX_LEN; } return (len / MAX_LEN) + 1; }} 최종 완성 및 확인용 코드Main.java12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061package com.lazyrodi.rsa;import java.security.KeyPair;public class Main { private static final int MAX_LEN = 245; public static void main(String[] args) { KeyPair keyPair = RSA.makeKeyPair(); String publicKey = RSA.byteArrToHexStr(keyPair.getPublic().getEncoded()); String privateKey = RSA.byteArrToHexStr(keyPair.getPrivate().getEncoded()); String shortText = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry.\"; String longText = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\"; String borderText = \"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.\"; byte[] encrypted = null; RSA.init(); String[] textSegments = getTextSegments(longText); for(String text : textSegments) { encrypted = RSA.encryptRSA(text, publicKey); byte[] decrypted = RSA.decryptRSA(RSA.byteArrToHexStr(encrypted), privateKey); System.out.println(\"Plain Text = \" + text); System.out.println(\"Encrypted Text = \" + RSA.byteArrToHexStr(encrypted)); System.out.println(\"Decrypted Text = \" + RSA.byteArrToHexStr(decrypted)); System.out.println(\"Decrypted Text String = \" + new String(decrypted)); } } private static String[] getTextSegments(String longText) { int segments = calcSegments(longText.length()); String[] textSegments = new String[segments]; for (int i = 0; i < segments - 1; i++) { textSegments[i] = longText.substring(i * MAX_LEN, (i * MAX_LEN) + MAX_LEN); } textSegments[segments - 1] = longText.substring((segments - 1) * MAX_LEN, longText.length()); return textSegments; } private static int calcSegments(int len) { if (len == 0) { return 0; } if (len % MAX_LEN == 0) { return len / MAX_LEN; } return (len / MAX_LEN) + 1; }} RSA.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145package com.lazyrodi.rsa;import java.security.InvalidKeyException;import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.NoSuchAlgorithmException;import java.security.PrivateKey;import java.security.PublicKey;import java.security.SecureRandom;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;public class RSA { private final static int KEY_SIZE = 2048; private static Cipher mCipher = null; public static void init() { if (mCipher == null) { try { mCipher = Cipher.getInstance(\"RSA\"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { e.printStackTrace(); } } } public static KeyPair makeKeyPair() { KeyPairGenerator keyPairGenerator = null; try { keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\"); keyPairGenerator.initialize(KEY_SIZE, new SecureRandom()); return keyPairGenerator.genKeyPair(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } public static byte[] encryptRSA(String plainText, String strKey) { if (plainText == null || strKey == null) { System.out.println(\"Log : plainText or strKey is null.\"); return null; } try { PublicKey publicKey = getPublicKey(strKey); if (mCipher == null || publicKey == null) { System.out.println(\"Log : cipher or publicKey is null.\"); return null; } mCipher.init(Cipher.ENCRYPT_MODE, publicKey); return mCipher.doFinal(plainText.getBytes()); } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) { e.printStackTrace(); } return null; } public static byte[] decryptRSA(String encryptedText, String strKey) { if (encryptedText == null || strKey == null) { System.out.println(\"Log : encryptedText or strKey is null.\"); return null; } Cipher cipher; try { cipher = Cipher.getInstance(\"RSA\"); if (cipher == null) { System.out.println(\"Log : cipher is null.\"); return null; } cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(strKey)); return cipher.doFinal(hexStrToByteArr(encryptedText)); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } return null; } private static PublicKey getPublicKey(String strKey) { X509EncodedKeySpec spec = new X509EncodedKeySpec(hexStrToByteArr(strKey)); try { KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\"); return keyFactory.generatePublic(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); } return null; } private static PrivateKey getPrivateKey(String strKey) { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(hexStrToByteArr(strKey)); try { KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\"); return keyFactory.generatePrivate(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); } return null; } public static byte[] hexStrToByteArr(String hex) { if (hex == null || hex.length() == 0) { return null; } byte[] byteArr = new byte[hex.length() / 2]; for (int i = 0; i < byteArr.length; i++) { byteArr[i] = (byte)Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); } return byteArr; } public static String byteArrToHexStr(byte[] byteArr) { if (byteArr == null || byteArr.length == 0) { return null; } StringBuilder hexString = new StringBuilder(byteArr.length + 2); for (int i = 0; i < byteArr.length; i++) { String hexNum = \"0\" + Integer.toHexString(0xff & byteArr[i]); hexString.append(hexNum.substring(hexNum.length() - 2)); } return hexString.toString(); }}","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Security","slug":"Dev/Security","permalink":"http://lazyrodi.github.io/categories/Dev/Security/"}],"tags":[{"name":"encryption","slug":"encryption","permalink":"http://lazyrodi.github.io/tags/encryption/"},{"name":"decryption","slug":"decryption","permalink":"http://lazyrodi.github.io/tags/decryption/"},{"name":"rsa","slug":"rsa","permalink":"http://lazyrodi.github.io/tags/rsa/"},{"name":"cipher","slug":"cipher","permalink":"http://lazyrodi.github.io/tags/cipher/"}]},{"title":"[HTML] Snippet","slug":"2017-10-22-web-snippets","date":"2017-10-22T08:00:00.000Z","updated":"2017-11-04T13:51:09.752Z","comments":true,"path":"2017/10/22/2017-10-22-web-snippets/","link":"","permalink":"http://lazyrodi.github.io/2017/10/22/2017-10-22-web-snippets/","excerpt":"","text":"Document start, Load favicon, css, js, UTF-8<!DOCTYPE html><html><head> <meta charset=\"UTF-8\"> <link rel=\"icon\" href=\"..../xxx.ico\"> <link rel=\"stylesheet\" type=\"text/css\" href=\"..../xxx.css\"> <script src=\"..../xxx.js\"></script></head><body></body></html> Footer 하단에 붙이기<html><head> <style> body { display: flex; height: 100%; flex-direction: column; margin: 0px; } main { overflow: auto; flex: 1; } footer { background-color: darkgrey; width: 100%; height: 100px; } </style></head><body> <header> </header> <main> </main> <footer> </footer></body></html> Menu item 클릭 시 배경 activate 시키기Source with JQuery <html><head><script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js\"></script><style type=\"text/css\">.active { background-color: #aaaaaa;}</style><script>$(document).ready(function() { $menuItem = $('#menu').children('.item'); $menuItem.each(function() { $(this).click(onClickMenuItem); });});onClickMenuItem = function() { $('#menu').children('.item').filter('.active').removeClass('active'); $(this).addClass('active');}</script></head><body><div id=\"menu\"> <a class=\"item active\">menu 1</a> <a class=\"item\">menu 2</a> <a class=\"item\">menu 3</a></div></body></html>","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Web","slug":"Dev/Web","permalink":"http://lazyrodi.github.io/categories/Dev/Web/"}],"tags":[{"name":"web","slug":"web","permalink":"http://lazyrodi.github.io/tags/web/"},{"name":"html","slug":"html","permalink":"http://lazyrodi.github.io/tags/html/"},{"name":"sniffet","slug":"sniffet","permalink":"http://lazyrodi.github.io/tags/sniffet/"}]},{"title":"[Android] Building Apps with Content Sharing (1)","slug":"2017-10-14-android-building_apps_with_content_sharing_1","date":"2017-10-14T00:00:00.000Z","updated":"2017-10-22T06:16:34.679Z","comments":true,"path":"2017/10/14/2017-10-14-android-building_apps_with_content_sharing_1/","link":"","permalink":"http://lazyrodi.github.io/2017/10/14/2017-10-14-android-building_apps_with_content_sharing_1/","excerpt":"","text":"Android developer site study Building Apps with Content Sharing Sharing Simple Data Sharing Files Sharing Files with NFC https://developer.android.com/training/index.html 간단한 Data 공유하기Android app의 중요한 기능 중 하나는 다른 app과 통신할 수 있다는 것이다. 간단한 data를 다른 app으로 전송하기Intent 생성 시 원하는 action을 명확히 해야 한다. Android는 몇 가지 action에 대해 정의하고 있다. ACTION_SEND는 하나의 activity에서 다른 activity로 data를 보낼 때 사용된다. process 경계를 넘어갈 수도 있다. Data를 다른 activity로 보내기 위해서는 data와 type을 지정하는 것이 전부이다. 그러면 시스템은 수신이 가능한 적당한 activity를 찾아 사용자에게 보여준다(여러가지 선택이 가능하다면). 아니면 바로 해당 activity를 시작한다(하나의 선택만 가능하다면). 이와 비슷하게, 개발자는 activity에서 사용 가능한 data와 type에 대해 manifest에 기술하여 다른 app들에게 알릴 수 있다. Intetnt를 사용하여 app 사이에 data를 주고받는 것은 가장 일반적인 공유 방법이다. Intent는 쉽고 빠르게 정보를 주고 받을 수 있게 해준다. Text 정보 전송하기가장 간단하고 일반적인 사용법은 ACTION_SEND action을 사용하여 text를 다른 activity로 전송하는 것이다. 예를 들어, 브라우저는 현재 보고있는 페이지의 URL 정보를 다른 app과 공유할 수 있다. 이는 글이나 웹사이트를 다른 사람과 공유하고 싶을 때 편하게 사용할 수 있는 방법이다. Intent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, \"This is my text to send.\");sendIntent.setType(\"text/plain\");startActivity(sendIntent); ACTION_SEND와 text/plane type에 맞는 filter가 존재한다면 Android 시스템은 해당 app을 구동시킬 것이다. 여러 개의 app을 발견한다면 시스템은 선택상자 (“chooser”)를 사용자에게 보여주고 선택하게 할 것이다. 하지만, Intent.createChooser()를 호출할 때 intent를 전달할 경우 항상 chooser을 보여주게 된다. 이 방법에는 몇 가지 장점이 있다. 사용자가 해당 intent에 대해 default action을 지정해둔 경우에도 chooser를 보여준다. 알맞은 app이 없을 때에는 Android는 시스템 메시지를 보여준다. Chooser dialog의 Title을 정할 수 있다. 이를 반영한 코드는 아래와 같다. Intent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, \"This is my text to send\");sendIntent.setTYpe(\"text/plane\");startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to))); 실행 결과는 아래 그림과 같다. 선택적으로, 개발자는 몇 가지 기본 EXTRA들을 세팅할 수 있다: EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC, EXTRA_SUBJECT. 만약, 수신하는 app이 이 EXTRA들을 지원하지 않는다면 간단히 무시된다. Gmail 등의 몇몇 e-mail app에서는 EXTRA_EMAIL, EXTRA_CC등의 EXTRA를 사용할 때 String[] 을 기대하므로 이러한 EXTRA를 intent에 추가할 때에는 putExtra(String, String[])을 사용하도록 하라. Binary 정보 전송하기Binary 데이터는 적당한 MIME type을 설정하여 ACTION_SEND를 사용하고 EXTRA_STREAM 내에 적절한 URI를 지정하여 공유할 수 있다. 일반적으로 image를 공유할 때 사용되지만 어떤 형태의 binary 데이터도 공유할 수 있다. Intent shareIntent = new Intent();shareIntent.setAction(Intent.ACTION_SEND);shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);shareIntent.setType(\"image/jpeg\");startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to))); 아래 사항들에 대해 주의하라. MIME type으로 “/“을 사용할 수 있지만 이는 generic data stream을 처리할 수 있는 activity만 이를 수신할 수 있다. 수신하는 app은 Uri가 가리키는 data에 접근할 수 있는 권한을 필요로 한다. 따라서 아래와 같은 방식을 추천한다. Data를 ContentProvider에 저장한 후 다른 app이 내 app의 provider에 저장할 수 있는 permission이 있는지 확인한다. 추천하는 방법은 per-URI permissions를 사용하여 접근을 준비해두는 것이다. 이는 intent를 수신하는 app에게 임시로 permission을 부여한다. 쉬운 방법으로는 FileProvider helper class를 사용하여 ContentProvider를 생성하는 것이다. 시스템의 MediaStore를 사용하라. MEdiaStore는 비디오, 오디오, 이미지 MIME type에 최적화 되어있다. 하지만 Android 3.0 (API level 11)부터 non-media type들도 저장할 수 있게 되었다(자세한 것은 MediaStore.Files를 참조하라.). 공유에 적합한 content:// 스타일의 Uri 을 onScanCompleted()에 통과시킨 후 scanFile()을 사용하여 파일들을 MediaStore에 저장할 수 있다. 한 번 시스템 MediaStore에 추가한 컨텐츠는 기기의 모든 app들이 접근할 수 있다는 사실을 반드시 기억하라. 여러 개의 컨텐츠를 한 번에 보내기여러 개의 컨텐츠를 한 번에 공유하기 위해서는 ACTION_SEND_MULTIPLE action에 URI의 목록을 넣어서 보내면 된다. MIME type은 공유하고자 하는 컨텐츠들에 따라 넣어준다. 예를 들어, 3개의 JPEG 이미지들은 “image/jpeg”로 한다. 이미지들을 섞어서 보낸다면 “iamge/*”로 보낸다. 만약 공유하고자 하는 type들이 광범위하게 다양할 때에는 “/“를 사용한다. 시작할 때 기술했던 것처럼 해당 데이터를 수신하는 것은 수신 app의 역량에 달려있다. ArrayList<Uri> imageUris = new ArrayList<Uri>();imageUris.add(imageUri1); // iamge URI 추가imageUris.add(imageUri2);Intent shareIntent = new Intent();shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);shareIntent.setType(\"image/*\");startActivity(Intent.createChooser(shareIntent, \"Share images to.\")); 다른 app으로부터 간단한 데이터를 수신하기Manifest 업데이트하기Intent filter를 통해 이 app 컴포넌트가 어떤 intent에 접근하길 원하는지 시스템에 알려줄 수 있다. Intent를 수신하기 위해 <intent-filter> 를 manifest에 정의해야 한다. 만약 app이 text, 하나의 image 또는 여러 개의 image들을 수신하길 원한다면 아래와 같이 작성하면 된다. <activity android:name=\".ui.MyActivity\"> <intent-filter> <action android:name=\"android.intent.action.SEND\" /> <category android:name=\"android.intent.category.DEFAULT\" /> <data android:mimeType=\"image/*\" /> </intent-filter> <intent-filter> <action android:name=\"android.intent.action.SEND\" /> <category android:name=\"android.intent.category.DEFAULT\" /> <data android.mimeType=\"text/plain\" /> </intent-filter> <intent-filter> <action android:name=\"android.intent.action.SEND_MULTIPLE\" /> <category android:name=\"android.intent.category.DEFAULT\" /> <data android.mimeType=\"image/*\" /> </intent-filter></activity> 더 많은 정보는 Intents and Intent Filters 에서 확인할 수 있다. 다른 app이 이런 정보들을 담은 intent를 생성하고 startActivity()를 통해 공유할 때 내 app이 intent chooser의 목록에 보이게 될 것이다. 만약 사용자가 내 app을 선택한다면 그에 해당하는 activity (여기서는 .ui.MyActivity)가 시작될 것이다. 이 때부터 전달된 정보를 통해 app 내에서 가공하여 사용할 수 있다. 수신한 컨텐츠를 다루기Intent를 통해 전달된 컨텐츠를 다루기 위해 getIntent()를 호출하여 Intent 객체를 얻어올 수 있다. 객체를 한 번 갖게 되면 컨텐츠의 내용을 살펴본 후 다음에 무엇을 할지 정하게 된다. 만약 이 activity가 launcher 등의 시스템의 다른 부분으로부터 시작이 가능한 것이라면 intent가 수신되었을 때 의도를 파악하는 것이 중요하다. 123456789101112131415161718192021222324252627282930313233343536373839404142void onCreate(Bundle savedInstanceState) { ... // intent, action, MIME type을 얻어온다 Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if (Intent.ACTION_SEND.equals(action) && type != null) { if (\"text/plain\".equals(type)) { handleSendText(intent); // 수신한 text 다루기 } else if (type.startsWith(\"image/\")) { handleSendImage(intent); // 수신한 이미지 처리 } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { if (type.startsWith(\"image/\")) { handleSendMultipleImages(intent); // 수신한 여러 개의 이미지 처리 } } else { // 다른 intent를 처리 (ex. home screen으로부터 시작되었을 때 등) } ...}void handleSendText(Intent intent) { String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); if (sharedTExt != null) { // 수신한 text를 사용하여 UI 업데이트 }}void handleSendImage(Intent intent) { Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); if (imageUri != null) { // 수신한 image를 사용하여 UI 업데이트 }}void handleSendMultipleImages(Intent intent) { ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); if (imageUris != null) { // 수신한 image들을 사용하여 UI 업데이트 }} 수신한 데이터를 처리할 때는 특별히 더 주의를 기해야 한다. 왜냐하면 어떤 app에서 어떤 데이터를 보낸 것인지 알 수 없기 때문이다. 예를 들어, 틀린 MIME type이 세팅되거나 엄청나게 큰 image 파일이 전송될 수도 있다. 마찬가지로, binary 데이터를 처리할 때에도 thread를 분리하여 main(UI) thread가 아닌 곳에서 처리하도록 하자. 간단한 공유 action을 추가해보기효과적이고 사용자 친화적인 action을 처리 위해 ActionProvider와 함께 ActionBar를 사용한다면 좀 더 쉬운 구현이 가능하다. Action bar에 한 번 메뉴 아이템이 추가되고 난 후 ActionProvider는 해당 아이템의 외견과 행동 모두를 제어한다. ShareActionProvider의 경우 개발자는 share intent를 준비하고 그것이 나머지를 수행한다. ShareActionProvider는 API level 14 이상에서 사용 가능하다. 메뉴 선언 업데이트하기ShareActionProvider와 함께 시작하기 위해 메뉴 리소스 파일 내의 <item>에 android:actionProviderClass를 정의해야 한다. <menu xmlns:android=\"http://schemas.android.com/apk/res/android\"> <item android:id=\"@+id/menu_item_share\" android:showAsAction=\"ifRoom\" android:title=\"Share\" android:action:actionProviderClass= \"android.widget.ShareActionProvider\" /> ...</menu> 이 방법은 아이템의 외견 및 기능에 대한 권한을 ShareActionProvider로 이관한다. 하지만, 개발자는 공유하고 싶은 내용을 provider에 전달해줘야 한다. Share intent 설정하기ShareActionProvider가 기능을 수행하기 위해 개발자는 반드시 share intent를 준비해두어야 한다. 이 Share intent는 앞서 “간단한-data를-다른-app으로-전송하기”에서 본 것과 같이 ACTION_SEND action과 추가 data인 EXTRA_TEXT, EXTRA_STREAM 등을 포함하여 작성되어야 한다. Share intent 할당을 위해 메뉴 리소스를 Activity나 Fragment에 inflating하는 동안 올바른 MenuItem을 찾아야 한다. 그 후, MenuItem.getActionProvider()를 호출하여 ShareActionProvider의 인스턴스를 찾아야 한다. setShareIntent()를 사용하여 action item과 연관된 share intent를 업데이트 한다. 123456789101112131415161718192021222324Private ShareActionProvider mShareActionProvider;...@Overridepublic boolean onCreateOptionsMenu(Menu menu) { // 메뉴 리소스 파일을 inflat한다. getMenuInflater().inflate(R.menu.share_menu, menu); // MenuItem을 ShareActionProvider와 함께 위치시킨다. MenuItem item = menu.findItem(R.id.menu_item_share); // ShareActionProvider를 fetch하고 저장한다. mShareActionProvider = (ShareActionProvider) item.getActionProvider(); // 디스플레이 메뉴로 true를 리턴한다. return true;}// share intent의 update를 호출한다.private void setShareIntent(Intent shareIntent) { if (mShareActionProvider != null) { mShareActionProvider.setShareIntent(shareIntent); }} Share intent는 메뉴를 생성할 때 한 번만 설정해두면 된다. 또는 UI 변경이 발생할 때 업데이트 할 수도 있다. 예를 들어, Gallery app에서 사진을 전체 화면으로 보면서 화면을 전환할 때, sharing intent가 변경된다. 출처 https://developer.android.com/training/sharing/shareaction.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] Getting Started (6)","slug":"2017-10-06-android-getting_started_6","date":"2017-10-06T02:00:00.000Z","updated":"2017-10-14T02:37:29.567Z","comments":true,"path":"2017/10/06/2017-10-06-android-getting_started_6/","link":"","permalink":"http://lazyrodi.github.io/2017/10/06/2017-10-06-android-getting_started_6/","excerpt":"","text":"Android developer site study Getting Started Building Your First App Supporting Different Devices uilding a Dynamic UI with Fragments Saving Data Interacting with Other Apps Working with System Permissions https://developer.android.com/training/index.html 시스템 Permission 사용시스템 무결성을 보장하고 사용자의 개인정보를 보호하기 위해 Android는 접근이 제한된 Sandbox에서 각각의 app을 실행한다. App이 Sandbox 외부의 리소스나 정보를 사용하고자 할 경우 Permission을 명확히 요청해야 한다. App이 요청하는 Permission 유형에 따라 시스템은 자동으로 Permission을 부여하거나 사용자에게 Permission 부여를 요청한다. Permission 선언하기App에 Permission이 필요함을 선언하기 위해 해당 Permission을 App Manifest에 나열해야 한다. Permission이 얼마나 민감한지에 따라 시스템이 자동으로 Permission을 부여할 수도 있고, 기기 사용자가 요청을 승인해야 할 수도 있다. 예를 들어, 기기의 플래시를 켜는 Permission은 시스템이 자동으로 부여하지만 사용자의 연락처를 읽는 Permission은 해당 Permission의 승인을 사용자에게 요청한다. Android 5.1 이하 버전에서는 App을 설치할 때 이런 Permission에 대해 묻게 되고, Android 6.0 이상의 버전에서는 App을 실행 중 Permission이 필요한 기능을 사용할 때 사용자에게 Permission을 요청하게 된다. App에 필요한 Permission 판별하기일반적으로 App이 직접 생성하지 않은 정보나 리소스를 사용하거나 기기 또는 기타 app의 동작에 영향을 미치는 동작을 수행할 때마다 Permission을 필요로 한다. 예를 들어, App이 인터넷에 접속하거나 카메라를 사용하거나 WiFi를 사용할 때 Permission이 필요하다. App이 직접 수행하는 동작에 대해서만 Permission이 필요하며 다른 App에게 작업을 수행하거나 정보를 제공하도록 요청하는 경우에는 Permission을 필요로하지 않는다. App이 사용자 주소록을 읽어야 하는 경우는 READ_CONTACTS Permission이 필요하지만 intent를 사용하여 연락처 app에 정보를 요청하는 경우 app에는 Permission이 필요없다. (연락처 app에는 Permission이 필요하다.) Manifest에 Permission 추가하기App에 권한이 필요함을 선언하려면 <uses-permission>요소를 최상위 <manifest> 요소의 하위 요소로 Manifest에 넣는다. 예를 들어 SMS 메시지를 보내야 하는 App은 manifest에 아래 줄이 있어야 한다. <manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.example.snazzyapp\"> <uses-permission android:name=\"android.permission.SEND_SMS\"/> <application ...> ... </application></manifest> 런타임에 permission 요청하기Android 6.0 (API level 23)부터는 App이 설치될 때가 아니라 App이 실행되는 중에 permission을 부여한다. App을 설치하거나 update할 때 permission을 부여할 필요가 없으므로 설치 절차가 간소화된다. 사용자는 언제든지 App 설정 화면으로 이동하여 권한을 취소할 수 있다. 시스템 permission은 normal과 dangerous 두 가지로 나눌 수 있다. normal 사용자 개인정볼르 직접 위험에 빠뜨리지 않는다. App이 manifest에 normal permission을 나열하는 경우, 시스템은 자동으로 permission을 부여한다. dangerous 사용자 기밀 데이터에 대한 접근은 app에 부여할 수 있다. App이 manifest에 normal permission을 나열하는 경우, 시스템은 자동으로 permission을 부여한다. App이 manifest에 dangerous permission을 나열하는 경우, 사용자는 app에 대한 명시적 승인을 해야한다. 모든 버전의 Android에서 App은 manifest에 필요한 normal permission, dangerous permission을 모두 선언해야 한다. 그러나 이 선언의 효과는 시스템 버전과 App의 target SDK 버전에 따라 달라진다. Android 5.1 이하의 버전 또는 App의 target SDK가 22 이하인 경우 사용자는 App을 설치할 때 permission을 부여해야 하며, permission을 부여하지 않는 경우 시스템이 app을 설치하지 않는다. Android 6.0 이상의 기기이고 App의 target SDK가 23 이상인 경우 app이 실행되는 중에 필요한 권한을 요청해야 한다. 사용자는 각 permission을 부여하거나 거부할 수 있으며, 사용자가 permission 요청을 거부하더라도 제한된 성능으로 app이 계속 실행될 수 있다. Android 6.0 (API 23)부터는 app이 더 낮은 API level을 대상으로 하더라도 사용자가 언제든지 모든 app에서 permission을 취소할 수 있다. 따라서 App을 제작할 때 필요한 permission이 없을 때에도 app이 올바르게 동작하는지 테스트 해야한다. permission 체크하기App에 dangerous permission이 필요한 경우 해당 permission이 요구되는 작업을 실행할 때마다 해당 permission의 보유 여부를 확인해야 한다. 사용자는 언제든지 permission을 취소할 수 있기 때문이다. Permission 보유 여부를 확인하기 위해 ContextCompat.checkSelfPermission() 메서드를 호출한다. 아래 코드는 activity가 캘린더에 write permission을 가지고 있는지 확인한다. // thisActivity가 현재의 activity라 가정한다.int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR); App이 permission을 보유한 경우 이 메서드는 PackageManager.PERMISSION_GRANTED를 반환하고, app은 계속 작업을 진행할 수 있다. App에 권한이 없는 경우 이 메서드는 PERMISSION_DENIED를 반환하고 app이 사용자에게 명시적으로 권한을 요청해야 한다. Permission 요청하기App Manifest에 나열되어 있는 Dangerous permission들이 필요한 경우 사용자에게 permission 부여를 요청해야 한다. Android에서는 permission 요청에 사용할 수 있는 여러 API를 제공하고 있으며, 이러한 메서드들을 호출하면 표준 Android dialog가 나타난다. App에 permission이 필요한 이유를 설명하라사용자가 사진 app을 실행하는 경우 사용자는 app이 카메라 permission을 요청한다고 해도 놀라지 않을 것이다. 하지만 app이 사용자의 위치나 연락처에 접근하려고 한다면 사용자가 이해하지 못할 것이다. 때문에 permission을 요청하기 전에 사용자에게 설명하는 것을 고려해야 한다. 이 때 사용할 수 있는 접근방법은 사용자가 해당 permission 요청을 이미 거절한 경우에만 설명을 제공하는 것이다. Permission이 요구되는 기능의 사용을 사용자가 계속 시도하면서도 계속하여 permission 요청을 거절한다면, 아마도 이 사용자는 해당 기능을 제공하기 위해 app에 permission이 필요한 이유를 모를 수도 있다. 이럴 때에는 설명을 하는 것이 좋을 수도 있다. 사용자가 설명이 필요할 수도 있는 상황을 찾도록 Android에서는 유틸리티 메서드인 shouldShowRequestPermissionRationale()을 제공하고 있다. 이전에 app이 해당 permission을 요청했고 사용자가 요청을 거부한 경우 이 메서드는 true를 반환한다. 과거에 사용자가 permission 요청을 거절하고 permission 요청 시스템 대화상자에서 Don't ask again 옵션을 선택한 경우, shouldShowRequestPermissionRationale() 메서드는 false를 반환한다. App이 permission을 가지지 못하도록 기기 정책에서 금지하는 경웨도 이 메서드는 ‘false’를 반환한다. 필요한 permission 요청하기App에 필요한 permission이 아직 없는 경우, requestPermission() 메서드 중 하나를 호출하여 적절한 permission을 요청해야 한다. App이 원하는 permission을 전달하고, 이 permission 요청을 식별하기 위해 정수 형태의 request code도 전달한다. 이 메서드는 비동기식으로 작동하며 즉각 반환된다. 사용자가 대화상자에 응답한 후에 시스템은 그 결과를 가지고 app의 콜백 메서드를 호출한 후 app이 requestPermission()에 전달했던 것과 동일한 request code를 전달한다. 아래 코드는 App이 사용자의 연락처를 읽을 permission을 갖고 있는지 확인하고 필요한 경우 permission을 요청하는 코드이다. 123456789101112131415161718// thisActivity 가 현재 activity이다.if (ContextCompat.checkSelfPermission(thisActivity, Manifest.pemission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // 설명이 필요한가? if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) { // 사용자에게 비동기 요청으로 \"설명\" 한다. - block하면 안된다. // 이 스레드는 사용자의 응답에 대해 대기해야 한다. // 사용자가 설명을 읽어본 후 permission을 다시 요청해야 한다. } else { // 설명이 필요하지 않다면 바로 permission을 요청한다. ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS); // MY_PERMISSIONS_REQUEST_READ_CONTACTS는 app이 정의한 상수이다. // 콜백 메서드는 요청의 결과를 얻어온다. }} App이 requestPermission()를 호출하는 경우 시스템은 표준 대화상자를 사용자에게 표시한다. App은 이 대화상자를 구성하거나 변경할 수 없다. 사용자에게 정보나 설명을 제공해야 하는 경우에는 requestPermissions()를 호출하기 전에 제공해야 한다. Permission 요청에 대한 응답 처리하기App이 permission을 요청하면 시스템이 사용자에게 대화상자를 표시하고 사용자가 응답하면 시스템은 app의 onRequestPermissionResult() 메서드를 호출하여 사용자 응답을 전달한다. Permission이 부여되었는지 여부를 확인하려면 app의 이 메서드를 재정의하면 된다. 이 콜백에는 requestPermissions()에 전달한 것과 동일한 request code가 포함된다. App이 READ_CONTACTS 접근을 요청하는 경우 아래와 같은 콜백 메서드를 가질 수 있다. 1234567891011121314151617@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_READ_CONTACTS: { // 요청이 취소되면 result 배열은 비어있다. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission을 부여받았음. // contacts와 관련된 작업을 수행하면 됨. } else { // permission을 부여받지 못하였음. // permission과 관련 없는 작업을 진행하도록 함. } return; } // 추가 작업들을 수행. }} 시스템이 표시하는 대화상자에서는 app이 접근해야 하는 Permission Group에 대해 설명하지만 특정 permission을 나열하지는 않는다. READ_CONTACTS를 요청해야 하는 경우, app이 기기의 연락처에 접근해야 한다는 메시지만 시스템 대화상자에 나타나게 된다. 사용자는 각 permission group에 대해 한 번만 permission을 부여해야 한다. App이 해당 group에 있는 다른 permission(manifest에 작성해둔)을 요청하는 경우, 시스템이 자동으로 permission을 부여하게 된다. Permission을 요청하는 경우 시스템은 사용자가 시스템 대화상자를 통해 요청을 명시적으로 부여했을 때와 동이한 방식으로 onRequestPermissionResult()를 호출하고 PERMISSION_GRANTED를 전달하게 된다. 사용자가 또 다른 permission을 같은 group에 이미 부여했다고 하더라도 app은 필요한 모든 permission을 명시적으로 요청해야 한다. 또한, permission을 group응로 묶는 것은 추후 Android 업데이트 시 변경될 수도 있다. 따라서 App에서 특정 permission이 같은 group에 있거나 없다는 가정을 하고 코딩을 해서는 안된다. 예를 들어, READ_CONTACTS, WRITE_CONTACTS를 둘 다 manifest에 작성했다고 해보자. 사용자가 READ_CONTACTS permission을 부여한 이후 app이 WRITE_CONTACTS permission을 요청하면 시스템은 사용자와의 상호작용 없이 즉시 app에 해당 permission을 부여하게 된다. 사용자가 permission을 거부하는 경우 app은 적절한 동작을 취해야 한다. 예를 들어, 사용자가 해당 permission이 필요한 작업을 요청한 경우 수행할 수 없는 이유를 설명해 줘야 한다. 시스템이 사용자에게 permission을 허용하도록 요청하면 사용자에게는 시스템에게 해당 permission을 다시는 요청하지 말라고 지시할 선택권이 있다. 그런 경우, app이 해당 permission을 요청하기 위해 requestPermissions()를 사용할 때마다 시스템이 즉시 이 요청을 거부한다. 시스템은 사용자가 명시적으로 요청을 다시 거절했을 때와 동일한 방식으로 onRequestPermissionsResult() 콜백 메서드를 호출하고 PERMISSION_DENIED를 전달한다. 즉, requestPermissions()를 호출하였더라도 사용자는 아무런 작업을 하지 않았을 수 있는 것이다. Permission 사용 참고사항자칫 사용자에게 필요한 기능 이상의 permission을 요청하게 될 수도 있다. 사용자는 혼란에 빠질 수도 있고 걱정에 잠길 수도 있다. 또한 사용자는 너무 많은 permission을 요청하는 경우 app을 사용하지 않게 될 수도 있다. 사용자 경험의 나쁜 예를 피하기 위해 아래의 글들을 참조하라. Permission을 받는 대신 Intent를 사용하는 것을 고려하라.많은 경우, 개발자는 두 가지 방법을 사용할 수 있다. permission을 부여받아 스스로 동작하는 방법 동작을 수행하기 위해 다른 app에게 intent로 요청하는 방법 예를 들어, app이 카메라를 사용하여 사진을 찍는다고 했을 때, app은 카메라에 직접 접근하기 위해 카메라 permission을 요청해야 한다. 그 후 app이 카메라 API를 사용하여 카메라를 제어하고 사진을 촬영한다. 이러한 방식을 사용하면 app에 사진 촬영 과정에 대해 완전한 제어권을 부여하고, 카메라 UI를 app에 통합할 수 있다. 하지만 이러한 완벽한 제어가 필요하지 않다면 ACTION_IMAGE_CAPTURE intent를 사용하여 이미지를 요청할 수 있다. Intent를 보내면 시스템은 카메라 app을 선택하라는 메시지를 사용자에게 표시한다(기본 카메라 app이 없는 경우). 사용자는 선택한 카메라 app으로 사진을 촬영하고 app은 이 사진을 onActivityResult() 메서드로 반환한다. 이와 마찬가지로, 전화를 걸거나 사용자의 연락처에 접근해야 하는 경우 등에는 해당 intent를 생성하거나 permission을 요청하고 해당 객체에 직접 접근할 수 있다. 이 두 가지 방식에는 각각 장단점이 있다. Permission을 사용하는 경우 작업을 수행할 때 app이 사용자 환경에 대한 완벽한 제어권을 가진다. 하지만, 광범위한 제어권을 가지면 적절한 UI 를 디자인해야 하므로 작업이 복잡해진다. Permission을 부여하라는 메시지는 runtime이나 설치 시에 한 번만 사용자에게 표시된다(Android 버전에 따라 다름). 그 이후는 app이 사용자로부터 더 이상의 상호작용을 요청하지 않아도 작업을 수행할 수 있다. 핮미ㅏㄴ, 사용자가 해당 permission을 부여하지 않는 경우 (또는 나중에 취소하는 경우), app은 해당 작업을 수행할 수 없게 된다. Intent를 사용하는 경우 Intent를 처리하는 app이 UI를 제공하기 때문에 작업을 위해 UI를 디자인하지 않아도 된다. 하지만, 이는 사용자 환경을 제어할 수 없다는 뜻이기도 하다. 사용자는 어쩌면 본 적도 없는 app과 상호작용 하고 있을 수도 있다. 사용자에게 해당 작업에 대한 기본 app이 없는 경우, 시스템은 사용자에게 app을 선택하라는 메시지를 표시한다. 사용자가 기본 handler를 지정하지 않으면, 해당 작업을 수행할 때마다 추가 대화상자를 거쳐야 할 수도 있다. 필요한 permission만 요청하기Permission을 요청할 때마다 사용자 입장에서는 결정을 내리도록 강요받는 것과 같다. 때문에 이러한 요청을 하는 횟수를 최소한으로 줄이는 것이 좋다. 사용자가 Android 6.0(API level 23) 이상을 실행 중인 경우, permission이 요구되는 새로운 app 기능을 시도할 때마다 이 app의 permission 요청에 의해 사용자 작업이 중단되어야 한다. 사용자가 이전 버전의 Android를 사용 중인 경우, app 설치 시에 모든 app의 permission을 하나하나 부여해야 하는데 그 목록이 너무 길거나 부적절해 보인다면 사용자가 app을 설치하지 않을 수도 있다. 그렇기 때문에 app에 필요한 permission의 수를 최소화해야 한다. 사용자에게 부담을 주지 말 것사용자에게 많은 permission 요청을 한 번에 하는 것보다 필요할 때마다 요청하는 것이 좋다. 일부 경우에는 app에 필요한 permission이 하나 이상 있을 수도 있다. 그런 경우에는 app이 시작되자마자 해당 권한을 모두 요청할 것을 권장한다. 예를 들어, 사진 app을 만드는 경우 app은 카메라에 접근할 수 있는 permission을 필요로 한다. 사용자가 app을 처음으로 시작할 때 카메라를 사용할 permission을 요청받아도 놀라지 않는다. 하지만 동일한 app에 사용자의 연락처와 사진을 공유하는 기능도 있다면, 이 경우 해당 READ_CONTACTS permission을 첫 시작 시에 요청하는 것은 바람직하지 않다. 대신 사용자가 “공유” 기능을 사용할 때까지 기다렸다가 그 때 permission을 요청하는 것이 좋다. App이 튜토리얼을 제공하는 경우 튜토리얼이 끝날 무렵에 app의 필수 permission을 요청하는 것이 좋을 수 있다. Permission이 필요한 이유를 설명하라.개발자가 requestPermissions()를 호출할 때 시스템에 표시되는 permission 대화상자에는 app이 원하는 permission이 무엇인지는 나타나지만 그것이 필요한 이유는 설명하지 않는다. 사용자가 이러한 것을 의아하게 여기는 경우가 있기 때문에 app이 왜 그런 permission을 원하는지 설명한 후 requestPermissions()를 호출하는 것이 좋다. 예를 들어 사진 app이 위치 서비스를 이용하고자 할 수 있다. 그래야 사진에 지오태그를 표시할 수 있기 때문이다. 일반적인 사용자는 사진에 위치 정보를 담을 수 있다는 점을 모를 수도 있고 그러면 사진 app이 왜 위치를 알고싶어 하는지 궁금해할 수 있다. 그러므로 이런 경우에는 app이 사용자에게 이런 기능에 대해 미리 알려준 후 requestPermissions()를 호출하는 것이 좋다. 사용자에게 알리기 위한 방법 중 하나는 이러한 요청을 app 튜토리얼에 넣는 것이다. 튜토리얼에는 app의 각 기능을 표시할 수 있고 어느 permission이 필요한지 설명할 수도 있기 때문이다. 예를 들어 사진 app의 튜토리얼에서 “연락처 목록의 지인들과 사진 공유” 기능을 시연한 다음, app이 사용자의 연락처를 보려면 permission을 부여해야 한다고 사용자에게 알리면 된다. 그런 다음, app이 requestPermissions()를 호출하여 사용자에게 해당 접근을 요청할 수 있다. 두 가지 permission 모델을 시험하기Android 6.0(API level 23)부터는 app 설치 시가 아닌 런타임 시 사용자가 app permission을 부여하고 취소해야 한다. 결과적으로 더욱 넓은 조건 하에서 app을 테스트 해야한다. Android 6.0 이전에는 app이 실행 중인 경우 모든 permission이 app manifest에 선언된 것으로 충분히 가정할 수 있다. 새로운 permission 모델에서는 이러한 가정을 할 수 없다. 아래 팁은 API level 23 이상을 실행 중인 기기에서 permission 관련 코드 문제를 식별하는 데 도움이 된다. App의 현재 permission 및 관련 코드 경로를 식별한다. Permission 보호된 서비스 및 데이터 전반에 걸친 사용자 흐름을 테스트 한다. 부여되거나 취소된 permission의 다양한 조합을 테스트 한다. 예를 들어, 카메라 app은 CAMERA, READ_CONTACTS, ACCESS_FINE_LOCATION을 manifest에 나열할 수도 있다. App이 모든 permission 구성을 정상적으로 처리할 수 있는지 확인하려면 각 permission을 켜고 끄면서 app을 테스트해야 한다. adb 도구를 사용하여 permission을 관리한다. Permission과 상태를 group별로 나열한다.$ adb shell pm list permissions -d -g 하나 이상의 permission을 부여하거나 취소한다.$ adb shell pm [grant|revoke] <permission-name> ... 출처 https://developer.android.com/training/permissions/index.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] Getting Started (5)","slug":"2017-10-05-android-getting_started_5","date":"2017-10-05T02:00:00.000Z","updated":"2017-10-08T08:10:27.004Z","comments":true,"path":"2017/10/05/2017-10-05-android-getting_started_5/","link":"","permalink":"http://lazyrodi.github.io/2017/10/05/2017-10-05-android-getting_started_5/","excerpt":"","text":"Android developer site study Getting Started Building Your First App Supporting Different Devices uilding a Dynamic UI with Fragments Saving Data Interacting with Other Apps Working with System Permissions https://developer.android.com/training/index.html Android App은 보통 여러 개의 Activity를 가지고 있다. 사용자가 하나의 Activity에서 다른 Activity로 전환하기 위해서는 Intent를 사용하여 App의 intent가 어떠한 작업을 하도록 정의해야 한다. startActivity()와 같은 메서드로 시스템에 Intent를 전달하면 시스템은 Intent를 사용하여 적절한 App 구성 요소를 식별하고 시작한다. Intent는 크게 두 가지로 나눌 수 있다. 특정 구성 요소 (특정 Activity 인스턴스)를 시작하기 위한 명시적 Intent “사진 캡쳐”와 같은 의도된 작업을 처리할 수 있는 임의의 구성 요소를 시작하기 위한 암시적 Intent 다른 App으로 사용자 이동시키기 암시적 Intent 만들기암시적 Intent는 시작할 구성 요소의 class 이름을 선언하지 않고 그 대신 수행할 작업을 선언한다. 작업은 보기, 편집, 보내기, 가져오기와 같이 수행하고자 하는 동작을 지정한다. 또한 보기 원하는 주소 또는 전송하기 원하는 이메일 등과 같이 작업과 연결된 데이터를 함께 포함하는 경우가 많다. 생성하고자 하는 Intent에 따라 데이터는 Uri이거나 다른 여러가지 데이터 유형 중 하나일 수 있으며, intent가 데이터를 전혀 필요로 하지 않을 수도 있다. 데이터가 Uri인 경우 간단한 Intent() 생성자를 이용하여 작업 및 데이터를 정의할 수 있다. 아래 코드는 Uri 데이터르 사용하여 전화번호를 지정하여 전화 걸기를 시작하는 Intent를 생성한다. Uri number = Uri.parse(\"tel:5551234\");Intent callIntent = new Intent(Intent.ACTION_DIAL, number); App이 startActivity()를 호출하여 이 intent를 호출하면 전화 app이 주어진 전화번호로 전화를 건다. 아래는 몇 가지 다른 intent와 해당 작업 및 Uri 데이터 쌍이다. 지도 보기 // 주소 기반의 지도 지점Uri location = Uri.parse(\"geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California\");// 또는 위도/경도 기반의 지도 지점// Uri location = Uri.parse(\"geo:37.422219,-122.08364?z=14\"); // z param is zoom levelIntent mapIntent = new Intent(Intent.ACTION_VIEW, location); 웹 페이지 보기 Uri webpage = Uri.parse(\"http://www.android.com\");Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage); 다른 종류의 암시적인 intent는 문자열과 같이 여러 데이터 유형을 제공하는 “Extra” 데이터를 필요로 한다. 다양한 putExtra() 메서드를 사용하여 하나 이상의 Extra 데이터를 추가할 수 있다. 기본적으로 시스템은 포함된 Uri 데이터를 바탕으로 intent가 필요로 하느 ㄴ적절한 MIME 유형을 결정한다. Uri를 intent에 포함하지 않을 경우, 일반적으로 setType()을 사용하여 intent와 관련된 데이터의 유형을 지정해야 한다. MIME 유형을 설정하면 intent를 수신할 activity의 종류도 지정된다. 첨부 파일과 함께 이메일 보내기 Intent emailIntet = new Intent(Intent.ACTION_SEND);// 이 Intent는 URI를 가지고 있지 않다. 때문에 \"text/plain\" MIME type을 선언한다.emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {\"[email protected]\"}); // 수신자emailIntent.putExtra(Intent.EXTRA_SUBJECT, \"Email subject\");emailIntent.putExtra(Intent.EXTRA_TEXT, \"Email message text\");emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(content://path/to/email/attachment\"));// Uri들의 arrayList를 여러 개 보내는 것도 가능하다. 캘린더 이벤트 생성 캘린더 이벤트에 대한 아래 intent는 API Level 14 이상에서만 지원한다. Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);Calendar beginTime = Calendar.getInstance().set(2017, 0, 19, 7, 30);Calendar endTime = Calendar.getInstance()set(2017, 0, 19, 10, 30);calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());calendarIntent.putExtra(Events.TITLE, \"Ninja class\");calendarIntent.putExtra(Events.EVENT_LOCATION, \"Secret dojo\"); Intent는 되도록 구체적으로 정의해야 한다. 예를 들어, ACTION_VIEW intent를 사용하여 이미지를 표시하고자 할 경우, image/*의 MIME 유형을 지정해야 한다. 그렇게 하면 지도 app과 같은 다른 유형의 데이터를 볼 수 있는 app이 intent에 의해 실행되는 것이 방지된다. Intent를 수신할 app이 있는지 확인하기Intent를 호출한 후 해당 intent를 처리할 수 있는 app이 기기에 없을 경우 app은 작동을 중단하기 때문에 intent를 호출하기 전에 항상 확인 단계를 포함하는 것이 좋다. 이를 위해 queryIntentActivities()를 호출하여 Intent를 처리할 수 있는 activity 목록을 가져와야 한다. 얻어온 List가 비어있지 않을 경우 intent를 안심하고 사용할 수 있다. PackageManager packageManager = getPackageManager();List activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);boolean isIntentSafe = activities.size() > 0; 위의 코드에서 isIntentSafe가 true일 경우 하나 이상의 intent가 응답하고 있는 것이므로 안심하고 intent를 호출한다. 사용자가 intent를 사용하려고 하기 전에 이 intent를 사용하는 기능을 해제해야 하는 경우, activity가 처음 시작될 때 이 확인 작업을 수행해야 한다. Intent를 처리할 수 있는 app을 알고 있을 경우, 사용자가 app을 다운로드 할 수 있도록 link를 제공할 수도 있다. Intent를 사용하여 Activity 시작하기Intent를 생성하고 EXTRA 정보를 설정한 후에는 startActivity()를 호출하여 시스템에 보내야 한다. 시스템이 Intent를 처리할 수 있는 activity를 둘 이상 식별하는 경우, 아래 그림과 같이 사용자가 사용할 app을 선택할 수 있는 대화상자를 표시한다. Intent를 처리할 수 있는 activity가 하나밖에 없을 경우, 시스템이 해당 activity를 바로 시작한다. startActivity(intent); 아래 코드는 지도 보기 intent를 생성하고 intent를 처리할 수 있는 app의 존재 여부를 확인한 후 app을 시작하는 방법에 대한 완벽한 예시이다. // intent 생성하기Uri location = Uri.parse(\"geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California\");Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);// intent 호출 가능여부 확인PackageManager packageManager = getPackageManager();List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);boolean isIntentSafe = activities.size() > 0;// 안전하다면, activity 시작if (isIntentSafe) { startActivity(mapIntent);} App 선택기 표시하기Intent에 응답하는 app이 두 개 이상인 경우 사용자는 어떤 app을 default로 사용할지 선택할 수 있다. 이 방법은 사용자가 일반적으로 항상 동일한 app을 사용하기 원하는 작업을 수행할 때 유용하게 사용된다. 예를 들어, 웹페이지를 열 때 브라우저를 선택하거나 사진을 찍을 때 카메라의 종류를 선택할 때 사용된다. 하지만 “공유” 작업 등과 같이 항목을 공유할 app을 여러 개 보유하고 있는 경우처럼 수행할 작업을 처리할 수 있는 app이 여러 개 있고 사용자가 매번 다른 app을 원한다면, 아래 그림과 같이 선택기 대화상자를 명시적으로 표시할 수 있다. 선택기를 명시적으로 표시하려면, createChooser()를 사용하여 Intent를 만들고 startActivity()에 전달한다. 아래의 예시는 createChooser() 메서드에 전달된 intent에 응답하는 app들의 목록을 대화상자에 표시하며, 제공된 text를 대화상자 제목으로 사용한다. Intent intent = new Intent(Intent.ACTION_SEND);...// UI text는 항상 string 리소스를 사용한다.// \"Share this photo with\" 등...String title = getResource().getString(R.string.chooser_title);// 선택기에서 보여줄 intent 생성하기Intent chooser = Intent.createChooser(intent, title);// intent를 수신할 수 있는 activity 존재 확인if (intent.resolveActivity(getaPackageManager()) != null) { startActivity(chooser);} Activity로부터 결과 가져오기다른 Activity를 시작시킨 후 그 activity로부터 결과를 수신할 수도 있다. 결과를 수신하고 싶을 때는 startActivity() 대신 startActivityForResult()를 호출한다. 예를 들어, 자신의 App에서 카메라 app을 실행시킨 후 그 결과로 캡쳐된 사진을 수신할 수 있다. 연락처의 선택 후 그 상세 정보를 수신하는 등의 동작에도 사용 가능하다. 응답하는 activity는 반드시 결과를 반환하도록 설계되어 있어야 하며 해당 activity는 또 다른 Intent 객체의 형태로 결과를 전송한다. 그러면 최초 호출한 activity는 onActivityResult() 콜백으로 결과를 수신한다. startActivityForResult()를 호출할 때 명시적 또는 암시적 intent를 사용할 수 있다. 자신이 정의한 activity 중 하나를 시작하여 결과를 수신하는 경우 예상하는 결과를 수신하도록 보장하려면 명시적인 intent를 사용해야 한다. Activity의 시작결과를 수신하기 위해서 startActivityForResult() 메서드에 추가적인 정수 인수를 전달해야 한다. 이 정수 인수는 요청을 식별하는 요청 코드이다. 결과 intent를 수신하는 경우 app이 결과를 올바르게 식별하여 이를 처리할 방법을 결정할 수 있도록 콜백이 이와 똑같은 요청 코드를 제공한다. 아래는 사용자가 연락처를 선택할 수 있게 하는 activity를 시작하는 방법이다. static final int PICK_CONTACT_REQUEST = 1; // 요청 코드...private void pickContact() { Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse(\"content://contacts\")); pickContactIntent.setType(Phone.CONTENT_TYPE); // 주소록에 있는 사용자와 폰 번호를 보여줌 startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);} 결과 수신하기사용자가 후속 activity 작업을 마치고 돌아오면 시스템은 개발자가 정의한 원래 activity의 onActivityResult() 메서드를 호출한다. 이 메서드는 아래의 세 가지 인수를 포함한다. startActivityForResult()에 전달한 요청 코드 두 번째 activity가 지정한 결과 코드. 이 코드는 작업이 성공한 경우 RESULT_OK이고 사용자가 작업을 취소하였거나 어떠한 이유로 실패한 경우 `RESULT_CANCELED’가 된다. 결과 데이터를 전달하는 Intent 아래는 “연락처 선택하기” intent에 대한 결과를 처리하는 방법이다. @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { // 요청했던 내용에 대한 응답인지 확인 if (requestCode == PICK_CONTACT_REQUEST) { // 요청이 성공했는지 확인 if (resultCode == RESULT_OK) { // 사용자가 주소록을 선택함. // Intent는 사용자가 선택한 주소록의 Uri 값을 가지고 있음 // 주소록을 가지고 여기서 필요한 작업을 수행함. (다음 코드 예제를 참조) } }} 위의 예시에서는 Android 주소록에서 반환되는 결과 intent가 사용자가 선택한 연락처를 식별하는 Uri 콘텐츠를 제공한다. 결과를 성공적으로 처리하기 위해서는 결과 intent의 형식이 무엇인지 이해하고 있어야 한다. 결과를 반환하는 activity가 자신이 정의한 activity 중 하나인 경우에는 그 결과를 이해하기가 쉽다. Android 플랫폼에 포함된 app은 특정한 결과 데이터를 기대할 수 있는 고유한 API를 제공한다. 예를 들어, PEOPLE app은 선택된 연락처를 식별하는 콘텐츠 URI와 함께 항상 결과를 반환한다. 또한 카메라 app은 “data” EXTRA에 Bitmap을 반환한다. 보너스: 연락처 데이터 읽기아래 코드는 데이터를 쿼리하여 선택된 연락처에서 전화번호를 가져오는 방법을 나타낸다. 123456789101112131415161718192021222324252627@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { // 요청했던 내용에 대한 응답인지 확인 if (requestCode == PICK_CONTACT_REQUEST) { // 요청이 성공했는지 확인 if (resultCode == RESULT_OK) { // 사용자가 주소록을 선택함. Uri contactUri = data.getData(); // NUMBER column만을 필요로 한다고 가정한다. 왜냐하면 결과로 하나의 row만 얻었을 것이기 때문이다. String[] projection = {Phone.NUMBER}; // NUMBER column을 주소록으로부터 얻어오기 위한 쿼리를 실행한다. // 결과 값이 URI로 넘어오기 때문에 별도의 정렬을 할 필요는 없다. // 주의: query() 메서드는 blocking을 피하기 위해 UI thread와 다른 별도의 thread에서 수행되어야 한다. // 이 코드에서는 간단한 수행만 할 것이라 굳이 그렇게 코드를 짜지는 않았다. // query를 수행 할 때 CursorLoader를 사용하는 것을 고려하라. Cursor cursor = getContentResolver() .query(contactUri, projection, null, null, null); cursor.moveToFirst(); // NUMBER column으로부터 폰 번호를 검색한다. int column = cursor.getColumnIndex(Phone.NUMBER); String number = cursor.getString(column); } }} Android 2.3 (API level 9) 이전에서는 위의 코드와 같이 Contacts Provider에 대해 쿼리를 수행하려면 App에서 READ_CONTACTS 권한을 선언해야 한다. 하지만 Android 2.3부터는 Contact Provider가 결과를 반환할 때 자신의 app에서 그 결과를 읽어올 수 있도록 주소록 app이 임시 권한을 부여한다. 임시 권한은 해당 연락처 요청에만 적용되기 때문에 intent의 Uri로 지정된 연락처 외에는 쿼리할 수 없다. 다만, READ_CONTACTS 권한을 명시적으로 선언한 경우는 된다. 다른 App이 자신의 Activity를 시작할 수 있도록 허용하기자신의 app이 다른 app에 유용할 수 있는 작업을 수행할 수 있다면 다른 app의 작업 요청에 응답할 수 있도록 준비해 두어야 한다. 예를 들어, 사용자가 친구와 메시지 또는 사진을 공유할 수 있는 소셜 app을 만드는 경우 사용자가 다른 app에서 “공유” 작업을 시작하고 이 작업을 수행하기 위해 내가 만든 app을 시작할 수 있도록 ACTION_SEND intent를 지원하는 것이 좋다. 다른 app이 자신의 activity를 시작할 수 있도록 하기 위해서는 manifest 파일에서 해당 <activity> 요소에 대해 <intent-filter> 요소를 추가해야 한다. App이 기기에 설치되면 시스템이 intent filter를 식별한 후 설치된 모든 app에서 지원되는 intent의 내부 카탈로그에 해당 정보를 추가한다. App이 암시적 intent를 사용하여 startActivity() 또는 startActivityForResult()를 호출하면 시스템이 intent에 응답 가능한 activity(들)을 찾는다. Intent filter 추가하기Activity가 처리 가능한 intent를 올바르게 정의하려면 activity가 받아들이는 데이터와 작업 유형 측면에서 추가하는 intent filter가 최대한 구체적이어야 한다. Activity의 Intent filter가 Intent 객체의 아래 기준을 충족할 경우 시스템이 주어진 Intent를 해당 activity에 보낼 수 있다. Action Data Category Action 수행할 작업의 이름을 지정하는 문자열이다. 일반적으로, 플랫폼에서 정의하는 값 중 하나이다. (예. ACTION_SEND 또는 ACTION_VIEW) <action> 요소를 사용하여 Intent filter에 지정해야 한다. 이 요소에 지정하는 값은 API 상수 대신 작업의 전체 문자열 이름이어야 한다. Data Intent와 관련된 Data에 대한 설명이다. <data> 요소를 사용하여 Intent filter에 지정한다. 이 요소에서 하나 이상의 특성을 사용하여 MIME 유형, URI 접두사, URI 구성표 또는 이들의 조합 그리고 수락된 데이터 유형을 나타내는 다른 요소들을 지정할 수 있다. Activity가 URI가 아닌 다른 종류의 EXTRA 데이터를 처리할 때와 같이 데이터 Uri에 대한 세부사항을 선언할 필요가 없는 경우, text/plain 또는 image/jpeg과 같이 activity가 처리하는 데이터 유형을 선언하는데 android.mimeType 특성만 지정하면 된다. Category Intent를 처리하는 activity의 특징을 지정할 수 있는 추가적인 방법을 제공한다. 일반적으로 사용자 제스쳐 또는 이러한 제스쳐가 시작된 위치와 관련이 있다. 시스템이 지원하는 category는 여러가지가 있지만 대부분은 거의 사용되지 않는다. 하지만 모든 암시적 intent는 기본적으로 CATEGORY_DEFAULT로 지정된다. <category> 요소를 사용하여 Intent filter에 지정한다. Intent filter에서 activity가 허용하는 기준을 선언할 수 있다. 이는 이러한 기준 각각을 <intent-filter> 요소 내에 해당 XML 요소를 중첩하여 선언하면 된다. 아래 코드는 데이터 유형이 텍스트 또는 이미지인 경우 ACTION_SEND intent를 처리하는 intent filter가 지정된 activity이다. <activity android:name=\"ShareActivity\"> <intent-filter> <action android:name=\"android.intent.action.SEND\" /> <category android:name=\"android.intent.category.DEFAULT\" /> <data android:mimeType=\"text/plane\" /> <data android:mimeType=\"image/*\" /> </intent-filter></activity> 수신되는 intent는 각각 하나의 action 및 하나의 데이터 유형만 지정한다. 하지만 <intent-filter> 각각에 <action>, <category>, <data> 요소에 대한 여러 인스턴스를 선언해도 문제가 되지는 않는다. action 및 data의 두 쌍이 서로 배타적으로 동작하는 경우 어떤 데이터 유형과 페어링되었을 때 어떤 작업이 가능한지를 지정하는 intent filter를 각각 따로 생성해야 한다. 예를 들어, activity가 ACTION_SEND 및 ACTION_SENDTO intent 모두에서 텍스트와 이미지를 모두 처리한다고 가정할 경우, 두 작업 각각에 별도의 intent filter를 정의해야 한다. 그 이유는 ACTION_SENDTO intent는 데이터 Uri를 사용해여 send 또는 sendto URI 구성표를 사용하는 수신자 주소를 지정해야 하기 때문이다. <activity android:name=\"ShareActivity\"> <!-- filter for sending text; accepts SENDTO action with sms URI schemes --> <intent-filter> <action android:name=\"android.intent.action.SENDTO\"/> <category android:name=\"android.intent.category.DEFAULT\"/> <data android:scheme=\"sms\" /> <data android:scheme=\"smsto\" /> </intent-filter> <!-- filter for sending text or images; accepts SEND action and text or image data --> <intent-filter> <action android:name=\"android.intent.action.SEND\"/> <category android:name=\"android.intent.category.DEFAULT\"/> <data android:mimeType=\"image/*\"/> <data android:mimeType=\"text/plain\"/> </intent-filter></activity> 암시적 intent를 수신하기 위해서는 CATEGORY_DEFAULT category를 intent filter에 포함해야 한다. startActivity() 및 startActivityForResult() 메서드는 모든 intent를 마치 CATEGORY_DEFAULT를 선언한 것처럼 취급한다. Intent filter에서 이 category를 선언하지 않으면 어떠한 암시적 intent오 activity로 확인할 수 없다. Activity 에서 intent 처리하기Activity를 시작하는데 사용된 intent를 읽어서 Activity에서 할 작업을 결정할 수 있다. Activity가 시작되면, getIntent()를 호출하여 activity를 시작한 intent를 검색한다. 이 작업은 activity의 수명 주기 동안 언제든지 가능하지만, 일반적으로 onCreate() 또는 onStart()와 같은 초기 콜백 과정에서 수행한다. 1234567891011121314151617@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 이 activity를 구동시킨 intent를 얻어온다. Intent intent = getIntent(); Uri data = intent.getData(); // intent type에 기반하여 무엇을 할 것인지 정한다. if (intent.getType().indexOf(\"image/\") != -1) { // 이미지 데이터를 다룬다. } else if (intent.getType().equals(\"text/palain\")) { // 텍스트 데이터를 다룬다. }} 결과 반환하기Activity를 호출한 activity로 결과를 반환하고자 하는 경우, 간단하게 setResult()를 호출하여 결과 코드 및 결과 intent를 지정하면 된다. 작업이 끝나고 사용자가 원래 activity로 되돌아갈 경우 finish()를 호출하여 activity를 종료(및 소멸)한다. 1234// 결과 데이터를 전달할 intent를 생성한다.Intent result = new Intent(\"com.example.RESULT_ACTION\", Uri.parse(\"content://result_uri\"));setResult(Activity.RESULT_OK, result);finish(); 결과 코드는 항상 결과와 함께 지정해야 한다. 일반적으로 RESULT_OK 또는 RESULT_CANCELED 이다. Intent를 사용하여 추가 데이터를 제공할 수 있다. 결과는 기본적으로 RESULT_CANCELED로 설정된다. 따라서 작업을 완료하기 전 또는 개발자가 결과를 설정하기 전에 사용자가 Back 버튼을 누를 경우, 원래의 activity는 취소 결과를 받게된다. 단순히 여러 결과 옵션 중 하나를 나타내는 정수만 반환하면 되는 경우, 결과 코드를 0보다 큰 임의의 값으로 설정하면 된다. 결과 코드를 사용하여 정수만 제공하고 Intent를 포함할 필요는 없는 경우, setResult()를 호출하고 결과 코드만 전달하면 된다. setResult(RESULT_COLOR_RED);finish(); 이 경우, 가능한 결과는 몇 개 없을 것이므로 결과 코드는 local로 정의된 0보다 큰 정수이다. startActivity() 또는 startActivityForResult()로 activity가 시작되었는지 확인할 필요는 없다. activity를 시작한 intent가 결과를 원할 경우, setResult()를 호출하기만 하면 된다. 원래의 activity가 startActivityRForResult()를 호출한 경우, 시스템은 개발자가 setResult()에 제공하는 결과를 activity에 제공한다. 그렇지 않은 경우 결과는 무시된다. 출처 https://developer.android.com/training/basics/data-storage/index.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] Getting Started (4)","slug":"2017-10-04-android-getting_started_4","date":"2017-10-04T02:00:00.000Z","updated":"2017-10-07T14:59:52.735Z","comments":true,"path":"2017/10/04/2017-10-04-android-getting_started_4/","link":"","permalink":"http://lazyrodi.github.io/2017/10/04/2017-10-04-android-getting_started_4/","excerpt":"","text":"Android developer site study Getting Started Building Your First App Supporting Different Devices uilding a Dynamic UI with Fragments Saving Data Interacting with Other Apps Working with System Permissions https://developer.android.com/training/index.html Android는 데이터를 저장하기 위해 주로 아래 세 가지의 방법을 사용한다. Shared Preference에 Key-Value 쌍 저장 File system에 file 저장 SQLite Database에 저장 Shared Preference의 사용저장하고자 하는 데이터의 수가 적은 경우 SharedPreferences API를 사용한다. SharedPreferences 객체는 Key-Value 쌍을 포함하는 파일을 가리키며 이를 읽고 쓸 수 있는 간단한 메서드를 제공한다. SharedPreferences API는 Key-Value 쌍을 읽고 쓰는 용도로만 사용된다.App 설정을 위한 사용자 interface 빌드를 지원하는 Preference API와는 다르다. SharedPreferences에 대한 Handle 가져오기아래 메서드 중 하나를 호출하여 새로운 SharedPreference 파일을 생성하거나 기존 파일에 접근할 수 있다. getSharedPreferences() 여러 개의 SharedPreference 파일이 필요한 경우 사용한다. 이 파일은 첫 번째 매개변수로 지정하는 일므으로 식별된다. App 내부의 모든 Context에서 이 메서드를 호출할 수 있다. getPreferences() Activity에 하나의 SharedPreference 파일만 사용해야 하는 경우 Activity 에서 이 메서드를 사용한다. 이 메서드는 Activity에 속한 기본 SharedPreference 파일을 가져오기 때문에 이름을 제공할 필요가 없다. 아래 코드는 Fragment 내부에서 실행된다. 이 코드는 리소스 문자열 R.string.preference_file_key에 의해 식별되는 SharedPreference 파일에 액세스하며, App 자신만 파일에 액세스할 수 있도록 전용 모드에서 파일을 연다. 12Context context = getActivity();SharedPreferences sharedPref = context.getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE); SharedPreference 파일의 이름을 지정할 때에는 “com.example.myapp.PREFERENCE_FILE_KEY”와 같이 App에 대한 고유 식별 이름을 사용해야 한다. Activity에 대한 SharedPreference만 필요한 경우 getPreferences() 메서드를 사용하면 된다. 1SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); MODE_WORLD_READABLE 또는 MODE_WORLD_WRITEABLE을 사용하여 SharedPreference 파일을 생성하는 경우 파일 식별자를 인식하는 모든 App이 데이터에 접근할 수 있으므로 주의해야 한다. SharedPreferences에 쓰기 SharedPreferences에서 edit()를 호출하여 SharedPreferences.Editor를 생성한다. putInt(), putString() 등의 메서드를 사용하여 Key-Value를 전달한다. commit()을 호출하여 변경사항을 저장한다. 1234SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);SharedPreferences.Editor editor = sharedPref.edit();editor.putInt(getString(R.string.saved_high_score), newHighScore);editor.commit(); SharedPreferences에서 읽어오기검색을 위해서는 원하는 Key를 제공해야 하며, Key 에 대한 Value가 저장되어 있지 않을 경우 반환할 기본 값도 넣어준 후 getInt(), getString()등의 메서드를 호출해야 한다. 123SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);int defaultValue = getResources().getInteger(R.string.saved_high_score_default);long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue); 파일에 저장File API를 사용하여 파일을 읽고 쓸 수 있다. File 객체는 대량의 데이터를 처음부터 끝까지 순서대로 읽거나 쓸 때 적합하다. 이미지 파일이나 네트워크에서 교환되는 모든 항목에 적합하다. 내부/외부 저장소 선택Android 기기에는 “내부 저장소”와 “외부 저장소(sd card 등)”가 나뉘어져 있다. 내부 저장소내부 저장소는 다른 App이 자신의 파일에 접근하는 것을 원치 않을 때 적합하다. 항상 사용 가능하다. 내부 저장소에 저장된 파일은 자신의 App에서만 접근할 수 있다. 사용자가 App을 삭제하면 시스템이 내장 저장소에서 App의 모든 파일을 제거한다. 외부 저장소외부 저장소는 접근 제한이 필요하지 않은 파일과 다른 App과 공유하기 원하는 파일 또는 사용자가 컴퓨터에서 접근할 수 있도록 허용하는 파일에 적합하다. 항상 사용 가능하지는 않다. 왜냐하면 사용자가 USB 저장소 등 외부 저장소를 마운트할 수도 있고 긱에서 외부 저장소를 제거할 수 있기 때문이다. 모든 사람이 읽을 수 있다. 사용자가 App을 삭제하면 getExternalFilesDir()의 디렉토리에 저장한 App 파일에 한해서 시스템이 제거한다. 기본적으로 App은 내부 저장소에 설치되지만 Manifest에 android:installLocation 특성을 지정하여 외부 저장소에 설치할 수도 있다.사용자는 APK 크기가 매우 크고 내부 저장소 공간보다 외부 저장소 공간이 더 클 때 이 옵션을 유용하게 사용할 수 있다. 외부 저장소에 대한 권한 취득외부 저장소에 데이터를 읽고 쓰기 위해서는 Manifest 파일에 READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE권한을 요청해야 한다. WRITE_EXTERNAL_STORAGE가 선언된 경우 READ_EXTERNAL_STORAGE권한이 없어도 묵시적으로 READ 권한도 포함되어 있다. <manifest ...> <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" /> <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" /> ...</manifest> 내부 저장소에 파일 저장하기내부 저장소에 파일을 저장할 경우 아래 두 메서드 중 하나를 사용하여 디렉토리를 File로 얻을 수 있게 된다. getFileDir() App에 대한 내부 디렉토리를 나타내는 File을 반환한다. getCacheDir() App의 임시 캐시 파일에 대한 내부 디렉토리를 나타내는 File을 반환한다. 더 이상 필요하지 않은 파일은 모두 삭제하고 언제든지 사용할 수 있는 메모리 크기에 합리적인 크기로(ex. 1MB) 제한하여 구현해야 한다. 저장 공간이 부족해지기 시작하면 경고 없이 시스템이 캐시 파일을 삭제할 수도 있다. 디렉토리 중 하나에서 새 파일을 생성하기 위해 File() 생성자를 사용하고 위의 메서드 중 하나를 통해 제공되는 File을 전달하면 된다. File file = new File(context.getFilesDir(), filename); openFileOutput()을 호출하여 내부 디렉토리의 파일에 데이터를 쓰는 FileOutputStream을 가져올 수 있다. 1234567891011String filename = \"myfile\";String string = \"Hello world\";FileOutputStream outputStream;try { outputStream = openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(string.getBytes()); outputStream.close();} catch (Exception e) { e.printStackTrace();} 파일을 캐싱해야 할 경우 createTempFile()을 사용한다. 아래 코드는 URL로부터 파일 이름을 추출한 뒤 해당 이름을 사용하여 App의 내부 캐시 디렉토리에 파일을 생성한다. 123456789public File getTempFile(Context context, String url) { File file; try { String fileName = Uri.parse(url).getLastPathSegment(); file = File.createTempFile(filenName, null, context.getCacheDir()); } catch (IOException e) { // Error while creating file }} App의 내부 저장소 디렉토리는 Android v아리 시스템의 특별한 위치에 있는 App의 Package 이름으로 지정된다.파일 모드를 읽기 가능으로 설정할 경우 다른 App이 내부 파일을 읽을 수 있다. 하지만 이는 다른 App도 나의 App package 이름 및 파일 이름을 알아야 가능하다.달느 App은 나의 App 내부 디렉토리를 탐색할 수 없으며, 명식적으로 읽기/쓰기 가능으로 파일을 설정하지 않으면 파일에 접근할 수 없다.따라서, MODE_PRIVATE을 내부 저장소 내 파일에 사용한다면 다른 App은 접근할 수 없게 된다. 외부 저장소에 파일 저장사용자가 외부 저장소를 PC에 마운트 했거나 SD Card를 제거한 경우 외부 저장소를 사용할 수 없기 때문에 접근하기 전 항상 사용 가능한지 확인해야 한다. 이 때 getExternalStorageState()를 사용하여 외부 저장소 상태를 확인할 수 있다. Return된 상태가 MEDIA_MOUNTED라면 사용이 가능한 것이다. 123456789101112131415161718/* Checks if external storage is available for read and write */public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false;}/* Checks if external storage is available to at least read */public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false;} 외부 저장소에 저장할 수 있는 파일의 종류는 크게 두 가지가 있다. 공용 파일 다른 App 및 사용자가 자유롭게 사용 가능한 파일들. 사용자가 App을 제거해도 파일은 유지되어야 함. (ex. 캡쳐된 사진, 다운로드된 파일) 전용 파일 App이 삭제될 때 같이 삭제되어야 하는 파일들. 사용자 및 다른 App의 접근이 가능하지만 App 외부에서의 가치는 없는 파일들. 사용자가 App을 제거하면 전용 파일들도 모두 삭제되어야 함. (ex. 추가 리소스 또는 임시 미디어 파일) 외부 저장소에 공용 파일을 저장하려는 경우 getExternalStoragePublicDirectory() 메서드를 사용하여 외부 저장소에 적절한 디렉토리를 나타내는 File을 가져온다. 이 메서드는 DIRECTORY_MUSIC 또는 DIRECTORY_PICTURES와 같은 다른 공개 파일과 논리적으로 구성될 수 있도록 저장하고자 하는 파일의 유형을 지정하는 매개변수를 받는다. 123456789public File getAlbumStorageDir(String albumName) { // 사용자의 공용 사진 디렉토리를 저장할 디렉토리를 가져온다. File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, \"Directory not created\"); } return file;} 개인 파일을 App에 저장하고자 하는 경우, getExternalFilesDir()을 호출하고 원하는 디렉토리 유형을 나타내는 이름을 전달하여 적절한 디렉토리를 얻을 수 있다. 이런 식으로 생성된 각 디렉토리는 부모 디렉토리에 추가된다. 이 디렉토리는 사용자가 App을 제거할 때 시스템이 삭제하는 App의 모든 외부 저장소 파일을 캡슐화한다. 123456789public File getAlbumStorageDir(Context context, String albumName) { // App의 개인 사진 디렉토리를 생성할 디렉토리를 가져온다. File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, \"Directory not created\"); } return file;} 미리 정의된 하위 디렉토리 이름 중 파일에 알맞은 이름이 없을 경우 대신 getExternalFilesDir()을 호출하고 null을 전달할 수 있다. 그렇게 되면 App의 외부 저장소 내의 전용 디렉토리의 root 디렉토리를 가져올 수 있다. 사용자가 App을 제거할 때 getExternalFilesDir()이 삭제된 디렉토리 내에 디렉토리를 생성한다는 것을 꼭 기억해야 한다. App이 삭제되어도 사진 등이 남아야 한다면 사용자가 App을 제거한 후에도 저장한 파일을 사용 간으하게 유지하기 위해 getExternalStoragePublicDirectory()를 사용해야 한다. 공유 파일에 getExternalStoragePublicDirectory()를 사용하든 개인 파일에 getExternalFilesDir()을 사용하든지 관계 없이 DIRECTORY_PICTURES와 같이 API 상수로 제공되는 디렉토리 이름을 사용해야 한다. 제공되는 디렉토리 이름을 사용함으로써 시스템이 파일을 적절하게 처리할 수 있게 된다. 예를 들어, DIRECTORY_RINTONES에 저장된 파일은 시스템 미디어 스캐너에 의해 음악 대신 벨소리로 자동 분류된다. 여유 공간 쿼리하기저장하는 데이터의 크기를 미리 알고 있을 경우, getFreeSpace() 또는 getTotalSpace()를 호출하여 사용 공간이 충분한지 확인할 수 있다. 이를 확인하지 않고 저장을 시도할 경우 저장 공간이 충분하지 않다면 IOException이 발생할 수 있다. 이런 정보를 통해 저장소가 임계치를 초과하는지 감시할 때도 사용할 수 있다. 하지만, 시스템은 getFreeSpace()로 지정된만큼 용량이 사용 가능하다는 것을 보장하지는 않는다. 저장하고자 하는 데이터보다 남은 용량이 수 MB 크거나 저장 공간이 90% 이상일 때에만 시스템은 안정성을 제공한다. 파일을 저장하기 전에 반드시 사용 가능한 공간을 체크할 필요는 없다. 하지만 IOException이 발생하는 경우 이를 catch하면 안 된다. 파일 삭제하기더 이상 필요하지 않은 파일은 항상 삭제하는 습관을 들이자. 파일을 삭제하는 가장 간단한 방법은 열린 파일 참조가 delete()를 직접 호출하도록 하는 것이다. myFile.delete(); 파일이 내부 저장소에 저장되어 있는 경우, Context에 위치를 요청하고 deleteFile()을 호출하여 파일을 삭제할 수도 잇다. myContext.deleteFile(fileName); 사용자가 App을 제거하면 Android는 아래 사항들을 삭제한다. 내부 저장소에 저장한 모든 파일 getExternalFilesDir()을 사용해 외부 저장소에 저장한 모든 파일 하지만 getCacheDir()로 생성된 모든 캐시 파일과 더 이상 필요치 않은 다른 파일은 정기적으로 직접 삭제해야 한다. SQL Database에 Data 저장하기연락처 정보 등 반복적이거나 구조적인 데이터의 경우 Database에 저장하는 것이 이상적이다. Android에서 Database 사용에 필요한 API는 android.database.sqlite 패키지에 있다. Schema 및 Contract의 정의Schema는 Database 기본 원칙 중 하나로 Database 구성 체계에 대한 공식적인 선언이다. Contract라고 하는 helper class를 생성하면 도움이 될 수 있다. Contact class는 체계적이고 자기 문서화 방식으로 Schema의 레이아웃을 명시적으로 지정한다. Contract class는 URI, Table 및 Column의 이름을 정의하는 상수를 유지하는 컨테이너이다. Contract class를 사용하면 동일한 package 내 모든 class에 동일한 상수를 사용할 수 있게 된다. 즉, 어느 한 장소에서 Column 이름을 변경하면 코드 전체에 변경 사항이 반영된다. Contract class를 구성하는 좋은 방법은 class의 root 레벨에 전체 Database에 전역적인 정의를 추가하는 것이다. 그 후 Column을 열거하는 각 테이블에 대해 내부 class를 생성한다. BaseColumns 인터페이스를 구현함으로써, 내부 class는 _ID라고 하는 기본 key field를 상속할 수 있다. Cursor adapter 같은 일부 Android class의 경우 내부 class가 이러한 기본 key field를 가지고 있을 것이라 예상하고 동작한다. 내부 class는 반드시 필요한 것은 아니지만, Database가 Android framework와 조화롭게 작업하는데 도움이 될 수 있다. 예를 들어 아래 sniffet은 테이블 이름과 단일 테이블의 column 이름을 정의한다. 123456789101112public final class FeedReaderContract { // To prevent someone from accidentally instantiating the contract class, // make the constructor private. private FeedReaderContract() {} /* Inner class that defines the table contents */ public static class FeedEntry implements BaseColumns { public static final String TABLE_NAME = \"entry\"; public static final String COLUMN_NAME_TITLE = \"title\"; public static final String COLUMN_NAME_SUBTITLE = \"subtitle\"; }} SQL Helper를 이용한 Database 생성하기Database의 모양을 정의한 후에는 Database 및 테이블을 생성 및 유지하는 메서드를 구현해야 한다. 아래는 테이블을 생성하고 삭제하는 몇 가지 일반적인 명령문이다. 12345678910private static final String TEXT_TYPE = \" TEXT\";private static final String COMMA_SEP = \",\";private static final String SQL_CREATE_ENTRIES = \"CREATE TABLE \" + FeedEntry.TABLE_NAME + \" (\" + FeedEntry._ID + \" INTEGER PRIMARY KEY,\" + FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_SUBTITLE + TEXT_TYPE + \" )\";private static final String SQL_DELETE_ENTRIES = \"DROP TABLE IF EXISTS \" + FeedEntry.TABLE_NAME; 기기의 내부 저장소에 저장하는 파일처럼 Android는 Database를 App과 관련된 전용 디스크 공간에 저장한다. 기본적으로 이 공간은 다른 App이 접근할 수 없기 때문에 저장된 데이터는 안전하게 유지된다. 유용한 API 집합이 SQLiteOpenHelper class에서 제공된다. Database에 대한 참조를 가져오기 위해 이 class를 사용하는 경우 시스템은 필요한 경우에 한해서면 Database 생성 및 업데이트와 같이 장시간 실행될 수 있는 작업을 수행하며, App이 시작되는 동안에는 이러한 작업을 수행하지 않는다. getWritableDatabase() 또는 getReadableDatabase()를 호출하기만 하면 된다. 이러한 작업은 장시간 실행될 수도 있기 때문에 AsyncTask 또는 IntentService와 같이 백그라운드 스레드에서 getWritableDatabase() 또는 getReadableDatabase()를 호출해야 한다. SQLiteOpenHelper를 사용하려면 onCreate(), onUpgrade(), onOpen() 콜백 메서드를 재정의하는 서브클래스를 생성해야 한다. 꼭 필요한 것은 아니지만 onDowngrade()를 구현해야 하는 경우도 있다. 아래는 SQLiteOpenHelper의 예시이다. 1234567891011121314151617181920212223public class FeedReaderDbHelper extends SQLiteOpenHelper { // Database schema를 변경하고 싶다면, database version을 올리면 된다. public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = \"FeedReader.db\"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 이 database는 online data의 캐시만을 담당한다. 따라서, upgrade 정책은 간단하게 data를 삭제하고 다시 시작하는 것 뿐이다. db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); }} Database에 접근하려면 SQLiteOpenHelper의 서브클래스를 인스턴스화한다. FeedReaderHelper mDbHelper = new FeedReaderDbHelper(getContext()); Database에 정보 삽입하기ContentValues 객체를 insert() 메서드에 전달하여 data를 database에 삽입한다. 12345678910// data 저장소를 write mode로 가져온다SQLiteDatabase db = mDbHelper.getWritableDatabase();// column name을 key로 하는 값의 map을 생성한다.ContentValues values = new ContentValues();values.put(FeedEntry.COLUMN_NAME_TITLE, title);values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);// 새 row를 삽입하고 새로운 row의 primary key 값을 반환한다.long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values); insert()의 첫 번째 인수는 테이블 이름이다. 두 번째 인수는 ContentValues가 비어있는 경우 즉, put에 어떤 값도 지정하지 않은 경우에 framework가 수행할 작업을 알려준다. column의 이름을 지정하면 framework는 row를 삽입하고 column의 값을 null로 설정한다. 위의 코드와 같이 null로 지정하면 framework는 값이 없는 경우 row를 삽입하지 않는다. Database에서 정보 읽어오기Database로부터 정보를 읽어오기 위해서는 query() 메서드를 사용해야 하고 선택 기준과 원하는 column을 전달한다. 이 메서드는 insert()와 update()를 결합한다. 단, 삽입하려는 data가 아닌, 가져오려는(fetch) data를 정의한 column list는 예외로 한다. 쿼리 결과는 Cursor 객체로 반환된다. 123456789101112131415161718192021222324252627SQLiteDatabase db = mDbHelper.getReadableDatabase();// Database로부터 읽어올 특정 column들을 projection으로 정의한다.// 이 query 이후 실제로 사용이 가능해진다.String[] projection = { FeedEntry._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE };// Filter results WHERE \"title\" = 'My Title'String selection = FeedEntry.COLUMN_NAME_TITLE + \" = ?\";String[] selectionArgs = { \"My Title\" };// How you want the results sorted in the resulting CursorString sortOrder = FeedEntry.COLUMN_NAME_SUBTITLE + \" DESC\";Cursor c = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The columns to return selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order ); Cursor 안의 row를 보려면 Cursor의 이동 메서드 중 하나인 moveToFirst()를 항상 제일 먼저 호출해야 한다. 이 메서드를 통해 일반적으로 결과의 처음 항목에 “읽기 위치”를 배치하게 된다. 이후 getString()이나 getLong()등과 같은 Cursor 가져오기 메서드 중 하나를 호출하여 각 row에 대한 column값을 읽어올 수 있게 된다. 가져오기 메서드 각각에 대해 원하는 column의 인덱스 위치를 전달해야 하며, 이는 getColumnIndex() 또는 getColumnIndexOrThrow()를 호출하여 가져올 수 있다. 12cursor.moveToFirst();long itemId = cursor.getLong(cursor.getColumnIndexOrThrow(FeedEntry._ID)); Database에서 정보 삭제하기테이블에서 row를 삭제하려면 row를 식별하는 선택 기준을 제공해야 한다. Database API는 SQL injection을 방지하는 선택 기준을 생성하는 메커니즘을 제공한다. 이 매커니즘은 선택하는 방식을 선택 절(selection creteria)과 선택 인수(selection arguments)로 나눈다. 선택 절은 보려는 column을 정의하고, 이를 통해 column 테스트를 결합할 수 있다. 선택 인수는 선택 절 안에 묶여 테스트되는 값이다. 이 결과는 일반 SQL 문과 같이 처리되지 않기 때문에 SQL injection의 영향을 받지 않는다. 123456// Define 'where' part of query.String selection = FeedEntry.COLUMN_NAME_TITLE + \" LIKE ?\";// Specify arguments in placeholder order.String[] selectionArgs = { \"MyTitle\" };// Issue SQL statement.db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs); Database 업데이트 하기update() 메서드를 사용한다. 테이블을 업데이트하면 insert()의 콘텐츠 값 구문과 delete()의 where 구문이 결합된다.\\ 123456789101112131415SQLiteDatabase db = mDbHelper.getReadableDatabase();// New value for one columnContentValues values = new ContentValues();values.put(FeedEntry.COLUMN_NAME_TITLE, title);// Which row to update, based on the titleString selection = FeedEntry.COLUMN_NAME_TITLE + \" LIKE ?\";String[] selectionArgs = { \"MyTitle\" };int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs); 출처 https://developer.android.com/training/basics/data-storage/index.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] Getting Started (3)","slug":"2017-10-03-android-getting_started_3","date":"2017-10-03T09:00:00.000Z","updated":"2017-10-04T14:55:24.702Z","comments":true,"path":"2017/10/03/2017-10-03-android-getting_started_3/","link":"","permalink":"http://lazyrodi.github.io/2017/10/03/2017-10-03-android-getting_started_3/","excerpt":"","text":"Android developer site study Getting Started Building Your First App Supporting Different Devices Building a Dynamic UI with Fragments Saving Data Interacting with Other Apps Working with System Permissions https://developer.android.com/training/index.html 동적이고 multi-pane 사용자 인터페이스를 생성하기 위해 UI 컴포넌트들과 activity 행동들을 캡슐화하여 모듈을 만들고 이를 activity에 넣었다 뺐다 할 수 있다. 이런 모듈들은 Fragmentclass로 생성할 수 있다. 이는 중첩된 activity처럼 행동하며 자신의 레이아웃을 정의하고 스스로의 라이프사이클을 관리한다. fragment가 자신의 레이아웃을 정하면 여러가지 화면 크기에 따라 레이아웃 구성을 변경하기 위해 activity 내의 다른 fragmet들과 다르게 조합될 수 있다. (작은 화면에서는 하나의 fragment만 보여주지만 큰 화면에서는 두 개 이상의 fragment를 한 번에 보여줄 수 있다.) Fragment의 생성Fragment를 스스로의 라이프사이클을 갖고, 스스로의 입력 이벤트를 수신하고, activity가 동작하는 동안 추가/삭제가 가능한 activity의 모듈부라고 생각해도 된다. (“sub activity”로서 다른 activity들에서 재사용하는 것의 일종) 아래 예제들을 따라하기 전에… 만약 Suport Library를 사용한 적이 없다면 아래 페이지를 통해 v4 library를 setup하는 과정이 선행되어야 한다. https://developer.android.com/topic/libraries/support-library/setup.html v7 appcompat Library를 사용해도 된다. Fragment Class의 생성fragment를 생성하기 위해 Fragment Class를 상속받아야 한다. 그 후 key lifecycle 메서드를 Override하여 app logic에 삽입한다. 이는 Activity class의 사용과 유사한 부분이다. Fragment를 생성할 때 하나의 차이점이 있는데 레이아웃을 정의할 때 반드시 onCreateView() callback를 사용해야 한다는 것이다. 사실 이것이 fragment가 수행될 때 필요한 유일한 callback이다. 간단한 fragment 예제를 살펴보자. 12345678910111213import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.ViewGroup;public class ArticleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.article_view, container, false); }} Activity와 비슷하게, fragment는 다른 라이프사이클 callback들을 구현해야 한다. 이는 activity에 추가/삭제될 때, 그리고 activity가 라이프사이클 상태를 천이(transition)하는 것을 관리할 수 있어야 한다. 예를 들어, activity의 onPause() 메서드가 호출되면 activity에 속한 fragment들도 onPause()를 수신한다. XML을 사용하여 Activity에 Fragment를 추가하는 방법모듈러 UI 요소로서의 fragment가 재사용 가능할 때 Fragment class의 각 인스턴스는 반드시 부모인 FragmentActivity와 연결되어야 한다. 이는 각 fragment를 activity 레이아웃 XML 파일에 정의함으로써 연결이 가능하다. FragmentActivity는 API level 11 이전의 오래된 시스템 버전에서 fragment를 다루기 위한 Support Libary에 속한 특별한 activity이다. 만약 시스템 버전이 API level 11보다 높다면 일반적인 Activity를 사용하면 된다. 아래의 예시는 activity 내의 두 개의 fragment를 결합하는 레이아웃 파일이다. res/layout-large/news_article.xml123456789101112131415161718<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:orientation=\"horizontal\" android:layout_width=\"fill_parent\" android:layout_height=\"fill_parent\"> <fragment android:name=\"com.example.android.fragments.HeadlinesFragment\" android:id=\"@+id/headlines_fragment\" android:layout_weight=\"1\" android:layout_width=\"0dp\" android:layout_height=\"match_parent\" /> <fragment android:name=\"com.example.android.fragments.ArticleFragment\" android:id=\"@+id/article_fragment\" android:layout_weight=\"2\" android:layout_width=\"0dp\" android:layout_height=\"match_parent\" /></LinearLayout> 그리고 이 레이아웃은 activity 내에 아래와 같이 적용한다. 12345678910import android.os.Bundle;import android.support.v4.app.FragmentActivity;public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); }} 만약 v7 appcompat library를 사용한다면 FragmentActivity의 subclass인 AppCompatActivity를 상속받아야 한다. 레이아웃 XML 파일 내에 fragment를 정의하여 activity 레이아웃 내에 fragment를 추가하는 경우 runtime 시 이 fragment를 제거할 수 없다. 만약 동작 중에 fragment를 제거하거나 바꿔치기 해야 할 경우 반드시 activity가 처음 시작할 때 activity 내에 넣어야 한다. 이에 대해서는 아래 계속 살펴본다. Flexible UI 구현다양한 화면 크기를 지원하는 App의 경우 fragment를 여러가지 레이아웃 구성에 재활용할 수 있다. FragmentManager Class는 동적 환경 생성을 위해 runtime 시 activity에 fragment를 추가/삭제/교체할 수 있는 메서드를 제공한다. Runtime 시 activity에 fragment를 추가하는 방법Fragment의 추가/삭제 등의 트랜잭션을 수행하려면 FragmentManager를 사용하여 FragmentTransaction을 생성해야 한다.FragmentTransaction은 fragment를 추가/삭제/교체 및 기타 fragment transaction을 수행하는 API를 제공한다. Activity에서 fragment의 교체와 제거를 허용하는 경우, activity의 onCreate() 메서드에서 fragment를 activity에 추가해야 한다. Fragment 관련 작업 시 (특히 runtime 시 fragment를 추가하는 경우) 주의해야 할 것은 fragment를 삽입할 수 있는 Container View를 activity 레이아웃에 포함하는 것이다. 아래 코드는 fragment를 다른 fragment와 교체하기 위해 Container View로 빈 FrameLayout을 사용한 것이다. res/layout/new_articles.xml1234<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:id=\"@+id/fragment_container\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" /> Activity에서 Support Library API를 통해 getSupportFragmentManager()를 호출하여 FragmentManager를 가져오고 beginTransaction()으로 FragmentTransaction을 생성하고 add()로 fragment를 추가한다. 하나의 FragmentTransaction으로 activity의 여러 fragment transaction을 수행할 수 있다. 변경 준비가 완료되면 commit()을 호출해야 한다. 아래 코드는 레이아웃에 fragment를 추가하는 방법이다. 123456789101112131415161718192021222324252627282930313233import android.os.Bundle;import android.support.v4.app.FragmentActivity;public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.fragment_container) != null) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // Create a new Fragment to be placed in the activity layout HeadlinesFragment firstFragment = new HeadlinesFragment(); // In case this activity was started with special instructions from an // Intent, pass the Intent's extras to the fragment as arguments firstFragment.setArguments(getIntent().getExtras()); // Add the fragment to the 'fragment_container' FrameLayout getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } }} 다른 fragment와 교체하는 방법추가하는 절차와 비슷하지만 add() 대신 replace()를 사용한다. Fragment 교체 또는 제거와 같은 fragment transaction 수행 시 사용자가 변경 사항을 “실행 취소”할 수 있도록 허용해야 할 때도 있다. 이런 동작 (fragment transaction에서 사용자가 뒤로 돌아갈 수 있게 하는 동작)을 위해서는 FragmentTransaction을 commit하기 전에 addToBackStack()을 호출하면 된다. Fragment를 제거 또는 교체 후 해당 transaction을 BackStack에 추가하면 제거된 fragment가 파기되지 않고 중단된다. 사용자가 되돌아가서 fragment를 복원하면 fragment가 재시작된다.반면 transaction을 BackStack에 추가하지 않으면 fragment가 제거되거나 교체될 때 파기된다. 다른 fragment와의 교체는 아래와 같이 이루어진다. addToBackStack() 메서드는 transaction의 고유한 이름을 지정하는 문자열 매개변수를 취한다. 하지만 FragmentManager.BackStackEntry API를 사용하여 고급 fragment 작업을 수행하는 경우 외에는 이 이름이 필요하지 않다. 123456789101112131415// Create fragment and give it an argument specifying the article it should showArticleFragment newFragment = new ArticleFragment();Bundle args = new Bundle();args.putInt(ArticleFragment.ARG_POSITION, position);newFragment.setArguments(args);FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();// Replace whatever is in the fragment_container view with this fragment,// and add the transaction to the back stack so the user can navigate backtransaction.replace(R.id.fragment_container, newFragment);transaction.addToBackStack(null);// Commit the transactiontransaction.commit(); 다른 Fragment와의 커뮤니케이션Fragment UI 요소를 재사용하기 위해 스스로의 레이아웃 및 행동을 갖는 각각의 독립적인 모듈러 요소를 빌드해야 한다. 이 재사용 가능한 Fragment를 한 번 정의한 뒤, 이들을 Activity와 연결하고 App의 로직과 연결하여 복합적인 UI를 실제로 구현할 수 있다. 사용자 이벤트 기반의 컨텐츠를 교환하는 등의 동작을 위해 하나의 Fragment를 다른 fragment와 연결하고 싶을 때가 있을 것이다. 모든 Fragment-to-Fragment 통신은 연관된 activity를 통해 수행된다. 두 개의 fragment는 직접 통신할 수 없다. Interface의 정의Activity에 속한 Fragment가 통신을 하기 위해 Activity와 연관된 fragment class의 interface와 구현체를 정의해야 한다. Fragment는 onAttach() 라이프사이클 메서드에서 interface 구현을 확인하고 Activity와 통신하기 위해 interface 메서드를 호출한다. 아래는 Fragment와 Activity 간 통신하는 예제이다. 123456789101112131415161718192021222324public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; // Container Activity must implement this interface public interface OnHeadlineSelectedListener { public void onArticleSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // This makes sure that the container activity has implemented // the callback interface. If not, it throws an exception try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + \" must implement OnHeadlineSelectedListener\"); } } ...} 이제 fragment는 onArticleSelected() 메서드(또는 interface 내의 다른 메서드)를 통해 activity에 message를 전달할 수 있다. 이 때 OnHeadlineSelectedListener interface의 mCallback 인스턴스가 사용된다. 예를 들어, fragment의 아래 메서드는 사용자가 list item을 클릭하면 호출된다. Fragment는 이 이벤트를 부모 activity로 전달할 때 callback interface를 사용한다. 12345@Overridepublic void onListItemClick(ListView l, View v, int position, long id) { // Send the event to the host activity mCallback.onArticleSelected(position);} Interface의 구현Fragment로부터 이벤트 콜백을 수신하기 위해 activity는 fragment class에 정의된 interface를 구현해야 한다. 아래 코드는 위의 예제 interface를 구현한 코드이다. 123456789public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article }} Fragment로 Message 전달Activity는 findFragmentById() 메서드로 Fragment 인스턴스를 가져온 후 직접 fragment의 public 메서드를 호출하믕로써 message를 전달할 수 있다. 만약, 위의 activity가 fragment를 포함할 수 있다고 가정한다면 activity는 콜백 메서드에서 받은 정보를 다른 fragment로 전달하여 해당 정보를 표시하게 할 수도 있다. 12345678910111213141516171819202122232425262728293031323334353637public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { // If article frag is available, we're in two-pane layout... // Call a method in the ArticleFragment to update its content articleFrag.updateArticleView(position); } else { // Otherwise, we're in the one-pane layout and must swap frags... // Create fragment and give it an argument for the selected article ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit(); } }} 출처 https://developer.android.com/training/basics/firstapp/index.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] Getting Started (2)","slug":"2017-10-02-android-getting_started_2 ","date":"2017-10-02T01:00:00.000Z","updated":"2017-10-03T06:18:06.965Z","comments":true,"path":"2017/10/02/2017-10-02-android-getting_started_2 /","link":"","permalink":"http://lazyrodi.github.io/2017/10/02/2017-10-02-android-getting_started_2 /","excerpt":"","text":"Android developer site study Getting Started Building Your First App Supporting Different Devices Building a Dynamic UI with Fragments Saving Data Interacting with Other Apps Working with System Permissions https://developer.android.com/training/index.html 다양한 언어의 지원Locale 디렉토리 및 문자열 파일 생성더 많은 언어를 지원하기 위해서는 추가 values 디렉토리를 생성해야 한다. 이 때 디렉토리 이름 끝에 하이픈과 ISO 언어 코드를 포함하여 res/ 디렉토리 아래 생성한다. 예를 들면, values-es/는 언어 코드가 “es”인 locale의 리소스를 포함하는 디렉토리이다. 지원할 언어를 결정하고 나면 리소스 하위 디렉토리 및 문자열 리소스 파일을 생성한다. MyProject/ res/ values/ strings.xml values-kr/ strings.xml values-fr/ strings.xml 문자열 리소스 사용<string> 요소의 name 특성으로 정의된 리소스 이름을 사용하여 소스 코드 및 다른 XML 파일에서 문자열 리소스를 참조할 수 있다. 소스 코드 구문에서 참조R.string.<string_name>을 사용하여 문자열 리소스를 참조. 123456// App의 리소스로부터 string 리소스를 가져오기String hello = getResources().getString(R.string.hello_world);// 메서드가 요구하는 string을 string 리소스로 제공TextView textView = new TextView(this);textView.setText(R.string.hello_world); 다른 XML 파일에서 참조@string/<string_name>을 사용하여 문자열 리소스를 참조 1234<TextView android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:text=\"@string/hello_world\" /> 다양한 화면의 지원Android는 size와 density 두 가지의 프로퍼티를 사용하여 장치의 화면을 분류한다. 따라서 다양한 화면 크기 및 해상도에 최적화하기 위해 대체 리소스를 준비해야 한다. 일반적으로 사용하는 네 가지 size small, normal, large, xlarge 일반적으로 사용하는 네 가지 density low (ldpi), medium (mdpi), high (hdpi), extra high (xhdpi) 다양한 화면을 지원하기 위한 레이아웃과 비트맵은 분리된 디렉토리에 위치시켜야 한다. 이는 앞에서 보았던 언어 별 문자열을 나눈 것과 비슷하다. 화면 방향 (landscape 또는 portrait) 또한 고려해야 한다. 많은 App들이 화면 방향에 따라 서로 다른 사용자 경험을 제공하고 있다. 다른 레이아웃의 생성서로 다른 화면 크기에 대한 사용자 경험을 최적화하기 위해 각 화면 크기에 맞는 고유한 레이아웃 XML 파일을 생성해야 한다. 각 레이아웃은 올바른 리소스 디렉토리에 저장해야 하며 접미사로 -<screen_size>를 사용한다. 예를 들어 large screen에 대한 레이아웃은 res/layout-large/에 저장해야 한다. Android는 자동으로 레이아웃의 비율을 조정하여 화면에 맞춰준다. 그리하여 개발자는 절대적인 크기에 연연할 필요가 없고 레이아웃 구조에 대해 더 신경써야 한다. 예를 들어, 이 프로젝트가 기본 레이아웃과 large screen을 위한 레이아웃을 가지고 있다고 하면 아래와 같은 구조를 갖는다. MyProject/ res/ layout/ main.xml layout-large/ main.xml 파일 이름은 완전히 같아야 한다. 레이아웃 파일은 app 내에서 일반적으로 아래와 같이 참조한다. 12345@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);} App이 시행되면 시스템은 화면 크기에 맞는 적당한 레이아웃 디렉토리로부터 레이아웃 파일을 읽어온다. 또 다른 예로, landscape 방향을 위한 레이아웃은 아래와 같이 사용할 수 있다. MyProject/ res/ layout/ main.xml layout-land/ main.xml layout/main.xml 파일의 기본 값은 portrait 방향이다. large와 landscape를 모두 사용할 경우 아래와 같이 구성한다. MyProject/ res/ layout/ main.xml layout-land/ main.xml layout-large/ main.xml layout-large-land/ main.xml 서로 다른 비트맵 생성개발자는 일반적인 해상도들(low, medium high, extra-high)에 맞춘 적절한 비트맵 리소스들을 준비해 두어야 한다. 이러한 이미지들을 생성하기 위해 raw 리소스를 vector format으로 시작하고 아래의 해상도에 맞는 각각의 이미지를 생성해야 한다. xhdpi: 2.0 hdpi: 1.5 mdpi: 1.0 (baseline) ldpi: 0.75 이것의 의미는 xhdpi 기기를 위해 200x200 이미지를, hdpi 기기를 위해 150x150 이미지를, mdpi 기기를 위해 100x100 이미지를, ldpi 기기를 위해 75x75 이미지를 준비해야 한다는 것이다. 그 후 각각의 drawable 리소스 디렉토리에 위치시킨다. MyProject/ res/ drawable-xhdpi/ awesomeimage.png drawable-hdpi/ awesomeimage.png drawable-mdpi/ awesomeimage.png drawable-ldpi/ awesomeimage.png 이 파일의 참조는 @drawable/awesomeimage 의 방식으로 할 수 있다. 그러면 시스템이 알아서 화면 해상도에 맞는 이미지를 찾아줄 것이다. Low-density(ldpi) 리소스는 꼭 필요한 것은 아니다. 개발자가 hdpi 리소스들만 준비해 둔다면 시스템은 해상도를 조정하여 ldpi 화면에 맞춰줄 것이다. 서로 다른 플랫폼 버전의 지원최신 버전의 Android에서는 새로운 API들을 지원하기 때문에 이전 버전의 Android를 업데이트 해야한다. 각각의 Android 버전에서 최상의 기능을 지원하기 위해서 Android Support Library를 사용하는 것을 추천한다.이를 사용하여 이전 버전의 Android App에서도 최신 플랫폼 API를 사용할 수 있게 된다. Minimum API level 및 Target API level 정하기AndroidManifest.xml 파일에는 App에 대한 정보들에 대해 기술되어 있다. 특별히 minSdkVersion과 targetSdkVersion 특성은 <uses-sdk> 요소에 정의된다. 이는 호환을 지원하는 최저 API level과 App이 어떤 버전을 Target으로 하여 제작되었는지를 나타낸다. 예를 들면 아래와 같다. 1234<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" ... > <uses-sdk android:minSdkVersion=\"4\" android:targetSdkVersion=\"15\" /> ...</manifest> Android의 새 버전이 릴리즈되면 몇몇의 스타일 및 동작이 달라진다. App이 이러한 변화들을 받아들일 수 있게 하려면 targetSdkVersion에는 꼭 최신 버전을 넣어주는 것이 좋다. Runtime에 시스템 버전 체크하기Android는 Build 라는 상수에 각 플랫폼 버전에 대한 고유한 코드를 넣어둔다. 이 코드를 이용하여 특정 API를 현재 App에서 사용할 수 있는지 확인할 수 있다. 1234567private void setUpActionBar() { // Make sure we're running on Honeycomb or higher to use ActionBar APIs if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ActionBar actionBar = getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); }} XML 리소스를 파싱할 때 Android는 현재 기기가 지원하지 않는 XML 특성들을 무시한다. 따라서 새 버전에서만 지원하는 XML 속성들을 안전하게 사용할 수 있다.예를 들어, targetSdkVersion = “11”로 설정되면 Android 3.0 이상에서는 기본적으로 ActionBar가 App에 포함된다.Menu item을 ActionBar에 추가하기 위해서는 menu 리소스 XML 파일에 android:showAsAction=”ifRoom” 을 설정해주면 된다.이 방법은 서로 다른 버전의 XML 파일에서 안전하게 작동하는데 그 이유는 이전 버전의 Android에서는 showAsAction 특성을 무시하기 때문이다. (즉, res/menu-v11/ 내에 별도의 버전이 필요하지 않다는 것이다.) 플랫폼 스타일과 테마 사용하기Android는 OS에 맞는 look and feel을 위해 App에 테마를 제공한다. 테마는 manifest 파일에서 설정할 수 있다. 이런 내장 스타일과 테마를 사용함으로써 App은 최신 Androrid의 look and feel을 자연스럽게 적용할 수 있게 된다. Activity를 Dialog box처럼 보이게 하기 <activity android:theme=\"@android:style/Theme.Dialog\"> Activity에 투명 배경으로 만들기 <activity android:theme=\"@android:style/Theme.Translucent\"> /res/values/styles.xml 에 정의한 커스텀 테마 적용하기 <activity android:theme=\"@style/CustomTheme\"> App의 모든 Activity에 테마를 적용하고 싶을 때에는 <application> 요소에 android:theme 특성을 추가한다. <application android:theme=\"@style/CustomTheme\"> 출처 https://developer.android.com/training/basics/firstapp/index.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] 추후 살펴봐야 할 것들","slug":"2017-10-01-android-have_to_look","date":"2017-10-01T04:00:00.000Z","updated":"2017-10-14T02:44:11.770Z","comments":true,"path":"2017/10/01/2017-10-01-android-have_to_look/","link":"","permalink":"http://lazyrodi.github.io/2017/10/01/2017-10-01-android-have_to_look/","excerpt":"","text":"Getting started (1) Android Manifest https://developer.android.com/guide/topics/manifest/manifest-intro.html Gradle https://developer.android.com/studio/build/index.html Layouts guide https://developer.android.com/guide/topics/ui/declaring-layout.html Resource https://developer.android.com/guide/topics/resources/providing-resources.html Intent https://developer.android.com/reference/android/content/Intent.html (2) 대체 resource 제공 https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources localization https://developer.android.com/guide/topics/resources/localization.html Desining for multiple screens https://developer.android.com/training/multiscreen/index.html icon design https://material.io/guidelines/style/icons.html Android Support Library https://developer.android.com/topic/libraries/support-library/index.html AndroidManifest.xml https://developer.android.com/guide/topics/manifest/manifest-intro.html Build constant https://developer.android.com/reference/android/os/Build.html 스타일 및 테마 https://developer.android.com/guide/topics/ui/themes.html (3) Fragment https://developer.android.com/guide/components/fragments.html (4) Preference https://developer.android.com/reference/android/preference/Preference.html 설정 https://developer.android.com/guide/topics/ui/settings.html App Install Location https://developer.android.com/guide/topics/data/install-location.html SQLite Helper https://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html Asynctask https://developer.android.com/reference/android/os/AsyncTask.html ContentValue https://developer.android.com/reference/android/content/ContentValues.html Cursor https://developer.android.com/reference/android/database/Cursor.html (5) Uri https://developer.android.com/reference/android/net/Uri.html Link to google play (구글 플레이에서 자신의 제품에 대한 링크를 제공하는 방법) https://developer.android.com/distribute/marketing-tools/linking-to-google-play.html Bitmap https://developer.android.com/reference/android/graphics/Bitmap.html 사진 캡쳐하기 https://developer.android.com/training/camera/index.html 콘텐츠 프로바이더 https://developer.android.com/guide/topics/providers/content-providers.html CursorLoader https://developer.android.com/reference/android/content/CursorLoader.html 보안 및 권한 https://developer.android.com/training/articles/security-tips.html 다른 앱에서 간단한 data 수신하기 https://developer.android.com/training/sharing/receive.html (6) 정상 권한 및 위험한 권한 https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous 인텐트 사용 고려 https://developer.android.com/training/permissions/best-practices.html#perms-vs-intents 지원 라이브러리 https://developer.android.com/topic/libraries/support-library/index.html 권한 그룹 https://developer.android.com/guide/topics/security/permissions.html#perm-groups Building Apps with Content Sharing(1) ActionProvider https://developer.android.com/reference/android/view/ActionProvider.html ContentProvider https://developer.android.com/reference/android/content/ContentProvider.html per-URI permissions https://developer.android.com/guide/topics/security/permissions.html#uri FileProvider https://developer.android.com/reference/android/support/v4/content/FileProvider.html MediaStore.Files https://developer.android.com/reference/android/provider/MediaStore.Files.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Android] Getting Started (1)","slug":"2017-10-01-android-getting_started_1","date":"2017-10-01T01:00:00.000Z","updated":"2017-10-02T09:52:44.411Z","comments":true,"path":"2017/10/01/2017-10-01-android-getting_started_1/","link":"","permalink":"http://lazyrodi.github.io/2017/10/01/2017-10-01-android-getting_started_1/","excerpt":"","text":"Android developer site study Getting Started Building Your First App Supporting Different Devices Building a Dynamic UI with Fragments Saving Data Interacting with Other Apps Working with System Permissions https://developer.android.com/training/index.html Android Project 생성 Android Studio > New Project Application Name: “My First App” Company Domain: “example.com” Next Minimum Required SDK App이 지원하는 가장 이전의 Android 버전. [API Level은 여기서 확인], [[버전 별 사용률은 여기서 확인](https://developer.android.com/about/dashboards/index.html) Empty Activity > Next > Finish 파일구조app > java > com.example.myfirstapp > MainActivity.javaActivity에 대한 Class 정의. App 실행 시 Activity가 실행되며 내용을 표시하는 레이아웃 파일을 로드함. app > res > layout > activity_main.xmlActivity의 레이아웃을 정의하는 XML 파일. app > manifest > AndroidManifest.xmlApp의 기본 특징을 설명하고 App의 각 구성요소를 정의함. Gradle Scripts > build.gradleAndroid Studio는 Build tool로 Gradle를 사용함. 프로젝트의 각 모듈을 위한 build.gradle 파일이 있고 전체 프로젝트를 위한 build.gradle 파일도 있음.일반적으로는 모듈용 build.gradle 파일만 보면 된다. App 실행App을 실행하여 확인하는 방법엔 두 가지가 있음. 실제 기기에서 실행하는 방법 에뮬레이터에서 실행하는 방법 실제 기기에서 실행하는 방법 각 OEM 별 Driver를 [여기에서 다운로드] 받음. 디바이스에서 Settings > Developer options로 이동하여 USB debugging을 활성화 시킴. (Android 4.2 이상 버전에서는 Developer options가 숨겨져 있으므로 Settings > About phone의 Build number를 7번 누른 후 이전 화면으로 돌아가면 Developer options에 진입할 수 있음.) Android Studio에서 Run을 선택하여 실행. 에뮬레이터에서 실행하는 방법AMD 칩셋에서는 하드웨어 가속화를 사용할 수 없기 때문에 에뮬레이터가 구동되지 않는다. (방법은 있지만 Intel 칩셋보다 10배 이상 느리므로 실제 기기에서 실행하는 방법이 추천되고 있다.) [[AVD (Android Virtual Device)]]의 정의 Tools > Android > AVD Manager Create Virtual Device 휴대폰 기기 선택 (ex. Nexus 6) > Next 원하는 시스템 이미지 선택 후 Next (특정 시스템 이미지가 설치되어 있지 않은 경우 download 링크를 선택하여 설치) 구성 설정 후 Finish App 실행 Android Studio에서 Run을 선택하여 실행. 생성한 에뮬레이터를 선택한 후 OK 간단한 사용자 인터페이스 구축간단하게 Text field와 Button이 포함된 XML layout을 만들어 보자. Android app의 GUI는 View 또는 ViewGroup개체의 계층 구조를 활용해 만들어진다. View개체는 일반적으로 Button, TextField와 같은 UI Widget을 의미하고, ViewGroup개체는 Grid, List 등의 하위 View의 layout을 정의하는 보이지 않는 View Container이다. Layout은 ViewGroup의 서브클래스이다. LinearLayout 생성 app > res > layout > activity_main.xml 을 연다. 모든 내용을 삭제 후 아래 소스로 대체한다.12345678<?xml version=\"1.0\" encoding=\"utf-8\"?><LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools=\"http://schemas.android.com/tools\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" android:orientation=\"horizontal\"></LinearLayout> LinearLayout은 android:orientation 특성에 지정된 대로 하위 View를 세로 또는 가로 방향으로 배치하는 ViewGroup이다. 각 하위 View는 XML에 나열된 순서에 따라 화면에 표시된다. Text field 추가아래 EditText를 추가해본다. 1234567891011<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools=\"http://schemas.android.com/tools\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\" android:orientation=\"horizontal\"> <EditText android:id=\"@+id/edit_message\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:hint=\"@string/edit_message\" /></LinearLayout> android:id코드에서 객체를 참조할 때 사용하는 View의 고유 식별자. XML에서 리소스 객체를 참조할 때에는 @ 기호를 사용해야 한다. @ 기호 다음에는 리소스 유형(여기서는 id), 슬래시(/), 리소스 이름(edit_message) 순서로 지정한다. 리소스 ID를 맨 처음으로 지정할 때에는 맨 앞에 + 를 붙여야 한다. App Compile 시 SDK Tool은 프로젝트의 R.java 파일에서 ID 이름을 사용하여 새 리소스 ID를 생성한다. android:layout_width, android:layout_height너비와 높이에 대해 특정 크기를 사용할 수도 있으며 wrap_content, match_parent를 사용할 수 있다. android:hintText field가 공백일 때 표시되는 기본 문자열이다. 리소스 객체는 비트맵, 레이아웃 파일, 문자열 등과 같이 앱 리소스와 연관된 고유한 정수 이름이다.모든 리소스마다 해당 리소스 객체가 프로젝트의 R.java 파일에 정의되어 있다.SDK Tool은 App을 Compile할 때마다 R.java 파일을 생성한다. 이 파일은 절대 직접 수정해서는 안 된다. 리소스 객체 문자열 리소스 추가res > values > strings.xml 에 문자열 리소스 파일을 포함한다. 새로운 문자열 추가는 아래와 같이 하면 된다. res > values > strings.xml 을 연다. 아래와 같이 문자열을 추가한다. 123456<?xml version=\"1.0\" encoding=\"utf-8\"?><resources> <string name=\"app_name\">My First App</string> <string name=\"edit_message\">Enter a message</string> <string name=\"button_send\">Send</string></resources> 문자열 지정 시 항상 각 문자열을 리소스로 지정하는 것이 좋다. 문자열 리소스를 사용하면 한 위치에서 모든 UI 텍스트를 관리할 수 있기 때문에 텍스트를 손쉽게 찾고 업데이트 할 수 있다. 또한, 문자열 외현화(Externalization)을 통해 각 문자열 리소스에 대한 대체 정의를 제공하여 다양한 언어로 현지화할 수 있다. Button 추가activity_main.xml 에 아래와 같이 Button을 추가한다. 123456789101112131415<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools=\"http://schemas.android.com/tools\" android:orientation=\"horizontal\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"> <EditText android:id=\"@+id/edit_message\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:hint=\"@string/edit_message\" /> <Button android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:text=\"@string/button_send\" /></LinearLayout> 입력란을 화면 너비만큼 채우도록 설정activity_main.xml 에서 \\를 아래와 같이 수정한다. 12345<EditText android:id=\"@+id/edit_message\" android:layout_weight=\"1\" android:layout_width=\"0dp\" android:layout_height=\"wrap_content\" android:hint=\"@string/edit_message\" /> width를 0으로 설정하면 레이아웃 성능이 개선된다. 왜냐하면 wrap_content를 width로 사용하면 시스템으로 하여금 관련 없는 width를 산출하게 만들이 때문이다.이런 동작을 하는 이유는 가중치(weight) 값이 남은 공간을 채우는 데 필요한 너비를 산출하게 만들기 때문이다. 완성된 activity_main.xml 레이아웃 파일은 아래와 같다. 12345678910111213141516<?xml version=\"1.0\" encoding=\"utf-8\"?><LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools=\"http://schemas.android.com/tools\" android:orientation=\"horizontal\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"> <EditText android:id=\"@+id/edit_message\" android:layout_weight=\"1\" android:layout_width=\"0dp\" android:layout_height=\"wrap_content\" android:hint=\"@string/edit_message\" /> <Button android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:text=\"@string/button_send\" /></LinearLayout> 다른 액티비티 시작하기사용자가 Send Button을 눌렀을 때 새로운 Activity를 시작하는 코드를 만들어보자. Send Button에 응답하기 res > layout > activity_main.xml의 Button에 android:onClick 특성을 추가한다. 이 특성은 사용자가 Send Button을 클릭할 때마다 시스템이 Activity에서 sendMessage() 메서드를 호출하도록 지시한다. 12345<Button android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:text=\"@string/button_send\" android:onClick=\"sendMessage\" /> java > com.example.myfirstapp > MainActivity.java에 sendMessage() 메서드 스텁을 추가 시스템이 이 메서드를 android:onClick에 지정된 메서드 이름과 일치시키도록 하려면 서명이 표시된 것과 정확히 같아야 한다. 이 메서드에는 다음과 같은 조건이 필요하다. public method여야 한다. 유효한 return값을 가져야 한다. View가 유일한 매개변수(클릭한 View)여야 한다. 123456789101112public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** Called when the user clicks the Send button */ public void sendMessage(View view) { // Do something in response to button }} Intent 만들기Intent는 별도의 구성요소 (ex. 두 개의 Activity) 간 Runtime Binding을 제공하는 객체이다. Intent는 App의 “작업 의도”를 나타내며 다양한 작업에 사용할 수 있다.여기서는 Intent를 사용하여 다른 Activity를 시작해 보자. MainActivity.java의 sendMessage()에 아래 코드를 추가한다. 1234567891011121314151617public class MainActivity extends AppCompatActivity { public final static String EXTRA_MESSAGE = \"com.example.myfirstapp.MESSAGE\"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** Called when the user clicks the Send button */ public void sendMessage(View view) { Intent intent = new Intent(this, DisplayMessageActivity.class); EditText editText = (EditText) findViewById(R.id.edit_message); String message = editText.getText().toString(); intent.putExtra(EXTRA_MESSAGE, message); startActivity(intent); }} Intent 생성자는 두 개의 매개변수를 가진다. 첫 번째 매개변수 Context(Activity Class가 Context의 서브클래스이기 때문에 this를 사용함) 시스템이 Intent를 전달할 App 구성요소의 Class (여기서는 시작할 Activity의 Class) putExtra() 메서드는 EditText의 값을 Intent에 추가한다. Intent는 Key-value 쌍을 사용하는 Extra라는 Data type을 전달할 수 있다. 다음 Activity가 Key를 사용하여 Text value를 검색하기 때문에 Key는 public 상수인 EXTRA_MESSAGE이다. App package의 이름을 접두사로 사용해 Intent extra의 key를 정의하는 것이 좋다. 그렇게 하면 다른 App와 상호작용 시 Key를 고유하게 유지할 수 있다. startActivity()는 Intent로 지정한 DisplayMessageActivity의 Instance를 시작한다. 이제 Class를 생성할 차례가 되었다. 두 번째 Activity 생성 Project 창에서 app 폴더를 우클릭 후 New > Activity > Empty Activity를 선택 Configure Activity 창에서 Activity Name에 “DisplayMessageActivity”를 입력 후 Finish Android Studio는 아래 세 가지 작업을 자동으로 수행하게 된다. 필수 onCreate() 메서드의 구현을 통해 Class DisplayMessageActivity.java 를 생성한다. 해당 레이아웃 파일 activity_display_message.xml을 생성한다. 필수 요소를 AndroidManifest.xml에 추가한다. 메시지 표시하기DisplayMessageActivity.java 에서 아래 코드를 onCreate() 에 추가한다. 1234567891011121314@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_display_message); Intent intent = getIntent(); String message = intent.getStringExtra(MainActivity.EXTRA_MESSAGE); TextView textView = new TextView(this); textView.setTextSize(40); textView.setText(message); ViewGroup layout = (ViewGroup) findViewById(R.id.activity_display_message); layout.addView(textView);} 위의 소스에 대해 설명은 아래와 같다. getIntent()를 호출하면 Activity를 시작한 Intent를 가져온다. 모든 Activity는 사용자의 탐색 방식과 상관없이 Intent에 의해 호출된다. getStringExtra()를 호출하면 첫 번째 Activity에서 Data를 검색한다. TextView를 생성하고 Size와 Message를 설정한다. 참고이전 버전의 Android Studio로 생성한 XML 레이아웃에는 android.id 특성이 포함되지 않을 수도 있다.레이아웃에 android:id 특성이 없으면 findViewById()를 호출할 수 없기 때문에 이런 경우 xml 파일을 열고 android:id 특성을 추가해주어야 한다. 즉, activity_display_message.xml 파일은 아래처럼 구성되어야 한다. 1234567891011<?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=\"com.example.myfirstapp.DisplayMessageActivity\" android:id=\"@+id/activity_display_message\" ></android.support.constraint.ConstraintLayout> 출처 https://developer.android.com/training/basics/firstapp/index.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"study","slug":"study","permalink":"http://lazyrodi.github.io/tags/study/"}]},{"title":"[Spring] Vue.js, Semantic UI 사용하기","slug":"2017-09-24-spring-vuejs-semanticui","date":"2017-09-24T08:22:00.000Z","updated":"2017-09-24T13:15:57.698Z","comments":true,"path":"2017/09/24/2017-09-24-spring-vuejs-semanticui/","link":"","permalink":"http://lazyrodi.github.io/2017/09/24/2017-09-24-spring-vuejs-semanticui/","excerpt":"","text":"Vue.jsFrontend framework으로 간단한 템플릿 구문을 사용해 선언적으로 DOM에 데이터를 렌더링할 수 있다. https://vuejs-kr.github.io/ 사용하기 위한 준비는 html 파일에 아래 구문을 추가하는 것만으로 충분하다. 예제는 다음과 같다. hexo에서 { { message } } 가 잘 표현되지 않아 \\ 를 넣었다. 실제 소스에서는 넣지 않는다. 123456789101112131415161718192021222324<html><head><script src=\"https://unpkg.com/vue\"></script><script>window.onload = function() { var app = new Vue({ el: '#app', data: { message: 'hello vue' } });}</script></head><body><div id=\"app\">\\{\\{ message }}</div></body></html> Semantic UI이전에 BootStrap을 사용해본 적이 있었기 때문에 핫한 ui framework이 뭐가 있나 찾아보았더니 Semantic UI를 찾을 수 있었다. Semantic UI는 jquery 기반의 UI Framework이다. vue.js와의 integration을 위한 https://semantic-ui-vue.github.io/ 도 있었으나, 아직은 잘 모르므로 일반적인 것으로 그냥 해보기로 한다. (아마 나중에 코드가 복잡해지면 js 코드끼리 충돌이 나서 깨지리라 예측해본다…) 사용법은 여기 나와있다. https://semantic-ui.com/introduction/getting-started.html 설치 등이 귀찮기 때문에 cdn을 찾아서 link하였다. 1234567<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.min.css\"><script src=\"https://code.jquery.com/jquery-3.1.1.min.js\" integrity=\"sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=\" crossorigin=\"anonymous\"></script><script src=\"https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.js\"></script> body에는 이런 코드를 넣어서 확인해본다. 12345678910<div id=\"index\"><button class=\"ui primary button\"> Save</button><button class=\"ui button\"> Discard</button></div> 참조 https://vuejs.org/ https://vuejs-kr.github.io/ https://nodejs.org/en/download/ http://www.itread01.com/articles/1487775447.html https://semantic-ui-vue.github.io","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"},{"name":"springboot","slug":"springboot","permalink":"http://lazyrodi.github.io/tags/springboot/"},{"name":"vue","slug":"vue","permalink":"http://lazyrodi.github.io/tags/vue/"},{"name":"vuejs","slug":"vuejs","permalink":"http://lazyrodi.github.io/tags/vuejs/"},{"name":"semantic ui","slug":"semantic-ui","permalink":"http://lazyrodi.github.io/tags/semantic-ui/"}]},{"title":"[Spring] Header, Footer 넣기 (Apache Tiles)","slug":"2017-09-23-spring-header-footer","date":"2017-09-23T05:22:00.000Z","updated":"2017-09-23T15:58:28.834Z","comments":true,"path":"2017/09/23/2017-09-23-spring-header-footer/","link":"","permalink":"http://lazyrodi.github.io/2017/09/23/2017-09-23-spring-header-footer/","excerpt":"","text":"Apache Tiles를 이용하여 Header와 Footer를 설정해본다. build.gradledependencies에 tiles 추가. Build.gradlecompile('org.apache.tiles:tiles-jsp:3.0.4') File 생성tiles.xml/WEB-INF/ 아래 tiles.xml 생성 https://tiles.apache.org/framework/tutorial/basic/pages.html 참조 tiles.xml12345678910111213141516171819202122<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?><!DOCTYPE tiles-definitions PUBLIC \"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN\" \"http://tiles.apache.org/dtds/tiles-config_3_0.dtd\"><tiles-definitions> <!-- template --> <definition name=\"layout.default\" template=\"/WEB-INF/views/jsp/default.jsp\"> <put-attribute name=\"header\" value=\"/WEB-INF/views/jsp/header.jsp\" /> <put-attribute name=\"footer\" value=\"/WEB-INF/views/jsp/footer.jsp\" /> </definition> <!-- pages --> <!-- 여기서 name의 index, login은 Controller에서 RequestMapping 할 때 return해주는 page 이름과 맞춰준다. --> <definition name=\"index\" extends=\"layout.default\"> <put-attribute name=\"body\" value=\"/WEB-INF/views/jsp/index.jsp\" /> </definition> <definition name=\"login\" extends=\"layout.default\"> <put-attribute name=\"body\" value=\"/WEB-INF/views/jsp/login.jsp\" /> </definition></tiles-definitions> header.jsp, footer.jsp, default.jsp/WEB-INF/tiles/ 아래 header.jsp, footer.jsp 파일 생성 header.jsp123<div id=\"header\" style=\"height:200px;background-color:#607d8b;color:#ffffff;\">Here is header</div> footer.jsp123<div id=\"footer\" style=\"height:200px;background-color:#607d8b;color:#ffffff;\">Here is footer</div> default.jsp 파일은 기본적인 layout이라고 보면 될 것 같다. 전체 layout의 변경은 이 파일에서 이루어진다. header, footer 형식 말고도 여러가지 layout들을 결합하여 사용할 수 있다. default.jsp123456789101112131415<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><%@ taglib uri=\"http://tiles.apache.org/tags-tiles\" prefix=\"tiles\" %><!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head> <link rel=\"stylesheet\" href=\"/css/style.css\" /></head><body> <tiles:insertAttribute name=\"header\" /> <tiles:insertAttribute name=\"body\" /> <tiles:insertAttribute name=\"footer\" /></body></html> Config 파일 생성TilesConfig class 생성. TilesConfig.java1234567891011121314151617181920212223242526package com.lazyrodi.binbang.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.view.tiles3.TilesConfigurer;import org.springframework.web.servlet.view.tiles3.TilesView;import org.springframework.web.servlet.view.tiles3.TilesViewResolver;@Configurationpublic class TilesConfig{ @Bean public TilesConfigurer tilesConfigurer() { final TilesConfigurer configurer = new TilesConfigurer(); configurer.setDefinitions(new String[] {\"WEB/INF/tiles/tiles.xml\"}); configurer.setCheckRefresh(true); return configurer; } @Bean public TilesViewResolver tilesViewResolver() { final TilesViewResolver resolver = new TilesViewResolver(); resolver.setViewClass(TilesView.class); return resolver; }} 참조 http://javasampleapproach.com/spring-framework/spring-boot/how-to-start-with-apache-tiles-in-spring-boot https://github.com/mmeany/spring-boot-web-mvc-tiles3 https://tiles.apache.org/framework/tutorial/basic/pages.html https://rebeccacho.gitbooks.io/spring-study-group/content/chapter6.html http://marsland.tistory.com/423","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"},{"name":"springboot","slug":"springboot","permalink":"http://lazyrodi.github.io/tags/springboot/"},{"name":"header","slug":"header","permalink":"http://lazyrodi.github.io/tags/header/"},{"name":"footer","slug":"footer","permalink":"http://lazyrodi.github.io/tags/footer/"},{"name":"tiles","slug":"tiles","permalink":"http://lazyrodi.github.io/tags/tiles/"}]},{"title":"Spring Troubleshooting","slug":"2017-09-18-troubleshooting-spring","date":"2017-09-18T11:22:00.000Z","updated":"2017-09-18T14:02:55.778Z","comments":true,"path":"2017/09/18/2017-09-18-troubleshooting-spring/","link":"","permalink":"http://lazyrodi.github.io/2017/09/18/2017-09-18-troubleshooting-spring/","excerpt":"","text":"java.lang.IllegalStateException: Could not load JDBC driver class [oracle.jdbc.driver.OracleDriver]Oracle DB를 사용하는 경우 Library를 찾을 수 없어서 발생하는 에러이다. http://www.oracle.com/technetwork/database/features/jdbc/jdbc-ucp-122-3110062.html 여기서 JDK8에 맞는 최신 버전인 ojdbc8.jar 를 다운받을 수 있다. 이 파일을 c: 에 복사해놓고 cmd > c:\\로 이동 한 후 아래 명령을 수행하면 Maven 환경에 추가할 수 있다. mvn install:install-file -Dfile=ojdbc8.jar -DgroupId=com.oracle -DartifactId=ojdbc8 -Dversion=12.2.0.1 -Dpackaging=jar DataSource에 MySQL과 Oracle 각각 설정하는 방법 http://houki.tistory.com/41 MySQL 연동<bean id=\"dataSource\" class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\"> <property name=\"driverClassName\" value=\"com.mysql.jdbc.Driver\" /> <property name=\"url\" value=\"jdbc:mysql://localhost:3306/mydb?characterEncoding=EUCKR\" /> <property name=\"username\" value=\"abcd\" /> <property name=\"password\" value=\"pppw\" /></bean>출처: http://houki.tistory.com/41 [아이고 어렵다] Oracle 연동<bean id=\"dataSource\" class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\"> <property name=\"driverClassName\" value=\"oracle.jdbc.driver.OracleDriver\" /> <property name=\"url\" value=\"jdbc:oracle:thin:@localhost:1521:orcl\" /> <property name=\"username\" value=\"abcd\" /> <property name=\"password\" value=\"pppw\" /></bean>출처: http://houki.tistory.com/41 [아이고 어렵다] @RequestMapping 에서 @PathVariable 로 값을 넘겨받을 때 @ 또는 . 등의 문자로 인해 String이 잘리는 문제 http://winmargo.tistory.com/category/%E2%99%A8%20FrameWork 문제 : email 정보([email protected])를 query 할 때 제대로 query되지 않았음.해결 : 아래와 같이 {email} 값을 사용할 때 {email:.+} 의 형식으로 정규표현식을 사용하면 해결됨. @RequestMapping(value = \"user/email/{email:.+}\", method = RequestMethod.GET)public ModelAndView getUserByEmail(@PathVariable(\"email\") String email) {출처: http://winmargo.tistory.com/category/♨ FrameWork [보리 & 마고] Establishing SSL connection without server’s identity verification is not recommended……왠지 모르겠지만 아래와 같은 에러가 발생하였고 찾아보니 간단한 해결책이 있었다. WARN: Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification. 아래와 같이 autoReconnect=true&useSSL=false 를 붙여준다. 그냥 & 으로 붙이면 예약어의 시작으로 인식할 수 있으므로 &amp; 를 사용한다. <beans:property name=\"url\" value=\"jdbc:mysql://localhost:3306/crudtest?characterEncoding=EUCKR&amp;autoReconnect=true&amp;useSSL=false\" /> https://stackoverflow.com/questions/34189756/warning-about-ssl-connection-when-connecting-to-mysql-database","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"TroubleShooting","slug":"Dev/TroubleShooting","permalink":"http://lazyrodi.github.io/categories/Dev/TroubleShooting/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"}]},{"title":"[Spring] Quick Guide","slug":"2017-09-18-spring-quick_guide","date":"2017-09-18T08:20:00.000Z","updated":"2017-10-22T10:31:19.805Z","comments":true,"path":"2017/09/18/2017-09-18-spring-quick_guide/","link":"","permalink":"http://lazyrodi.github.io/2017/09/18/2017-09-18-spring-quick_guide/","excerpt":"","text":"나만의 빠른 가이드. STS 설치 Maven 설치 New Spring Legacy Project 생성 pom.xml 수정 Project property 수정 JSTL 파일 변경 Tomcat 설정 변경 MySQL Workbench에서 DB Schema 및 Table 생성 CRUD 구성 STS 설치 https://spring.io/tools Maven 설치 http://maven.apache.org/download.cgi 설치 후 cmd 에서 mvn -version 으로 설치 확인 New Spring Legacy Project 생성pom.xml 수정Spring version 변경// before<org.springframework-version>3.1.1.RELEASE</org.springframework-version>// after<org.springframework-version>4.3.10.RELEASE</org.springframework-version> log4j-slf4j 를 dependency에 추가Library 충돌 문제 해결을 위한 것으로, Spring 3.0 이상의 경우 아래의 코드를 pom.xml 에 추가. JDBC와 MySQL 사용을 위한 dependency 추가123456789101112<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.11.RELEASE</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.5</version></dependency> Project property 수정 Java 의 Version을 1.8 로 변경 Project Facets 검색 > Runtimes 탭에서 Apache Tomcat V7.0 선택 JSTL 파일 변경 다운로드 : https://mvnrepository.com/artifact/javax.servlet/jstl/1.2 C:\\Users\\사용자\\.m2\\repository\\javax\\servlet\\jstl\\1.2 경로의 파일 바꿔치기 Tomcat 설정 변경 Eclipse의 Tomcat server 더블 클릭 Server Options의 Publish module contexts to separate XML files 체크 TOMCAT 재구동 한글이 깨질 경우jsp 파일의 상단에 아래 코드 추가 <%@ page pageEncoding=\"UTF-8\" contentType=\"text/html; charset=UTF-8\"%> MySQL Workbench에서 DB Schema 및 Table 생성예시 Table. email: VARCHAR(100), PK, NNpassword: VARCHAR(20), NNnumber: VARCHAR(20)age: INT(10), UN CRUD 구성Data Class 생성사용할 Data의 Class 생성. src/main/java/com.lazyrodi.crud/User.java User.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950package com.lazyrodi.crud;public class User { private String mEmail; private String mPassword; private String mNumber; private int mAge; User() { } User(String email, String password, String number, int age) { mEmail = email; mPassword = password; mNumber = number; mAge = age; } public String getEmail() { return mEmail; } public String getPassword() { return mPassword; } public String getNumber() { return mNumber; } public int getAge() { return mAge; } public void setEmail(String email) { mEmail = email; } public void setPassword(String password) { mPassword = password; } public void setNumber(String number) { mNumber = number; } public void setAge(int age) { mAge = age; }} DAO Class 생성Data와 Database 간 connection을 위한 DAO (Data Access Object) 생성. src/main/java/com.lazyrodi.crud/UserDao.java UserDao.java1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465package com.lazyrodi.crud;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;public class UserDao { JdbcTemplate mTemplate; public void setTemplate(JdbcTemplate template) { System.out.println(\"[UserDao] Created\"); mTemplate = template; } public int save(User user) { System.out.println(\"[UserDao] save(), email = \" + user.getEmail()); String sql = \"insert into User(email, password, number, age)\" + \"values('\" + user.getEmail() + \"','\" + user.getPassword() + \"','\" + user.getNumber() + \"', \" + user.getAge() + \")\"; return mTemplate.update(sql); } public int update(User user) { System.out.println(\"[UserDao] update(), email = \" + user.getEmail()); String sql = \"update User set password='\" + user.getPassword() + \"', number='\" + user.getNumber() + \"', age=\" + user.getAge() + \" where email='\" + user.getEmail() + \"'\"; return mTemplate.update(sql); } public int delete(String email) { System.out.println(\"[UserDao] delete(), email = \" + email); String sql = \"delete from User where email='\" + email + \"'\"; return mTemplate.update(sql); } public User getUserByEmail(String email) { System.out.println(\"[UserDao] getUserByEmail(), email = \" + email); String sql = \"select * from User where email=?\"; return mTemplate.queryForObject(sql, new Object[] {email}, new BeanPropertyRowMapper<User>(User.class)); } public List<User> getUsers() { System.out.println(\"[UserDao] getUsers()\"); return mTemplate.query(\"select * from User\", new RowMapper<User>() { public User mapRow(ResultSet rs, int row) throws SQLException { User user = new User(); user.setEmail(rs.getString(1)); user.setPassword(rs.getString(2)); user.setNumber(rs.getString(3)); user.setAge(rs.getInt(4)); return user; } }); }} Data를 다루기 위한 Controller 생성. src/main/java/com.lazyrodi.crud/UserController.java UserController.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960package com.lazyrodi.crud;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.servlet.ModelAndView;@Controllerpublic class UserController { @Autowired UserDao dao; @RequestMapping(\"/userform\") public ModelAndView showform() { System.out.println(\"[UserController] showform()\"); return new ModelAndView(\"userform\", \"command\", new User()); } @RequestMapping(value = \"/save\", method = RequestMethod.POST) public ModelAndView save(@ModelAttribute(\"user\") User user) { System.out.println(\"[UserController] save(), email = \" + user.getEmail()); dao.save(user); return new ModelAndView(\"redirect:/viewuser\"); } @RequestMapping(\"/viewuser\") public ModelAndView viewuser() { System.out.println(\"[UserController] viewuser()\"); List<User> userList = dao.getUsers(); return new ModelAndView(\"viewuser\", \"list\", userList); } @RequestMapping(value = \"/edituser/{email:.+}\") public ModelAndView edit(@PathVariable(\"email\") String email) { System.out.println(\"[UserController] edit(), email = \" + email); User user = dao.getUserByEmail(email); return new ModelAndView(\"usereditform\", \"command\", user); } @RequestMapping(value = \"/editsave\", method = RequestMethod.POST) public ModelAndView editsave(@ModelAttribute(\"user\") User user) { System.out.println(\"[UserController] editsave(), email = \" + user.getEmail()); dao.update(user); return new ModelAndView(\"redirect:/viewuser\"); } @RequestMapping(value = \"/deleteuser/{email:.+}\", method = RequestMethod.GET) public ModelAndView delete(@PathVariable(\"email\") String email) { System.out.println(\"[UserController] delete(), email = \" + email); dao.delete(email); return new ModelAndView(\"redirect:/viewuser\"); }} Data 추가를 위한 form 생성 /src/main/webapp/WEB-INF/views/userform.jsp userform.jsp123456789101112131415161718192021222324252627282930313233343536373839404142434445<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><%@ taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\"%> <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%><!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>Edit User</title></head><body><h1>Edit User</h1><form:form method=\"POST\" action=\"/crud/editsave\"><table><tr><td>Email</td><td><form:input path=\"email\" /></td></tr><tr><td>Password</td><td><form:input path=\"password\" /></td></tr><tr><td>Phone Number</td><td><form:input path=\"number\" /></td></tr><tr><td>Age</td><td><form:input path=\"age\" /></td></tr><tr><td></td><td><input type=\"submit\" value=\"Edit save\" /></td></tr></table></form:form></body></html> Data 수정을 위한 form 생성 /src/main/webapp/WEB-INF/views/usereditform.jsp usereditform.jsp123456789101112131415161718192021222324252627282930313233343536373839404142434445<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><%@ taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\"%> <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%><!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>Edit User</title></head><body><h1>Edit User</h1><form:form method=\"POST\" action=\"editsave\"><table><tr><td>Email</td><td><form:input path=\"email\" /></td></tr><tr><td>Password</td><td><form:input path=\"password\" /></td></tr><tr><td>Phone Number</td><td><form:input path=\"number\" /></td></tr><tr><td>Age</td><td><form:input path=\"age\" /></td></tr><tr><td></td><td><input type=\"submit\" value=\"Edit save\" /></td></tr></table></form:form></body></html> Data를 보여주기 위한 view page 생성 (+삭제) /src/main/webapp/WEB-INF/views/viewuser.jsp viewuser.jsp1234567891011121314151617181920212223242526272829303132333435363738<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><%@ taglib uri=\"http://www.springframework.org/tags/form\" prefix=\"form\"%> <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\"%> <!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>View User</title></head><body><h1>User List</h1><table><tr> <th>Email</th> <th>Password</th> <th>Phone Number</th> <th>Age</th> <th>Edit</th> <th>Delete</th></tr><c:forEach var=\"user\" items=\"${list}\"><tr> <td>${user.email}</td> <td>${user.password}</td> <td>${user.number}</td> <td>${user.age}</td> <td><a href=\"edituser/${user.email}\">edit</a></td> <td><a href=\"deleteuser/${user.email}\">delete</a></td></tr></c:forEach></table></body></html> servlet에 bean 추가 /src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml servlet-context.xml1234567891011121314151617181920212223242526272829303132333435363738394041<?xml version=\"1.0\" encoding=\"UTF-8\"?><beans:beans xmlns=\"http://www.springframework.org/schema/mvc\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:beans=\"http://www.springframework.org/schema/beans\" xmlns:context=\"http://www.springframework.org/schema/context\" xsi:schemaLocation=\"http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd\"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Enables the Spring MVC @Controller programming model --> <annotation-driven /> <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory --> <resources mapping=\"/resources/**\" location=\"/resources/\" /> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\"> <beans:property name=\"prefix\" value=\"/WEB-INF/views/\" /> <beans:property name=\"suffix\" value=\".jsp\" /> </beans:bean> <context:component-scan base-package=\"com.lazyrodi.crud\" /> <beans:bean id=\"ds\" class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\"> <beans:property name=\"driverClassName\" value=\"com.mysql.jdbc.Driver\" /> <beans:property name=\"url\" value=\"jdbc:mysql://localhost:3306/스키마이름?characterEncoding=EUCKR&amp;autoReconnect=true&amp;useSSL=false\" /> <beans:property name=\"username\" value=\"MySQL아이디\" /> <beans:property name=\"password\" value=\"비밀번호\" /> </beans:bean> <beans:bean id=\"jt\" class=\"org.springframework.jdbc.core.JdbcTemplate\"> <beans:property name=\"dataSource\" ref=\"ds\" /> </beans:bean> <beans:bean id=\"dao\" class=\"com.lazyrodi.crud.UserDao\"> <beans:property name=\"template\" ref=\"jt\" /> </beans:bean> </beans:beans>","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"crud","slug":"crud","permalink":"http://lazyrodi.github.io/tags/crud/"},{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"},{"name":"quick guide","slug":"quick-guide","permalink":"http://lazyrodi.github.io/tags/quick-guide/"}]},{"title":"[Spring] CRUD","slug":"2017-09-13-spring-crud","date":"2017-09-13T04:20:00.000Z","updated":"2017-09-18T14:07:40.851Z","comments":true,"path":"2017/09/13/2017-09-13-spring-crud/","link":"","permalink":"http://lazyrodi.github.io/2017/09/13/2017-09-13-spring-crud/","excerpt":"","text":"CRUD는 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 묶어서 일컫는 말이다.사용자 인터페이스가 갖추어야 할 기능(정보의 참조/검색/갱신)을 가리키는 용어로서도 사용된다. Wiki백과 Spring에서 CRUD를 구현하기 위해서는 여러가지 방법이 있는데 그 중 JDBC를 이용하여 MySQL과 연동하여 User에 대한 Data handling을 도전해보기로 했다. JDBC (Java Database Connectivity) 연결 User class 구현 User DAO (Data Access Object) 구현 User Controller 구현 View 역할의 JSP 구현 위의 순서대로 진행하면 된다. 이 페이지에 정리를 하다가 꼬이는 부분들이 있어서 각각 아래 페이지들에 다시 정리해 두었다. [Spring] Quick Guide Spring Troubleshooting 참조 https://ko.wikipedia.org/wiki/CRUD https://www.javatpoint.com/spring-mvc-crud-example http://javawebtutor.com/articles/spring/spring-mvc-hibernate-crud-example.php https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jdbc.html http://genesis8.tistory.com/214 http://javabycode.com/build-tools/maven/add-oracle-jdbc-driver-maven.html https://www.mkyong.com/spring/spring-jdbctemplate-querying-examples/","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"crud","slug":"crud","permalink":"http://lazyrodi.github.io/tags/crud/"},{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"}]},{"title":"[Data Structure] Binary Search Tree","slug":"2017-09-08-algorithm-data_structure-binary_search_tree","date":"2017-09-11T13:00:00.000Z","updated":"2017-09-12T07:22:39.839Z","comments":true,"path":"2017/09/11/2017-09-08-algorithm-data_structure-binary_search_tree/","link":"","permalink":"http://lazyrodi.github.io/2017/09/11/2017-09-08-algorithm-data_structure-binary_search_tree/","excerpt":"","text":"이진 탐색 Tree (Binary Search Tree)이진 탐색 Tree는 데이터의 삽입, 삭제, 탐색 등이 자주 발생하는 경우에 효율적인 구조이다. 같은 값을 갖는 Node가 없어야 한다. 왼쪽 Sub Tree에 있는 모든 데이터는 현재 Node의 값보다 작고, 오른쪽 Sub Tree에 있는 모든 Node의 데이터는 현재 Node의 값보다 크다. 데이터의 탐색Root에서부터 탐색이 시작되며 Root node의 데이터가 찾으려는 데이터보다 작으면 오른쪽 Sub Tree를 탐색하고 찾으려는 데이터보다 크면 왼쪽 Subtree를 탐색한다. 데이터 삽입Binary Search Tree에서의 데이터 삽입은 탐색을 통해 이루어지며, 탐색에 성공하면 데이터 삽입에 실패한다. (Binary tree는 같은 데이터를 갖는 Node가 없어야 하기 때문) 탐색에 실패할 경우 데이터를 삽입하며, 아래와 같은 과정을 거친다. 삽입하려는 데이터가 Root node의 데이터보다 작으면 왼쪽 Subtree로. 왼쪽 Subtree의 Root node보다 크면 오른쪽 Subtree로. 만약에 단말 Node를 만난다면 더 이상 탐색은 진행하지 않는다. 단말 Node보다 작으면 단말 Node의 왼쪽 자식 Node로, 단말 Node보다 크면 단말 Node의 오른쪽 자식 Node로 새 Node를 삽입한다. 데이터 삭제데이터 삭제는 삭제할 Node의 위치에 따라 세 가지 케이스로 구분된다. 삭제할 Node가 단말 Node일 경우부모 Node에서 삭제할 Node를 가리키는 링크를 제거하면 된다. 삭제할 Node의 자식 Node가 하나일 경우부모 Node가 삭제할 Node의 자식 Node를 가리키게 한 후 제거하면 된다. 삭제할 Node의 자식 Node가 두 개일 경우삭제할 Node를 왼쪽 Sub Tree에서 가장 큰 Node로 대체하거나 오른쪽 Sub Tree에서 가장 작은 Node로 대체한 후 원래 Node를 삭제한다. 이거 이해하는데 시간이 걸렸다. 멍청해 멍청해… 구현Node는 아래와 같이 꾸미고, 1234567891011121314151617181920212223242526272829303132333435public class Node { private int mData; private Node leftNode; private Node rightNode; Node(int data) { mData = data; } public Node getLeftNode() { return leftNode; } public Node getRightNode() { return rightNode; } public void setLeftNode(Node newNode) { leftNode = newNode; } public void setRightNode(Node newNode) { rightNode = newNode; } public int getData() { return mData; } public void setData(int data) { mData = data; }} BST (Binary Search Tree)는 아래와 같이 꾸민다. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102public class BinarySearchTree { private Node mRootNode; BinarySearchTree(Node newNode) { mRootNode = newNode; } public Node getRoot() { return mRootNode; } public void insertNode(Node newNode) { Node offset = mRootNode; // 반복적 탐색 알고리즘을 이용 while (offset != null) { if (offset.getData() == newNode.getData()) { System.out.println(\"insert fail because same data. BST regulation violation.\"); return; } if (newNode.getData() < offset.getData()) { if (offset.getLeftNode() == null) { offset.setLeftNode(newNode); return; } else { offset = offset.getLeftNode(); } } else { if (offset.getRightNode() == null) { offset.setRightNode(newNode); return; } else { offset = offset.getRightNode(); } } } } // 순환적 탐색 알고리즘을 이용 public Node searchNode(Node curNode, int data) { if (curNode == null) { return null; } if (curNode.getData() == data) { return curNode; } if (curNode.getLeftNode() == null && curNode.getRightNode() == null) { System.out.println(\"search fail. BST is not contained this element.\"); return null; } if (data < curNode.getData()) { return searchNode(curNode.getLeftNode(), data); } // data > curNode.getData() return searchNode(curNode.getRightNode(), data); } public void deleteNode(int data) { deleteTarget(mRootNode, data); } public Node deleteTarget(Node curNode, int data) { if (curNode == null) { return null; } if (data < curNode.getData()) { curNode.setLeftNode(deleteTarget(curNode.getLeftNode(), data)); } else if (data > curNode.getData()) { curNode.setRightNode(deleteTarget(curNode.getRightNode(), data)); } else { // found!! // parent has no or one node. if (curNode.getLeftNode() == null) { return curNode.getRightNode(); } else if (curNode.getRightNode() == null) { return curNode.getLeftNode(); } // parent has two nodes. find smallest nodes in right tree. Node temp = curNode.getRightNode(); while (temp != null) { if (temp.getLeftNode() != null) { temp = temp.getLeftNode(); continue; } curNode.setData(temp.getData()); temp = null; } curNode.setRightNode(deleteTarget(curNode.getRightNode(), data)); } return curNode; }} Main에서 Tree 생성 후 실행해보면 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public class Main { public static void main(String[] args) { BinarySearchTree bst = new BinarySearchTree(new Node(20)); System.out.println(\"making tree...\"); bst.insertNode(new Node(10)); bst.insertNode(new Node(4)); bst.insertNode(new Node(15)); bst.insertNode(new Node(2)); bst.insertNode(new Node(7)); bst.insertNode(new Node(5)); bst.insertNode(new Node(15)); bst.insertNode(new Node(12)); bst.insertNode(new Node(18)); bst.insertNode(new Node(30)); bst.insertNode(new Node(25)); bst.insertNode(new Node(22)); bst.insertNode(new Node(23)); bst.insertNode(new Node(27)); bst.insertNode(new Node(26)); bst.insertNode(new Node(36)); bst.insertNode(new Node(33)); bst.insertNode(new Node(34)); bst.insertNode(new Node(50)); bst.insertNode(new Node(60)); System.out.println(\"print some nodes...\"); System.out.println(bst.getRoot().getData()); System.out.println(bst.getRoot().getLeftNode().getData()); System.out.println(bst.getRoot().getLeftNode().getLeftNode().getData()); System.out.println(bst.getRoot().getLeftNode().getLeftNode().getLeftNode().getData()); System.out.println(bst.getRoot().getLeftNode().getLeftNode().getRightNode().getData()); System.out.println(\"delete node data \\'4\\'\"); bst.deleteNode(4); try { System.out.println(\"print some nodes...\"); System.out.println(bst.getRoot().getData()); System.out.println(bst.getRoot().getLeftNode().getData()); System.out.println(bst.getRoot().getLeftNode().getLeftNode().getData()); System.out.println(bst.getRoot().getLeftNode().getLeftNode().getLeftNode().getData()); System.out.println(bst.getRoot().getLeftNode().getLeftNode().getRightNode().getData()); System.out.println(\"search some datas...\"); System.out.println(bst.searchNode(bst.getRoot(), 2).getData()); System.out.println(bst.searchNode(bst.getRoot(), 30).getData()); System.out.println(bst.searchNode(bst.getRoot(), 25).getData()); System.out.println(bst.searchNode(bst.getRoot(), 11).getData()); System.out.println(bst.searchNode(bst.getRoot(), 9).getData()); } catch (NullPointerException e) { System.out.println(\"null\"); } }} 각각 아래와 같은 결과를 얻을 수 있다. making tree...insert fail because same data. BST regulation violation.print some nodes...2010427delete node data '4'print some nodes...2010527search some datas...23025search fail. BST is not contained this element.null 참조 http://terms.naver.com/entry.nhn?docId=2270430&cid=51173&categoryId=51173&expCategoryId=51173 http://www.algolist.net/Data_structures/Binary_search_tree/Removal https://helloacm.com/how-to-delete-a-node-from-a-binary-search-tree/ http://www.geeksforgeeks.org/binary-search-tree-set-2-delete/","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Algorithm","slug":"Dev/Algorithm","permalink":"http://lazyrodi.github.io/categories/Dev/Algorithm/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"datastructure","slug":"datastructure","permalink":"http://lazyrodi.github.io/tags/datastructure/"},{"name":"tree","slug":"tree","permalink":"http://lazyrodi.github.io/tags/tree/"},{"name":"binarysearchtree","slug":"binarysearchtree","permalink":"http://lazyrodi.github.io/tags/binarysearchtree/"}]},{"title":"[Data Structure] Binary tree","slug":"2017-09-07-algorithm-data_structure-binary_tree","date":"2017-09-07T12:20:00.000Z","updated":"2017-09-07T14:46:09.058Z","comments":true,"path":"2017/09/07/2017-09-07-algorithm-data_structure-binary_tree/","link":"","permalink":"http://lazyrodi.github.io/2017/09/07/2017-09-07-algorithm-data_structure-binary_tree/","excerpt":"","text":"이진 트리 (Binary tree)이진 트리는 모든 Node들의 자식 Node가 두 개 이하인 트리를 의미한다. 서브 트리를 왼쪽 서브 트리와 오른쪽 서브 트리로 구분한다. 단말 Node를 제외한 나머지 Node가 두 개의 자식 Node를 가지고 있는 트리를 완전 이진 트리(complete binary tree)라고 한다. 트리의 마지막 level까지 모든 Node가 채워진 이진 트리를 포화 이진 트리(full binary tree)라고 한다. 이진 트리는 Array 또는 Linked list로 구현할 수 있다. 하나의 Node가 left child와 right child 두 개의 pointer를 가진다. 이진 트리의 순회 (traversal)이진 트리의 순회란 이진 트리의 모든 노드를 특정한 순서대로 한 번씩 방문하는 것이다. 순회하는 방법은 아래와 같이 세 가지가 있다. preorder (전위) inorder (중위) postorder (후위) preorder방문 -> 왼쪽 서브 트리 방문 -> 오른쪽 서브 트리 방문```1234### inorder```왼쪽 서브 트리 방문 -> 노드 방문 -> 오른쪽 서브 트리 방문 postorder왼쪽 서브 트리 방문 -> 오른쪽 서브 트리 방문 -> 노드 방문 아래와 같이 Node를 구성하고, 12345678910111213141516171819202122232425262728293031public class Node { private char mData; private Node leftNode; private Node rightNode; Node(char data) { mData = data; } public Node getLeftNode() { return leftNode; } public Node getRightNode() { return rightNode; } public void setLeftNode(Node newNode) { leftNode = newNode; } public void setRightNode(Node newNode) { rightNode = newNode; } public char getData() { return mData; }} Binary Tree를 이렇게 만든다. preorder, inorder, postorder는 결국 어느 부분에서 확인하느냐에 따라 다르다. 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class BinaryTree { private Node mRoot; BinaryTree(Node data) { mRoot = data; } public Node getRoot() { return mRoot; } public void preOrder(Node root) { System.out.print(root.getData() + \" \"); if (root.getLeftNode() != null) { preOrder(root.getLeftNode()); } if (root.getRightNode() !=null) { preOrder(root.getRightNode()); } } public void inOrder(Node root) { if (root.getLeftNode() != null) { inOrder(root.getLeftNode()); } System.out.print(root.getData() + \" \"); if (root.getRightNode() != null) { inOrder(root.getRightNode()); } } public void postOrder(Node root) { if (root.getLeftNode() != null) { postOrder(root.getLeftNode()); } if (root.getRightNode() != null) { postOrder(root.getRightNode()); } System.out.print(root.getData() + \" \"); }} Main에서 BinaryTree를 구성하고 실행해보면 1234567891011121314151617181920212223242526public class Main { public static void main(String[] args) { BinaryTree bTree = new BinaryTree(new Node('A')); setTree(bTree); bTree.preOrder(bTree.getRoot()); System.out.println(); bTree.inOrder(bTree.getRoot()); System.out.println(); bTree.postOrder(bTree.getRoot()); } public static void setTree(BinaryTree bTree) { bTree.getRoot().setLeftNode(new Node('B')); bTree.getRoot().setRightNode(new Node('C')); bTree.getRoot().getLeftNode().setLeftNode(new Node('D')); bTree.getRoot().getLeftNode().setRightNode(new Node('E')); bTree.getRoot().getLeftNode().getRightNode().setLeftNode(new Node('F')); }} 아래와 같은 결과를 얻을 수 있다. A B D E F C D B F E A C D F E B C A 참조 http://terms.naver.com/entry.nhn?docId=2270429&cid=51173&categoryId=51173&expCategoryId=51173","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Algorithm","slug":"Dev/Algorithm","permalink":"http://lazyrodi.github.io/categories/Dev/Algorithm/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"datastructure","slug":"datastructure","permalink":"http://lazyrodi.github.io/tags/datastructure/"},{"name":"tree","slug":"tree","permalink":"http://lazyrodi.github.io/tags/tree/"},{"name":"binarytree","slug":"binarytree","permalink":"http://lazyrodi.github.io/tags/binarytree/"}]},{"title":"[Data Structure] Queue","slug":"2017-09-06-algorithm-data_structure-queue","date":"2017-09-06T12:20:00.000Z","updated":"2017-09-06T13:17:05.754Z","comments":true,"path":"2017/09/06/2017-09-06-algorithm-data_structure-queue/","link":"","permalink":"http://lazyrodi.github.io/2017/09/06/2017-09-06-algorithm-data_structure-queue/","excerpt":"","text":"Queue는 각 Data들을 순차적으로 넣었다가 순차적으로 빼내는 FIFO(First In First Out) 구조이다. 기본 연산으로 push로 rear에 데이터를 추가하고 pop으로 front의 데이터를 가져오는 방식을 가진다. 배열(Array)로 구현한 Queue123456789101112131415161718192021222324252627282930public class ArrayQueue { private int[] aQueue; private int mSize; private int mFront = 0; private int mRear = 0; ArrayQueue(int size) { mSize = size; aQueue = new int[mSize]; } public void push(int data) { if (mRear >= mSize) { System.out.println(\"Queue is full.\"); return; } aQueue[mRear++] = data; } public int pop() { if (mFront >= mSize) { System.out.println(\"Queue is empty.\"); return -1; } return aQueue[mFront++]; }} Main에서 아래와 같이 확인해보면 12345678910111213141516171819202122232425public class Main { public static void main(String[] args) { ArrayQueue aQueue = new ArrayQueue(5); aQueue.push(1); aQueue.push(2); aQueue.push(3); aQueue.push(4); aQueue.push(5); aQueue.push(6); aQueue.push(7); System.out.println(aQueue.pop()); System.out.println(aQueue.pop()); System.out.println(aQueue.pop()); System.out.println(aQueue.pop()); System.out.println(aQueue.pop()); System.out.println(aQueue.pop()); System.out.println(aQueue.pop()); }} 아래와 같은 결과를 얻을 수 있다. Queue is full.Queue is full.12345Queue is empty.-1Queue is empty.-1 Linked List로 구현한 QueueArray로 구현한 Queue는 크기(size)에 한계가 있으므로 Linked list로 구현한다. [Data Structure] Linked-list 에서 구현했던 SinglyLinkedList 를 상속하여 아래와 같이 구현한다.12345678910111213141516171819202122public class LinkedListQueue extends SinglyLinkedList { LinkedListQueue(Node newNode) { super(newNode); } public void push(Node newNode) { super.addNode(newNode); } public Node pop() { if (mHead == null) { return null; } Node temp = mHead; mHead = mHead.getNext(); return temp; }} Main에서 아래와 같이 확인해보면 12345678910111213141516171819202122232425262728293031323334public class Main { public static void main(String[] args) { LinkedListQueue llq = new LinkedListQueue(new Node(1)); llq.push(new Node(2)); llq.push(new Node(3)); llq.push(new Node(4)); llq.push(new Node(5)); llq.push(new Node(6)); llq.push(new Node(7)); try { System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); llq.push(new Node(10)); llq.push(new Node(11)); llq.push(new Node(12)); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); System.out.println(llq.pop().getData()); } catch (NullPointerException e) { System.out.println(\"Queue is empty.\"); } }} 아래와 같은 결과를 얻을 수 있다. 1234567101112Queue is empty. 참조 http://terms.naver.com/entry.nhn?docId=2270426&cid=51173&categoryId=51173&expCategoryId=51173 http://terms.naver.com/entry.nhn?docId=2270427&cid=51173&categoryId=51173&expCategoryId=51173","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Algorithm","slug":"Dev/Algorithm","permalink":"http://lazyrodi.github.io/categories/Dev/Algorithm/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"queue","slug":"queue","permalink":"http://lazyrodi.github.io/tags/queue/"},{"name":"datastructure","slug":"datastructure","permalink":"http://lazyrodi.github.io/tags/datastructure/"}]},{"title":"[Data Structure] Stack","slug":"2017-09-05-algorithm-data_structure-stack","date":"2017-09-05T12:20:00.000Z","updated":"2017-09-05T13:55:07.557Z","comments":true,"path":"2017/09/05/2017-09-05-algorithm-data_structure-stack/","link":"","permalink":"http://lazyrodi.github.io/2017/09/05/2017-09-05-algorithm-data_structure-stack/","excerpt":"","text":"Stack은 각 Data들을 순차적으로 넣었다가 역순으로 빼내는 LIFO(Last In First Out) 또는 FILO(First In Last Out) 구조이다. 기본 연산으로 push로 top에 데이터를 추가하고 pop으로 top의 데이터를 가져오는 방식을 가진다. Stack은 제한된 용량을 가지도록 구현되기 때문에 Stack이 가득 찼을 때 push하는 경우 Overflow가 발생한다. 또한, Stack이 비었을 때 pop을 시도하는 경우 Underflow가 발생한다. 배열(Array)로 구현한 Stack12345678910111213141516171819202122232425262728293031323334353637public class ArrayStack { private final int mSize = 5; private int[] mData; private int mTop; ArrayStack() { mData = new int[mSize]; mTop = -1; } public void push(int data) { mTop++; if (mTop == mSize) { System.out.println(\"Overflow\"); mTop--; return; } mData[mTop] = data; } public int pop() { if (mTop == -1) { System.out.println(\"Underflow\"); return -1; } int ret = mData[mTop]; mData[mTop] = 0; mTop--; return ret; }} Main에서 아래와 같이 확인해보면 12345678910111213141516171819202122232425262728293031public class Main { public static void main(String[] args) { ArrayStack aStack = new ArrayStack(); aStack.push(1); aStack.push(2); aStack.push(3); aStack.push(4); aStack.push(5); aStack.push(6); aStack.push(7); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); aStack.push(10); aStack.push(11); aStack.push(12); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); System.out.println(aStack.pop()); }} 아래와 같은 결과를 얻을 수 있다. OverflowOverflow54321Underflow-1Underflow-1121110Underflow-1 Linked List로 구현한 Stack[Data Structure] Linked-list 에서 구현했던 SinglyLinkedList 를 상속하여 아래와 같이 구현한다.어찌보면 상속 자체가 오버일 수 있지만… 이렇게 해본다.1234567891011121314151617181920212223public class LinkedListStack extends SinglyLinkedList { LinkedListStack(Node newNode) { super(newNode); } public void push(Node newNode) { newNode.setNext(mHead); mHead = newNode; } public Node pop() { if (mHead == null) { return null; // Underflow } Node temp = mHead; mHead = mHead.getNext(); return temp; } } Main에서 아래와 같이 확인해보면 12345678910111213141516171819202122232425public class Main { public static void main(String[] args) { LinkedListStack lls = new LinkedListStack(new Node(1)); lls.push(new Node(2)); lls.push(new Node(3)); lls.push(new Node(4)); lls.push(new Node(5)); lls.printNodes(); try { System.out.println(lls.pop().getData()); System.out.println(lls.pop().getData()); System.out.println(lls.pop().getData()); System.out.println(lls.pop().getData()); System.out.println(lls.pop().getData()); System.out.println(lls.pop().getData()); System.out.println(lls.pop().getData()); } catch (NullPointerException e) { System.out.println(\"Underflow\"); } }} 아래와 같은 결과를 얻을 수 있다. 5 4 3 2 1 54321Underflow 참조 http://terms.naver.com/entry.nhn?docId=2270423&cid=51173&categoryId=51173&expCategoryId=51173 http://terms.naver.com/entry.nhn?docId=2270424&cid=51173&categoryId=51173&expCategoryId=51173 http://terms.naver.com/entry.nhn?docId=2837556&cid=40942&categoryId=32841","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Algorithm","slug":"Dev/Algorithm","permalink":"http://lazyrodi.github.io/categories/Dev/Algorithm/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"datastructure","slug":"datastructure","permalink":"http://lazyrodi.github.io/tags/datastructure/"},{"name":"stack","slug":"stack","permalink":"http://lazyrodi.github.io/tags/stack/"}]},{"title":"[Data Structure] Linked-list","slug":"2017-09-04-algorithm-data_structure-linkedlist","date":"2017-09-04T12:20:00.000Z","updated":"2017-09-05T13:42:29.390Z","comments":true,"path":"2017/09/04/2017-09-04-algorithm-data_structure-linkedlist/","link":"","permalink":"http://lazyrodi.github.io/2017/09/04/2017-09-04-algorithm-data_structure-linkedlist/","excerpt":"","text":"Linked List는 각 Data들을 Pointer로 연결하여 관리하는 구조이다. 첫 번째 Node인 Head pointer가 다음 Node를 가리키고 그 Node는 다음 Node를 가리킨다. 맨 마지막 Node에는 더 이상 다음 Node를 가리키는 Pointer가 없는데 이를 Tail Node라 한다. 단순 Linked list (Singly Linked List) 단순히 Head부터 Tail까지 이어져 있는 구조. 우선 Node를 아래와 같이 구성하고. 12345678910111213141516171819202122232425262728293031323334public class Node { Node mNext; Node mPrev; int mData; Node(int data) { mData = data; } public void setPrevious(Node prev) { mPrev = prev; } public void setNext(Node next) { mNext = next; } public void setData(int data) { mData = data; } public Node getPrev() { return mPrev; } public Node getNext() { return mNext; } public int getData() { return mData; }} 단일 Linked List는 아래와 같이 구성한다. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748public class SinglyLinkedList { Node mHead; Node mTail; SinglyLinkedList(Node newNode) { mHead = newNode; mTail = newNode; } public void addNode(Node newNode) { mTail.setNext(newNode); mTail = newNode; } public void insertNode(Node newNode, int offset) { Node temp = mHead; for (int i = 1; i < offset; i++) { temp = temp.getNext(); } newNode.setNext(temp.getNext()); temp.setNext(newNode); } public void deleteNode(int offset) { Node temp = mHead; Node delNode; for (int i = 1; i < offset - 1; i++) { temp = temp.getNext(); } delNode = temp.getNext(); temp.setNext(delNode.getNext()); delNode = null; } public void printNodes() { Node temp = mHead;; while (temp != null) { System.out.print(temp.getData() + \" \"); temp = temp.getNext(); } System.out.println(); }} Main에서 아래와 같이 확인해보면 1234567891011121314151617public class Main { public static void main(String[] args) { SinglyLinkedList sll = new SinglyLinkedList(new Node(1)); sll.addNode(new Node(2)); sll.addNode(new Node(3)); sll.addNode(new Node(4)); sll.addNode(new Node(5)); sll.printNodes(); sll.insertNode(new Node(8), 3); sll.printNodes(); sll.deleteNode(2); sll.printNodes(); }} 아래와 같은 결과를 얻을 수 있다. 1 2 3 4 5 1 2 3 8 4 5 1 3 8 4 5 원형 Linked list (Circular Linked List) Tail이 Head를 가리키는 원형 구조. Node는 위에 구성했던 것을 재사용한다. Circular Linked List는 아래와 같이 구성한다. 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364public class CircularLinkedList { Node mHead; Node mTail; int mCount = 0; CircularLinkedList(Node newNode) { mHead = newNode; mTail = newNode; mHead.setNext(mTail); mTail.setNext(mHead); mCount++; } public void addNode(Node newNode) { mTail.setNext(newNode); mTail = newNode; mTail.setNext(mHead); mCount++; } public void insertNode(Node newNode, int offset) { Node temp = mHead; for (int i = 1; i < offset; i++) { temp = temp.getNext(); } newNode.setNext(temp.getNext()); temp.setNext(newNode); mCount++; } public void deleteNode(int offset) { Node temp = mHead; Node delNode; for (int i = 1; i < offset - 1; i++) { temp = temp.getNext(); } delNode = temp.getNext(); temp.setNext(delNode.getNext()); delNode = null; mCount--; } /* * cycle : circular linked list를 몇 번 반복하여 출력할지 나타냄 */ public void printNodes(int cycle) { Node temp = mHead;; for (int i = 0; i < mCount * cycle; i++) { System.out.print(temp.getData() + \" \"); temp = temp.getNext(); } System.out.println(); }} Main을 아래와 같이 구성할 경우, 결과는 아래와 같다. 1234567891011121314151617181920public class Main { public static void main(String[] args) { CircularLinkedList cll = new CircularLinkedList(new Node(1)); cll.addNode(new Node(2)); cll.addNode(new Node(3)); cll.addNode(new Node(4)); cll.addNode(new Node(5)); cll.printNodes(1); cll.printNodes(2); cll.insertNode(new Node(8), 3); cll.printNodes(1); cll.printNodes(2); cll.deleteNode(2); cll.printNodes(1); cll.printNodes(2); }} 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 8 4 5 1 2 3 8 4 5 1 2 3 8 4 5 1 3 8 4 5 1 3 8 4 5 1 3 8 4 5 이중 Linked list (Doubly Linked List) 앞 Node와 뒷 Node가 서로 바라보는 구조. 위에서 봤던 Linked list들과는 다르게 Previous Node도 설정한다. 역방향으로 출력하기 위해 print문을 하나 추가한다. 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485public class DoublyLinkedList { Node mHead; Node mTail; int mCount = 0; DoublyLinkedList(Node newNode) { mHead = newNode; mTail = newNode; mHead.setNext(mTail); mTail.setPrevious(mHead); // for circular mHead.setPrevious(mTail); mTail.setNext(mHead); mCount++; } public void addNode(Node newNode) { mTail.setNext(newNode); newNode.setPrevious(mTail); mTail = newNode; // for circular newNode.setNext(mHead); mHead.setPrevious(newNode); mCount++; } public void insertNode(Node newNode, int offset) { Node temp = mHead; for (int i = 1; i < offset; i++) { temp = temp.getNext(); } newNode.setNext(temp.getNext()); newNode.setPrevious(temp); temp.getNext().setPrevious(newNode); temp.setNext(newNode); mCount++; } public void deleteNode(int offset) { Node temp = mHead; Node delNode; for (int i = 1; i < offset - 1; i++) { temp = temp.getNext(); } delNode = temp.getNext(); temp.setNext(delNode.getNext()); delNode.getNext().setPrevious(temp); delNode = null; mCount--; } public void printNodesFront() { Node temp = mHead; for (int i = 0; i < mCount; i++) { System.out.print(temp.getData() + \" \"); temp = temp.getNext(); } System.out.println(); } public void printNodesBack() { Node temp = mHead; for (int i = 0; i < mCount; i++) { System.out.print(temp.getData() + \" \"); temp = temp.getPrev(); } System.out.println(); }} Main은 아래와 같이 구성한다. 1234567891011121314151617181920public class Main { public static void main(String[] args) { DoublyLinkedList dll = new DoublyLinkedList(new Node(1)); dll.addNode(new Node(2)); dll.addNode(new Node(3)); dll.addNode(new Node(4)); dll.addNode(new Node(5)); dll.printNodesFront(); dll.printNodesBack(); dll.insertNode(new Node(8), 3); dll.printNodesFront(); dll.printNodesBack(); dll.deleteNode(2); dll.printNodesFront(); dll.printNodesBack(); }} 결과는 아래와 같다. 1 2 3 4 5 1 5 4 3 2 1 2 3 8 4 5 1 5 4 8 3 2 1 3 8 4 5 1 5 4 8 3 참조 http://terms.naver.com/entry.nhn?docId=2270421&cid=51173&categoryId=51173&expCategoryId=51173","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Algorithm","slug":"Dev/Algorithm","permalink":"http://lazyrodi.github.io/categories/Dev/Algorithm/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"datastructure","slug":"datastructure","permalink":"http://lazyrodi.github.io/tags/datastructure/"},{"name":"linkedlist","slug":"linkedlist","permalink":"http://lazyrodi.github.io/tags/linkedlist/"}]},{"title":"[Spring] 기본 프로젝트 파일 구조","slug":"2017-09-03-spring-structure","date":"2017-09-03T02:20:00.000Z","updated":"2017-09-13T02:06:37.732Z","comments":true,"path":"2017/09/03/2017-09-03-spring-structure/","link":"","permalink":"http://lazyrodi.github.io/2017/09/03/2017-09-03-spring-structure/","excerpt":"","text":"STS로 새 프로젝트를 생성하면 아래와 같은 기본 구조를 확인할 수 있다. Maven의 기본 디렉토리 설정인 것 같다. 개발자마다 설정 및 사용 용도가 조금씩 다른듯 하다. (https://slipp.net/questions/11) src/main/java Java source 디렉토리 Java 파일들은 모두 이 디렉토리에 생성한다. HomeController.java Web client로부터의 요청을 해당 비즈니스 로직으로 분기 및 수행 결과를 응답하는 역할. 12345678910111213141516 // @RequestMapping이 jsp로부터 들어온 요청에 해당하는 비즈니스 로직을 찾아주는 역할@RequestMapping(value = \"/\", method = RequestMethod.GET)public String home(Locale locale, Model model) { logger.info(\"Welcome home! The client locale is {}.\", locale); Date date = new Date(); DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); String formattedDate = dateFormat.format(date); // formattedDate 를 \"serverTime\"이라는 이름으로 알려준다. model.addAttribute(\"serverTime\", formattedDate ); // 수행 결과의 응답을 home.jsp 로 보낸다는 의미 return \"home\";} src/main/resources Resources들(설정 파일, img, js, css 등)이 저장되는 디렉토리. [추측] src/main/webapp/resources 에 저장한 static resource들을 빌드 시 이쪽으로 copy하는 것으로 보이며 이쪽으로 copy된 resource들은 classpath를 통해 접근할 수 있다고 한다. log4j.xml Logging 설정 파일 Maven Dependencies Maven을 사용하기 위한 라이브러리들 Apache Tomcat v7.0 [Apache Tomcat v7.0] Tomcat 사용을 위한 라이브러리들 JRE System Library Java를 사용하기 위한 Java Runtime Environment 관련 라이브러리들 src/main/webapp Maven의 기본 폴더. 이 아래 모든 jsp 및 js 파일이 포함된다. resources Web service에 사용할 static resource들을 저장하는 디렉토리. WEB-INFclassesspring root-context.xml appServlet Servlet 설정 파일 servlet-context.xml views JSP 디렉토리. 실제 page들이 여기에 위치한다고 보면 된다. home.jsp 1234567891011121314151617<%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %><%@ page session=\"false\" %><%@ page pageEncoding=\"UTF-8\" contentType=\"text/html; charset=UTF-8\"%> <html><head> <title>Home</title></head><body><h1> Hello world! </h1><!-- HomeController.java에서 넘겨준 \"serverTime\"을 여기에서 출력한다. --><P> The time on the server is ${serverTime}. </P></body></html> web.xml 서블릿 배포 기술자 (설정 파일) WAS (Web Application Server) 즉, TOMCAT이 구동될 때, WEB-INF 디렉토리에 존재하는 web.xml를 읽어와서 Web application 설정을 구성한다. target 빌드 결과가 출력되는 디렉토리 pom.xml Project Object Model 로 Maven project와 관련된 설정들이 저장되는 파일이다. 프로젝트 기본 정보 빌드 설정 프로젝트 관계 설정 빌드 환경 Property 관리 참조 http://addio3305.tistory.com/37 http://holy1017.tistory.com/32 http://syaku.tistory.com/342 http://dimdim.tistory.com/entry/Maven-%EC%A0%95%EB%A6%AC","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"},{"name":"structure","slug":"structure","permalink":"http://lazyrodi.github.io/tags/structure/"}]},{"title":"[Algorithm] Quick sort","slug":"2017-09-02-algorithm-quicksort","date":"2017-09-02T01:20:00.000Z","updated":"2017-09-02T11:13:09.252Z","comments":true,"path":"2017/09/02/2017-09-02-algorithm-quicksort/","link":"","permalink":"http://lazyrodi.github.io/2017/09/02/2017-09-02-algorithm-quicksort/","excerpt":"","text":"한 평생 Bubble sort만 사용하다가 이대로 계속 무식하면 안되겠다 싶어서 찾아보았다. Quick sort는 Charles Antony Richard Hoare 경에 의해 개발되었다. 알고리즘 List 중 하나의 Element를 선택한다. 이를 Pivot 이라 부른다. Pivot을 기준으로 좌측에는 작은 Element들이 오도록, 우측에는 큰 Element들이 오도록 나눈다. 이 과정을 분할 (Divide)이라 한다. 분할한 두 개의 좌, 우측 List에 대해 재귀적으로 이 과정을 반복한다. List의 크기가 0 또는 1이 될 때까지 반복한다. Psuedocode1234567891011121314151617181920void QuickSort (Element* list, const int left, const int right){ if (left < right) { int i = left; int j = right + 1; pivot = list[left].getKey(); do { do i++; while(list[i].getKey() < pivot); do j--; while(list[j].getKey() > pivot); if (i < j) { InterChange(list, i, j); } } while (i < j); InterChange(list, left, j); QuickSort(list, left, j - 1); QuickSort(list, j + 1, right); }} 위의 Psuedocode는 pivot 지점을 list의 좌측 즉, 시작점인 left로 설정한다. right는 우측 끝 값이며, j는 아래의 while문 처리를 위해 right + 1로 설정한다. 좌측 값이 pivot보다 작으면 무시하고 우측 값이 pivot보다 크면 무시하게끔 while문이 돌아간다. 좌측에서 발견한 pivot보다 큰 값과 우측에서 발견한 pivot보다 작은 값을 교환한다. 이를 i와 j의 위치가 역전될 때까지 반복한다. 이 때 j는 pivot보다 작은 값이 위치하게 되기 때문에 pivot과 자리를 바꾼다. ImplementationJava로 구현하면 아래와 같이 된다. Psuedocode대로 구현하면 제대로 동작하지 않아 조금 수정하였다. 다시 구현하라고 하면 못할 것 같다… 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class QuickSort { public static void main(String[] args) { int data[] = {66, 10, 34, -10, 200, 5, 2, 1, 1, 1, 5, 2, 3, -20, 5, 3, 65, -2}; aSort(data, 0, data.length - 1); } public static void aSort(int[] data, int left, int right) { if (left < right) { int fromLeft = left; // 좌측부터 우측으로 이동하는 offset int fromRight = right; // 우측부터 좌측으로 이동하는 offset int pivot = data[(left + right) / 2]; // 정렬의 기준이 되는 pivot. 여기서는 중앙값으로 정했다. while (fromLeft < fromRight) { while (data[fromLeft] < pivot) { fromLeft++; // pivot값의 좌측에 큰 값이 나올 때까지 이동 } while (data[fromRight] > pivot) { fromRight--; // pivot값의 우측이 작은 값이 나올 때까지 이동 } // 작은 값과 큰 값을 서로 교체. // 그 값들은 이제 제 자리를 찾았으니 다음 index로 넘어감 if (fromLeft < fromRight) { swap(data, fromLeft, fromRight); fromLeft++; fromRight--; // 같은 값을 가진다면 swap동작 안함. } else if (fromLeft == fromRight) { fromLeft++; fromRight--; } } // fromRight offset이 fromLeft offset보다 왼쪽에 가 있는 상태에서... if (left < fromRight) { // pivot값보다 작은 쪽 그룹에 대해 QuickSort aSort(data, left, fromRight); } if (fromLeft < right) { // pivot값보다 큰 쪽 그룹에 대해 QuickSort aSort(data, fromLeft, right); } } } public static void swap(int[]data, int left, int j) { int temp = data[left]; data[left] = data[j]; data[j] = temp; }} 참조 http://terms.naver.com/entry.nhn?docId=2270437&cid=51173&categoryId=51173 https://ko.wikipedia.org/wiki/%ED%80%B5_%EC%A0%95%EB%A0%AC","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Algorithm","slug":"Dev/Algorithm","permalink":"http://lazyrodi.github.io/categories/Dev/Algorithm/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"sort","slug":"sort","permalink":"http://lazyrodi.github.io/tags/sort/"}]},{"title":"[Spring] MySQL Workbench에서 DB 생성","slug":"2017-08-31-spring-mysql","date":"2017-08-31T12:30:00.000Z","updated":"2017-09-03T03:24:48.360Z","comments":true,"path":"2017/08/31/2017-08-31-spring-mysql/","link":"","permalink":"http://lazyrodi.github.io/2017/08/31/2017-08-31-spring-mysql/","excerpt":"","text":"Schema 생성Schema란 위키피디아에서 아래와 같이 정의하고 있다. 컴퓨터 과학에서 데이터베이스 스키마(database schema)는 데이터베이스에서 자료의 구조, 자료의 표현 방법, 자료 간의 관계를 형식 언어로 정의한 구조이다. 데이터베이스 관리 시스템(DBMS)이 주어진 설정에 따라 데이터베이스 스키마를 생성하며, 데이터베이스 사용자가 자료를 저장, 조회, 삭제, 변경할 때 DBMS는 자신이 생성한 데이터베이스 스키마를 참조하여 명령을 수행한다. 스키마는 3층 구조로 되어있다. 외부 스키마(External Schema) : 프로그래머나 사용자의 입장에서 데이터베이스의 모습으로 조직의 일부분을 정의한 것 개념 스키마(Conceptual Schema) : 모든 응용 시스템과 사용자들이 필요로하는 데이터를 통합한 조직 전체의 데이터베이스 구조를 논리적으로 정의한 것 내부 스키마(Internal Schema) : 전체 데이터베이스의 물리적 저장 형태를 기술하는 것 MySQL Workbench 실행 홈의 Local instance MySQL57 을 선택하여 root로 Login Server Status 를 눌러보고 Stop 상태라면 Startup/Shutdown 메뉴에서 Start server를 선택하여 Server 구동 상단의 좌측에서 네 번째 아이콘 create a new schema in the connected server을 클릭 마음에 드는 Schema name을 넣고 Collation은 utf8 - default collation을 선택 후 apply 사용자 생성!! 사용자 생성은 반드시 DB 생성 후 진행할 것 !! 좌측의 Users and Privileges 선택 Add Account Login Name 설정 Authentication type: standard Limit to Hosts Matching: % Apply Schema Privileges 탭 > Add Entry… > Selected schema에서 아까 생성한 schema를 선택 > OK 아래쪽의 Object rights를 모두 선택 (SELECT, INSERT, UPDATE, DELETE, EXECUTE, SHOW VIEW) !! 보통은 사용자가 DB를 날려먹을 위험이 있으므로 DELETE는 주지 않는다고 한다. !! Limit to Hosts Matching의 의미 % : 모든 IP에서 이 계정으로 접속 가능 localhost : localhost에서만 접속 가능 Database와 계정 연결 MySQL WorkBench의 홈 화면으로 간 후 + 버튼을 누른다. Connection Name 설정 Username 넣기 Default Schema에 생성한 Schema명 넣기 Test Connection > “Succesfully made the MySQL connection” 성공 > OK > OK Table 생성MySQL Workbench를 사용하여 Table을 생성해본다. 툴바의 좌측에서 다섯번째 버튼 Create a new table in the active schema in connected server 를 누른다. Table Name 입력 Column Name 및 Datatype 추가 정보를 넣고 Apply를 누르면 그에 해당하는 query문이 생성되며, 여기서 Apply를 누르면 Table이 생성된다. 참 좋은 세상이다… 평소에 query문을 쓸 일이 없는 나로서는 굉장히 마음에 든다. 리눅스에 세팅하려면 결국 query를 날려야 하겠지만… PK: Primary KeyNN: Not NullUQ: Create/remove Unique KeyB : Binary (값을 Binary string으로 저장한다. sorting, comparing을 할 수 없음.)UN: Unsigned (원래의 범위가 -500~500 이라면 체크 후 0~1000 사용 가능)ZF: Zero-Filled (INT(5)의 경우 12=00012, 400=00400 이 된다.)AI: Auto IncrementG : Generated column (다른 column들의 조합에 의해 생성되는 값) 참조 http://diaryofgreen.tistory.com/132 http://aristatait.tistory.com/60 http://blog.naver.com/taehun3718/140156156867 https://m.blog.naver.com/PostView.nhn?blogId=93immm&logNo=220790625969&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F https://stackoverflow.com/questions/3663952/what-do-column-flags-mean-in-mysql-workbench","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"setting","slug":"setting","permalink":"http://lazyrodi.github.io/tags/setting/"},{"name":"mysql","slug":"mysql","permalink":"http://lazyrodi.github.io/tags/mysql/"},{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"}]},{"title":"[Spring] Setting","slug":"2017-08-27-spring-setting","date":"2017-08-27T02:30:00.000Z","updated":"2017-09-03T03:48:20.549Z","comments":true,"path":"2017/08/27/2017-08-27-spring-setting/","link":"","permalink":"http://lazyrodi.github.io/2017/08/27/2017-08-27-spring-setting/","excerpt":"","text":"좋지 않은 방법이지만, 지식이 전혀 없는 상태에서 인터넷 보고 해보기. STS 설치Eclipse 기반으로 만든 것으로 보임. spring.io 에서 다운로드 후 압축 풀면 끝. 프로젝트 생성 File > New > Dynamic web project WebContent 디렉토리 아래 index.html 파일 생성 후 body에 hello 넣기. Run무작정 Run 해보기. Choose an existing server 선택 후 실행. 브라우저에서 localhost 접속해보니 에러가 난다. localhost:8080 해보니 Pivotal tc Server가 뜬다. 일단 서버 구동은 잘 되고 있다. 좀 더 인터넷 검색해보니 그 아래 프로젝트 명을 입력해줘야 한다고 한다. http://localhost:8080/hello 여기서는 page 생성된 것을 확인. Tomcat여기저기 보니 spring은 보통 tomcat에 연동하는 것으로 보인다. 7.0 버전이 안정적이라는 것 같다. 다운로드 한다. 7.0.81 64bit windows zip 버전으로 받고 압축 풀고 STS에서 Window > Preferences > Server > Runtime Environments > Add > Apache > Apache Tomcat v7.0 > Next > Browse > 설치한 경로 찾아주고 Finish File > New > Other > Server > Apache > Tomcat v7.0 Server 다음, Server perspective에서 Pivotal tc Server에 연결되어 있던 hello project를 끊고 Tomcat에 Add를 통해 연결하려 하였으나, 아래와 같은 에러메시지 발생. "There are no resources that can be added or removed from the server." 새로 Project hello2를 생성해본다. Target runtime에서 Apache를 선택하니 Dynamic web module version이 3.0으로 바뀐다. Apache 7.0에서 3.1을 지원하지 않는 것인가 싶다. 아래 사이트에서 확인해보니 저게 아마 Servlet version인가 싶다. https://tomcat.apache.org/whichversion.html 실행 후, http://localhost:8080/hello2 에 가보니 잘 나온다. 다른 방법으로 해보기Project 생성 시 New Spring Legacy Project를 사용하여 만들어보자. Project name을 적당히 넣고 Templates에서 Spring MVC Project를 선택 후 Next. 패키지명에 적당히 com.my.hello를 넣고 완료. 실행해보면 세 개의 에러가 난다. 1Description Resource Path Location TypeArchive for required library: 'C:/Users/xxx/.m2/repository/org/springframework/spring-aop/3.1.1.RELEASE/spring-aop-3.1.1.RELEASE.jar' in project 'Hello' cannot be read or is not a valid ZIP file Hello Build path Build Path Problem2The project cannot be built until build path errors are resolved Hello Unknown Java Problem3The superclass \"javax.servlet.http.HttpServlet\" was not found on the Java Build Path home.jsp /Hello/src/main/webapp/WEB-INF/views line 1 JSP Problem 으음… pom.xml을 열고. 아래 3.1.1 을 4.3.10 으로 변경한다. <org.springframework-version>3.1.1.RELEASE</org.springframework-version> 다음은 Java를 1.8로 바꾼다. (PC 환경에 맞춰서…) 생성한 Project를 우클릭 > Properties Project Facets > Java를 1.8로 바꾼다. 여기까지의 조치로 세 개의 에러 중 두 개는 없어졌는데 마지막 것이 남아있다. 아래와 같이 설정하여 에러는 모두 없어졌으나 페이지는 아직 보이지 않는다. 생성한 Project를 우클릭 > Properties Project Facets > 우측의 Runtimes 탭 > Apache Tomcat v7.0 선택 > Apply and Close 한참 고생하다가 다른 사이트에서 MAVEN에 대한 언급이 있었으나 STS를 사용하면 그냥 지원하는 줄 알고 설치를 안 했었다. 그래서 MAVEN 문제인가? 싶어서 설치해 보았다. 여기에서 에서 MAVEN을 받아서 압축을 풀고, 경로를 환경 변수의 PATH에 추가해준다. 잘 설치되었는지 확인을 위해서는 cmd를 다시 열고 mvn -version을 입력해보면 된다. 하지만 이게 문제가 아니었다. 한참 헤매다가 Console쪽에서 Tomcat의 에러로 보이는 것들을 찾았다. 심각: Unable to process Jar entry [org/apache/taglibs/standard/tag/rt/sql/DateParamTag.class] from Jar [jar:file:/D:/2018/Dev/Workspace/Workspace_spring/.metadata/.plugins/org.eclipse.wst.server.core/tmp1/wtpwebapps/hello/WEB-INF/lib/jstl-1.2.jar!/] for annotations 이에 대해 아래의 글들을 찾았다. http://burucodegallery.blogspot.kr/2013/12/unable-to-process-jar-entry.html https://open.egovframe.go.kr/cop/bbs/selectBoardArticle.do?bbsId=BBSMSTR_000000000013&nttId=13261 jstl-1.2.jar를 검색해보니 https://mvnrepository.com/artifact/javax.servlet/jstl/1.2 를 찾았고 JSTL에 대해서는 이런 사이트를 찾았고 http://gangzzang.tistory.com/entry/JSP-JSTLJSP-Standard-Tag-Library-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%B6%94%EA%B0%80 그래서 이 경로의 파일을 바꿔치기 했다. C:\\Users\\사용자\\.m2\\repository\\javax\\servlet\\jstl\\1.2 이 에러는 해결되었다. 다음 에러를 보자. [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.jee.server:hello' did not find a matching property. 이건 https://blog.outsider.ne.kr/559 를 따라서 고쳤다. Servers perspective에서 Tomcat server 더블 클릭 후 Server Option의 Publish module contexts to separate XML files를 체크. Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListenerjava.lang.NoClassDefFoundError: org/apache/log4j/Level 이 문제도 아래 사이트를 참고하여 해결했다. pom.xml에 log4j-slf4j 에 대한 내용을 dependency에 추가하는 것이다. https://open.egovframe.go.kr/cop/bbs/selectBoardArticle.do?bbsId=BBSMSTR_000000000013&nttId=13450 여기까지 하고 프로젝트 재시작을 해보니 페이지가 뜬다. 남은 TOMCAT 에러 로그는 놔두기로 한다. 페이지는 잘 떴으나 이제 깨지는 문자가 있다. 나름 인코딩을 UTF-8로 다 맞췄는데 뭐가 문제일까 싶었다. 아래 사이트를 따라 web.xml에 아래 코드를 추가해보았다. http://hellogk.tistory.com/76 <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param></filter><filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping> 그래도 안 된다. 그 다음 servlet-context.xml 에 아래 코드를 추가해 보라는 글을 보고 따라해본다. <beans:bean class=\"org.springframework.web.servlet.view.InternalResourceViewResolver\"> <beans:property name=\"prefix\" value=\"/WEB-INF/views/\" /> <beans:property name=\"suffix\" value=\".jsp\" /> <beans:property name=\"contentType\" value=\"text/html; charset=UTF-8\"/></beans:bean> 이것도 안 된다. 다음은 home.jsp 파일에 아래 코드를 추가해본다. <%@ page pageEncoding=\"UTF-8\" contentType=\"text/html; charset=UTF-8\"%> 이건 된다. 한글이 나온다. jsp 파일에 직접 추가해주는 것 만으로 위의 web.xml, servlet-context.xml 에서의 삽질은 필요하지 않다. 참조 https://www.slideshare.net/ssusera97034/the-way-to-setting-the-spring-framework-for-web http://boxfoxs.tistory.com/334 http://all-record.tistory.com/156 http://myeonguni.tistory.com/1211 https://stackoverflow.com/questions/21499695/spring-mvc-http-status-404 https://stackoverflow.com/questions/25590027/spring-mvc-http-status-404-error","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Spring","slug":"Dev/Spring","permalink":"http://lazyrodi.github.io/categories/Dev/Spring/"}],"tags":[{"name":"setting","slug":"setting","permalink":"http://lazyrodi.github.io/tags/setting/"},{"name":"spring","slug":"spring","permalink":"http://lazyrodi.github.io/tags/spring/"},{"name":"STS","slug":"STS","permalink":"http://lazyrodi.github.io/tags/STS/"}]},{"title":"Windows 10 Bash Troubleshooting","slug":"2017-08-26-troubleshooting-windows10-bash","date":"2017-08-26T00:30:00.000Z","updated":"2017-09-18T14:17:13.796Z","comments":true,"path":"2017/08/26/2017-08-26-troubleshooting-windows10-bash/","link":"","permalink":"http://lazyrodi.github.io/2017/08/26/2017-08-26-troubleshooting-windows10-bash/","excerpt":"","text":"bash 접속 시도 시 0x80070005 에러 발생.구글링 해보니 여러가지 해결책이 있었고, 해결이 안 된 사람도 많아보였다. 일단 내 해결책은 temp 디렉토리를 삭제하는 것이었는데… 이유는 모르겠다. 아직 불안정한듯 보인다… C:\\Users\\사용자명\\AppData\\Local\\lxss\\temp apt-get update 시도 시 Hash Sum mismatch 에러 발생. /var/lib/apt/lists 디렉토리를 삭제한다. 12# sudo rm -rf /var/lib/apt/lists/*# sudo apt-get update 내 경우는 해결되지 않았다. 다음은 미러링 사이트의 변경 아래의 명령으로 /etc/apt/sources.list 파일의 archive.ubuntu.com 을 ftp.daum.net 으로 변경하는 것이다. 해결 완료! 1# sudo sed -i 's/archive.ubuntu.com/ftp.daum.net/g' /etc/apt/sources.list http://ccambo.blogspot.kr/2015/02/ubuntu-apt-get-update-hash-sum-mismatch.html screen 사용 시 Cannot make directory ‘/var/run/screen’: Permission denied 발생Windows 10 bash라서 발생한 건 아닌 것 같고 Ubuntu에서도 발생할 수 있는 것 같지만 일단 여기에 정리. 문제: bash에서 screen을 사용하려고 하였더니 아래의 에러 발생.해결: sudo /etc/init.d/screen-cleanup start 명령어 수행 시 해결됨. Cannot make directory '/var/run/screen': Permission denied","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"TroubleShooting","slug":"Dev/TroubleShooting","permalink":"http://lazyrodi.github.io/categories/Dev/TroubleShooting/"}],"tags":[{"name":"bash","slug":"bash","permalink":"http://lazyrodi.github.io/tags/bash/"},{"name":"windows 10","slug":"windows-10","permalink":"http://lazyrodi.github.io/tags/windows-10/"}]},{"title":"[Windows 10] Bash 설치","slug":"2017-08-20-etc-install-bash-in-windows10","date":"2017-08-20T00:30:00.000Z","updated":"2017-08-20T02:18:04.535Z","comments":true,"path":"2017/08/20/2017-08-20-etc-install-bash-in-windows10/","link":"","permalink":"http://lazyrodi.github.io/2017/08/20/2017-08-20-etc-install-bash-in-windows10/","excerpt":"","text":"Windows 10 bashWindows 10 에도 Bash 가 추가되어 설치해 보았다. 설치 방법은 여기저기 잘 나와있으니 여기을 참고하는 걸로~! uname -a 를 수행해 보면, 아래와 같은 정보를 확인할 수 있다. 1Linux DESKTOP-QLPR0RK 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 x86_64 x86_64 x86_64 GNU/Linux 설치 후 Bash 에서의 자신의 home 경로는 C:\\Users\\사용자명\\AppData\\Local\\lxss\\home\\사용자명\\ 이 된다. 자신의 다른 드라이브들은 /mnt/ 에 마운트 되어있다. Windows 상에서 직접 복사한 파일은 bash 상에서 보이지 않는다. 이유는 여기 나와있다. 이건 좀 귀찮다. samba 연결이 가능할지 모르겠다. hexo를 위한 설정123sudo apt-get install npmcurl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash# https://github.com/creationix/nvm#install-script 에서 최신버전 참조 터미널 종료 후 다시 진입 한 후, 12nvm install stablenpm install -g hexo-cli","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"bash","slug":"bash","permalink":"http://lazyrodi.github.io/tags/bash/"},{"name":"windows 10","slug":"windows-10","permalink":"http://lazyrodi.github.io/tags/windows-10/"}]},{"title":"[Electron] Hello world","slug":"2017-02-09-etc-install-electron","date":"2017-02-09T11:46:50.000Z","updated":"2017-02-09T15:09:23.475Z","comments":true,"path":"2017/02/09/2017-02-09-etc-install-electron/","link":"","permalink":"http://lazyrodi.github.io/2017/02/09/2017-02-09-etc-install-electron/","excerpt":"","text":"Electron은 뜨고있는 JavaScript 기반의 Cross Platform Desktop Application 제작 툴이다. 홈페이지에서 직접 다음과 같이 설명하고 있다. If you can build a website, you can build a desktop app. Electron is a framework for creating native applications with web technologies like JavaScript, HTML, and CSS. It takes care of the hard parts so you can focus on the core of your application. Downloadhttp://electron.atom.io/releases/에서 직접 Download가 가능하다. (하지만 나는 네트워크가 잘 안 맞는지 계속 실패해서 화가 치밀었다.) 중국 서버 (https://npm.taobao.org/mirrors/electron/) 는 좀 받을만하다. PC에 npm이 설치되어 있다면, 아래 명령어로 Download 받을 수도 있다. 12345# Install as a development dependencynpm install electron --save-dev# Install the `electron` command globally in your $PATHnpm install electron -g 한글로 된 튜토리얼 문서가 있으니 보시라. 설치Windows 기준으로 설치는 압축만 풀면 된다. 압축을 풀면 electron.exe 파일이 생성되고, 아래처럼 실행된다. 아직 아무것도 모르지만 뭔가 잘 해놨다는 느낌이 온다. Hello worldElectron application의 기본 구성은 다음과 같다. your-app/├── package.json├── main.js└── index.html package.jsonApplication 정보를 나타내며, 여기서 main에 지정하는 .js 파일 은 entry point를 의미한다. main 지정한 main.js에서 Window를 만들고 System event를 처리해야 한다. main 에서 아무런 .js파일을 지정하지 않았다면, 자동으로 index.js파일이 있는지 찾게 된다. { \"name\" : \"your-app\", \"version\" : \"0.1.0\", \"main\" : \"main.js\"} main.js일단 모를 때는 베끼고 보자. const {app, BrowserWindow} = require('electron')const path = require('path')const url = require('url')// 윈도우 객체를 전역에 유지합니다. 만약 이렇게 하지 않으면// 자바스크립트 GC가 일어날 때 창이 멋대로 닫혀버립니다.let winfunction createWindow () { // 새로운 브라우저 창을 생성합니다. win = new BrowserWindow({width: 800, height: 600}) // 그리고 현재 디렉터리의 index.html을 로드합니다. win.loadURL(url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', slashes: true })) // 개발자 도구를 엽니다. win.webContents.openDevTools() // 창이 닫히면 호출됩니다. win.on('closed', () => { // 윈도우 객체의 참조를 삭제합니다. 보통 멀티 윈도우 지원을 위해 // 윈도우 객체를 배열에 저장하는 경우가 있는데 이 경우 // 해당하는 모든 윈도우 객체의 참조를 삭제해 주어야 합니다. win = null })}// 이 메서드는 Electron의 초기화가 끝나면 실행되며 브라우저// 윈도우를 생성할 수 있습니다. 몇몇 API는 이 이벤트 이후에만// 사용할 수 있습니다.app.on('ready', createWindow)// 모든 창이 닫히면 애플리케이션 종료.app.on('window-all-closed', () => { // macOS의 대부분의 애플리케이션은 유저가 Cmd + Q 커맨드로 확실하게 // 종료하기 전까지 메뉴바에 남아 계속 실행됩니다. if (process.platform !== 'darwin') { app.quit() }})app.on('activate', () => { // macOS에선 보통 독 아이콘이 클릭되고 나서도 // 열린 윈도우가 없으면, 새로운 윈도우를 다시 만듭니다. if (win === null) { createWindow() }})// 이 파일엔 제작할 애플리케이션에 특화된 메인 프로세스 코드를// 포함할 수 있습니다. 또한 파일을 분리하여 require하는 방법으로// 코드를 작성할 수도 있습니다. index.html<!DOCTYPE html><html> <head> <meta charset=\"UTF-8\"> <title>헬로 월드!</title> </head> <body> <h1>헬로 월드!</h1> 이 애플리케이션은 node <script>document.write(process.version)</script>, Chrome <script>document.write(process.versions.chrome)</script>, Electron <script>document.write(process.versions.electron)</script>을 사용합니다. </body></html> Build아까 위에서 electron.exe를 실행시켰을 때 나온 가이드처럼 빌드해본다. electron.exe 내가앱을넣어둔경로 해보고 나서 깨닫는다. ‘아 이거 빌드가 아니네.’ (…) 그렇다. 바로 실행된 화면이다. 다시 한 번, 배포를 위해 Build 도전애플리케이션 배포 를 참고하여 진행해본다. 1. 처음 Electron을 Downlaod 받은 폴더로 이동한다. (`electron.exe`가 있는 곳)2. 그 아래 `/resources` 로 가서 `/app` 이라는 이름의 폴더를 생성한다.3. 그리고 그 아래 위에서 만들었던 Application 파일 세 개를 넣는다.4. 다시 `electron.exe` 가 있던 폴더로 이동하여 `electron.exe`를 실행시켜본다. 실행되는 것을 확인할 수 있다.5. 그러면 이제 이 폴더들을 통째로 압축하여 전달하면 된다. 아… 뭔가 이상하다. 내가 원하는 건 이런게 아니다. Hello world가 134 MB라니? 게다가 이것도 Build라고 하기엔 좀 그렇다. Packaging을 해본다.애플리케이션 패키징 을 보니… asar 을 설치해야 한다. 설치하자. npm install asar -g 처음에 Application을 만들었던 디렉토리로 이동하여 아래 명령어를 실행한다. asar pack helloworld(디렉토리명) helloworld.asar helloworld.asar 파일이 생기긴 했는데, 알고보니 이것도 실행 파일은 아니고 electron.exe 실행 후 넣으면 되는 것이다. 그냥 소스를 숨기는 용도이다. electron-packager 사용npm install electron-packager -g 이번 삽질이 마지막이기를 바라며 명령을 실행해본다. electron-packager에 대한 명령어는 https://github.com/electron-userland/electron-packager/blob/master/usage.txt 에서도 확인할 수 있다. electron-packager [App 경로] --platform=win32 --arch x64 --out dist 아래와 같은 에러가 발생했다. 야호 신난다. Unable to determine Electron version. Please specify an Electron versionFor more information, please seehttps://github.com/electron-userland/electron-packager/blob/master/docs/api.md#version electron version까지 지정해줘야 하나보다. 불안하니 최신 말고 이전 버전을 넣어보자. electron-packager [App 경로] --platform=win32 --arch x64 --out dist --electron-version 1.4.13 무언가 실행이 된다. 굉장히 오래 걸린다. Downloading electron-v1.4.13-win32-x64.zip[=> ] 5.6% of 54.34 MB (25.18 kB/s) 그러더니 에러가 났다… 안해!!!! Downloading electron-v1.4.13-win32-x64.zipError: read ENCONNRESETread ECONNRESET 참조 http://electron.atom.io/ https://github.com/electron/electron http://electron.atom.io/releases/ http://proinlab.com/archives/1928","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"electron","slug":"electron","permalink":"http://lazyrodi.github.io/tags/electron/"}]},{"title":"[Sonarqube] 설치하기","slug":"2017-02-06-etc-install-sonarqube","date":"2017-02-06T11:46:50.000Z","updated":"2017-02-06T13:22:16.490Z","comments":true,"path":"2017/02/06/2017-02-06-etc-install-sonarqube/","link":"","permalink":"http://lazyrodi.github.io/2017/02/06/2017-02-06-etc-install-sonarqube/","excerpt":"","text":"SonarQube는 우연히 알게된 Tool로 코드 품질 관리 툴이다. 회사에서 개인적으로 사용하고 있다. 젠킨스 등의 CI 툴과도 연계(https://www.sonarqube.org/features/integration/)가 가능하여 잘 사용하면 파워풀하게 사용할 수도 있다. 개인적인 사용을 위해서는 별로 세팅할 것도 없이 그냥 사용하면 된다. SonarQube Download & RunSonarQube의 홈페이지 (https://www.sonarqube.org)에서 Download만 하면 별도의 설치 없이 실행이 가능하다. 압축을 푼 후 ...\\sonarqube-6.2\\bin\\windows-x86-64 아래의 StartSonar.bat 파일을 실행하면 웹 서버가 실행되며 브라우저를 통해 localhost:9000 으로 접속하면 아래와 같은 화면을 볼 수 있다. SonarQube Scanner Download소스를 연동시켜서 확인하기 위해서는 SonarQube Scanner(https://docs.sonarqube.org/display/SCAN/Analyzing+Source+Code)를 사용해야 한다. Download 받은 후 압축을 풀고 bin 폴더를 환경 변수에 추가하여 편하게 사용하자. 소스 연동환경설정 파일 생성Project에 대한 환경설정 파일을 생성하여야 한다. Analyzing with SonarQube Scanner Page를 참고하여 sonar-project.properties 파일을 생성하여 소스의 root 폴더에 저장한다. 12345678910111213sonar.projectKey=TestProject# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.sonar.projectName=My projectsonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace \"\\\" by \"/\" on Windows.# Since SonarQube 4.2, this property is optional if sonar.modules is set. # If not set, SonarQube starts looking for source code from the directory containing # the sonar-project.properties file.sonar.sources=. # Encoding of the source code. Default is default system encoding#sonar.sourceEncoding=UTF-8 Scan !Windows의 Command window를 이용하여 소스가 있는 폴더로 이동하자. 아래 명령어로 소스를 Scan 한다. 1> sonar-scanner 다음과 같이 scan이 성공하면 브라우저에서 localhost:9000으로 다시 접속하자. 그러면 프로젝트가 추가된 것을 볼 수 있다. 사용한 소스는 아래와 같다. 1234567891011121314151617181920212223242526package com.test.sonarqube;import java.util.ArrayList;import java.util.List;import java.util.regex.*;public class Main { private final int three = 3; public static void main(String[] args) { // TODO Auto-generated method stub int a, b; String STRING_VAR; boolean t = true; a = 3; if (t == true) { System.out.println(\"-\"); } List<String> listA = new ArrayList<String>(); listA.add(\"add\"); }} 확인Projects 메뉴에서 위에서 생성한 My Project 를 선택하여 들어가면 아래와 같은 화면을 볼 수 있다. 소스에 존재하는 Bug, Vulnerability, Code Smell, 이를 수정하기 위해 필요한 예상 시간인 Debt, Duplicated code 등의 현황을 볼 수 있다. 사용하기 나름이지만, 나는 code 메뉴를 통해 파일 별로 보는 것을 선호한다. 문제가 되는 라인마다 folding 버튼이 나타나고, 클릭하면 무엇이 문제인지 보여준다. 여기서 ... 버튼을 누르면 아래쪽에 해결책까지 보여준다. 참조 https://www.sonarqube.org https://blog.sonarsource.com https://docs.sonarqube.org/display/SCAN/Analyzing+Source+Code","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"refactoring","slug":"refactoring","permalink":"http://lazyrodi.github.io/tags/refactoring/"},{"name":"sonarqube","slug":"sonarqube","permalink":"http://lazyrodi.github.io/tags/sonarqube/"}]},{"title":"Android Vector Image","slug":"2016-08-28-android-vector-image","date":"2016-08-28T05:47:50.000Z","updated":"2016-08-28T14:49:36.916Z","comments":true,"path":"2016/08/28/2016-08-28-android-vector-image/","link":"","permalink":"http://lazyrodi.github.io/2016/08/28/2016-08-28-android-vector-image/","excerpt":"","text":"Android 5.0 (Lollipop) 부터 지원합니다. Android Studio > File > New > Vector Asset 으로 Vector Asset Studio를 실행할 수 있다. 이미 그려둔 SVG이미지가 있다면 그것을 로딩하면 되고, Material Icon을 SVG로 변환할 수 있다. 아무거나 골라서 Next > Finish Vector image를 사용하게 되면 크기가 확대되어도 화면에 깨지지 않고 보이게 된다. 아래의 미묘한 차이를 보라!","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"image","slug":"image","permalink":"http://lazyrodi.github.io/tags/image/"},{"name":"vector","slug":"vector","permalink":"http://lazyrodi.github.io/tags/vector/"}]},{"title":"MediaRecorder","slug":"2016-08-27-android-mediarecorder","date":"2016-08-27T13:47:50.000Z","updated":"2016-08-28T07:29:12.133Z","comments":true,"path":"2016/08/27/2016-08-27-android-mediarecorder/","link":"","permalink":"http://lazyrodi.github.io/2016/08/27/2016-08-27-android-mediarecorder/","excerpt":"","text":"https://developer.android.com/guide/topics/media/audio-capture.html 의 내용입니다. Audio CaptureAndroid multimedia framework은 다양한 오디오 형식의 획득(capture) 및 인코딩을 지원하여 application 작성 시 쉽게 사용할 수 있다. 이 때 MediaRecorder API를 사용하면 하드웨어 자원을 사용할 수 있다. Performing Audio CaptureAudio capture 기능을 사용하기 위해서는 다음과 같은 과정들이 필요하다. android.media.MediaRecorder의 새 개체(instance)를 생성. MediaRecorder.setAudioSource() 로 사용할 audio source를 설정. 보통 MediaRecorder.AudioSource.MIC가 된다. MediaRecorder.setOutputFormat() 으로 출력 파일의 형식을 설정. MediaRecorder.setOutputFile() 로 출력 파일의 이름을 설정. MediaRecorder.setAudioEncoder() 로 audio encoder를 설정. MediaRecorder.prepare() 를 호출. MediaRecorder.start() 로 녹음 시작. MediaRecorder.stop() 으로 녹음 종료. 사용이 끝나면 MediaRecorder.release()를 호출. Statement diagramMediaRecorder는 아래와 같은 state machine으로 동작한다. 위의 1~9 번 과정이 이루어져야 하는 이유이다. 예제 코드https://developer.android.com/guide/topics/media/audio-capture.html 제공 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176/* * The application needs to have the permission to write to external storage * if the output file is written to the external storage, and also the * permission to record audio. These permissions must be set in the * application's AndroidManifest.xml file, with something like: * * <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" /> * <uses-permission android:name=\"android.permission.RECORD_AUDIO\" /> * */package com.android.audiorecordtest;import android.app.Activity;import android.widget.LinearLayout;import android.os.Bundle;import android.os.Environment;import android.view.ViewGroup;import android.widget.Button;import android.view.View;import android.view.View.OnClickListener;import android.content.Context;import android.util.Log;import android.media.MediaRecorder;import android.media.MediaPlayer;import java.io.IOException;public class AudioRecordTest extends Activity{ private static final String LOG_TAG = \"AudioRecordTest\"; private static String mFileName = null; private RecordButton mRecordButton = null; private MediaRecorder mRecorder = null; private PlayButton mPlayButton = null; private MediaPlayer mPlayer = null; private void onRecord(boolean start) { if (start) { startRecording(); } else { stopRecording(); } } private void onPlay(boolean start) { if (start) { startPlaying(); } else { stopPlaying(); } } private void startPlaying() { mPlayer = new MediaPlayer(); try { mPlayer.setDataSource(mFileName); mPlayer.prepare(); mPlayer.start(); } catch (IOException e) { Log.e(LOG_TAG, \"prepare() failed\"); } } private void stopPlaying() { mPlayer.release(); mPlayer = null; } private void startRecording() { mRecorder = new MediaRecorder(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mRecorder.setOutputFile(mFileName); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { mRecorder.prepare(); } catch (IOException e) { Log.e(LOG_TAG, \"prepare() failed\"); } mRecorder.start(); } private void stopRecording() { mRecorder.stop(); mRecorder.release(); mRecorder = null; } class RecordButton extends Button { boolean mStartRecording = true; OnClickListener clicker = new OnClickListener() { public void onClick(View v) { onRecord(mStartRecording); if (mStartRecording) { setText(\"Stop recording\"); } else { setText(\"Start recording\"); } mStartRecording = !mStartRecording; } }; public RecordButton(Context ctx) { super(ctx); setText(\"Start recording\"); setOnClickListener(clicker); } } class PlayButton extends Button { boolean mStartPlaying = true; OnClickListener clicker = new OnClickListener() { public void onClick(View v) { onPlay(mStartPlaying); if (mStartPlaying) { setText(\"Stop playing\"); } else { setText(\"Start playing\"); } mStartPlaying = !mStartPlaying; } }; public PlayButton(Context ctx) { super(ctx); setText(\"Start playing\"); setOnClickListener(clicker); } } public AudioRecordTest() { mFileName = Environment.getExternalStorageDirectory().getAbsolutePath(); mFileName += \"/audiorecordtest.3gp\"; } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); LinearLayout ll = new LinearLayout(this); mRecordButton = new RecordButton(this); ll.addView(mRecordButton, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0)); mPlayButton = new PlayButton(this); ll.addView(mPlayButton, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0)); setContentView(ll); } @Override public void onPause() { super.onPause(); if (mRecorder != null) { mRecorder.release(); mRecorder = null; } if (mPlayer != null) { mPlayer.release(); mPlayer = null; } }} Capturing videosCamera 객체는 API 21에서 변경이 있었다. 이전: Camera 이후: android.hardware.camera2 아무튼, MediaRecorder와 함께 Camera를 제어할 수 있다. Camera 녹화를 시작할 때에는 반드시 Camera.lock(), Camera.unlock()을 통해 MediaRecorder가 camera 하드웨어에 접근할 수 있게 하고 Camera.open(), Camera.release()를 호출할 수 있다. Android 4.0 (API 14)부터 Camera.lock()과 Camera.unlock()은 자동으로 제어된다. 사진을 찍는 것과는 달리 Video 녹화는 호출 순서가 세분화되어있다. 아래의 순서를 반드시 따라야 한다. Camera.open() 을 통해 Camera 객체를 가져온다. Camera.setPreviewDisplay() 를 통해 SurfaceView에 접근하고 liver camera image를 준비한다. Camera.startPreview() 를 호출하여 liver camera image를 보여준다. Camera.unlock() 한다. MediaRecorder.setCamera() 로 사용할 camera를 선택한다. MediaRecorder.setAudioSource() 로 audio source를 선택한다. MediaRecorder.AudioSource.CAMCORDER 를 사용한다. MediaRecorder.setVideoSource() 로 video source를 선택한다. MediaRecorder.VideoSource.CAMERA 를 사용한다. 출력 파일 및 인코딩을 설정한다.8.1. Android 2.2까지 setOutputFormat(): Default는 MediaRecorder.OutputFormat.MPEG_4 setAudioEncoder(): Default는 MediaRecorder.AudioEncoder.AMR_NB setVideoEncoder(): Default는 MediaRecorder.VideoEncoder.MPEG_4_SP8.2. Android 2.2 (API 8) 이상 MediaRecorder.setProfile 메소드를 사용하며, CamcorderProfile.get() 으로 프로필 개체를 가져온다. setOutputFile() 로 출력 파일을 설정한다. getOutputMediaFile(MEDIA_TYPE_VIDEO).toString() 을 사용하며, Saving Media Files에서 예제를 확인할 수 있다. setPreviewDisplay() 를 통해 preview를 확인할 수 있다. MediaRecorder.prepare()를 호출하여 설정사항을 준비한다. MediaRecorder.start() 로 시작한다. MediaRecorder.stop() 으로 중지한다. MediaRecorder.reset() 으로 설정사항을 제거한다. 이 과정은 선택 사항이다. MediaRecorder.release() 로 MediaRecorder를 release한다. Camera.lock() 으로 잠근다. Android 4.0(API 14)부터는 MediaRecorder.prepare()의 호출이 실패하지 않는다면 이 작업은 필요없다. Camera.stopPreview() 로 preview를 중지한다. Camera.release() 로 Camera를 release한다. MediaRecorder를 이용하여 Camera preview를 생성하지 않고 몇 개의 단계를 건너뛸 수 있지만 일반적으로는 그렇게 하지 않기 때문에 여기서 다루지는 않는다. Application에서 Video 녹화를 빈번하게 사용한다면 setRecordingHint(boolean)을 사용하여 시간을 아낄 수 있다. Configure MediaRecorderMediaRecorder를 사용하여 Video 녹화를 할 때, 설정사항을 체크하고 구현하기 위해 MediaRecorder.prepare()를 호출해야만 한다. 아래의 예제는 그 과정을 나타낸 좋은 예제이다. 123456789101112131415161718192021222324252627282930313233343536private boolean prepareVideoRecorder(){ mCamera = getCameraInstance(); mMediaRecorder = new MediaRecorder(); // Step 1: Unlock and set camera to MediaRecorder mCamera.unlock(); mMediaRecorder.setCamera(mCamera); // Step 2: Set sources mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // Step 3: Set a CamcorderProfile (requires API Level 8 or higher) mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)); // Step 4: Set output file mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()); // Step 5: Set the preview output mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface()); // Step 6: Prepare configured MediaRecorder try { mMediaRecorder.prepare(); } catch (IllegalStateException e) { Log.d(TAG, \"IllegalStateException preparing MediaRecorder: \" + e.getMessage()); releaseMediaRecorder(); return false; } catch (IOException e) { Log.d(TAG, \"IOException preparing MediaRecorder: \" + e.getMessage()); releaseMediaRecorder(); return false; } return true;} Android 2.2(API 8)까지는 출력 형식 및 인코딩 형식에 대해 CamcorderProfile를 사용하지 않고 아래 코드처럼 직접 설정했어야 했다. 1234// Step 3: Set output format and encoding (for versions prior to API Level 8)mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 아래의 설정값들은 MediaRecorder에 의해 기본으로 설정되지만 원하는대로 변경할 수 있다. setViedeoEncodingBitRate() setVideoSize() setVideoFrameRate() setAudioEncodingBitRate() setAudioChannels() setAudioSamplingRate() Starting and stopping MediaRecorderMediaRecorder를 사용하여 video 녹화를 시작/중지할 때에는 반드시 아래의 순서를 따라야 한다. Camera.unlock() 아래 예시 코드처럼 MediaRecorder를 설정한다. MediaRecorder.start() 로 녹화 시작 녹화 MediaRecorder.stop() 으로 녹화 중지 MediaRecorder.release() Camera.lock() 123456789101112131415161718192021222324252627282930313233343536private boolean isRecording = false;// Add a listener to the Capture buttonButton captureButton = (Button) findViewById(id.button_capture);captureButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { if (isRecording) { // stop recording and release camera mMediaRecorder.stop(); // stop the recording releaseMediaRecorder(); // release the MediaRecorder object mCamera.lock(); // take camera access back from MediaRecorder // inform the user that recording has stopped setCaptureButtonText(\"Capture\"); isRecording = false; } else { // initialize video camera if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, // now you can start recording mMediaRecorder.start(); // inform the user that recording has started setCaptureButtonText(\"Stop\"); isRecording = true; } else { // prepare didn't work, release the camera releaseMediaRecorder(); // inform user } } } }); Releasing the cameraCamera는 shared resource이기 때문에 개체를 얻어와서 사용하고 사용이 끝나면 release해줘야 한다. Activity.onPause() 상태가 되어도 release해줘야 한다. Application에서 camera를 적절히 release해주지 않는다면 application이 종료될 때까지 다른 곳에서 접근할 수 없다. Camera 개체를 release하기 위해 Camera.release()를 사용하면 된다. 아래 예시를 보자. 123456789101112131415161718192021222324252627282930public class CameraActivity extends Activity { private Camera mCamera; private SurfaceView mPreview; private MediaRecorder mMediaRecorder; ... @Override protected void onPause() { super.onPause(); releaseMediaRecorder(); // if you are using MediaRecorder, release it first releaseCamera(); // release the camera immediately on pause event } private void releaseMediaRecorder(){ if (mMediaRecorder != null) { mMediaRecorder.reset(); // clear recorder configuration mMediaRecorder.release(); // release the recorder object mMediaRecorder = null; mCamera.lock(); // lock camera for later use } } private void releaseCamera(){ if (mCamera != null){ mCamera.release(); // release the camera for other applications mCamera = null; } }} Saving Media Files사진, 비디오 등의 Media 파일들은 장치의 외부 저장소 (SD Card)에 저장되어야 한다. 그래야 사용자가 접근할 수 있고 System 공간을 보존할 수 있다. 아무 디렉토리에나 저장이 가능하긴 하지만 다음과 같은 두 개의 표준 저장소를 제공한다. Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) 사진 및 비디오를 저장하기 위해 추천되는 저장소이다. 이 공간은 public이기 때문에 쉽게 검색, 읽기, 쓰기, 삭제를 할 수 있다. Application을 삭제하더라도 여기에 남아있는 자료는 삭제되지 않는다. 사용자가 원래 사용하던 파일들과의 간섭을 피하기 위해 하위 디렉토리를 생성해서 사용하자(아래 예시에 나와있다). 이 메소드는 Android 2.2 (API 8)부터 사용 가능하다. 이전 버전에서는 Saving Shard Files를 확인하자. Context.getExternalFilesDir(Environment.DIRECTORY_PICTURE) 이 메소드는 해당 application에 속한 디렉토리를 반환한다. Application이 삭제될 때 여기에 있는 파일들이 같이 삭제된다. 이 파일들에대해 보안규칙이 적용되지 않기 떄문에 다름 application들도 이 파일들을 읽기/수정/삭제 할 수 있다. 아래 예제는 Media 파일을 위한 File 또는 Uri 저장소를 어떻게 생성하고 Camera application이 Intent를 통해 사용하는 방법을 나타낸다. 더 자세한 정보는 Data Storage 에서 확인할 수 있다. 1234567891011121314151617181920212223242526272829303132333435363738394041public static final int MEDIA_TYPE_IMAGE = 1;public static final int MEDIA_TYPE_VIDEO = 2;/** Create a file Uri for saving an image or video */private static Uri getOutputMediaFileUri(int type){ return Uri.fromFile(getOutputMediaFile(type));}/** Create a File for saving an image or video */private static File getOutputMediaFile(int type){ // To be safe, you should check that the SDCard is mounted // using Environment.getExternalStorageState() before doing this. File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), \"MyCameraApp\"); // This location works best if you want the created images to be shared // between applications and persist after your app has been uninstalled. // Create the storage directory if it does not exist if (! mediaStorageDir.exists()){ if (! mediaStorageDir.mkdirs()){ Log.d(\"MyCameraApp\", \"failed to create directory\"); return null; } } // Create a media file name String timeStamp = new SimpleDateFormat(\"yyyyMMdd_HHmmss\").format(new Date()); File mediaFile; if (type == MEDIA_TYPE_IMAGE){ mediaFile = new File(mediaStorageDir.getPath() + File.separator + \"IMG_\"+ timeStamp + \".jpg\"); } else if(type == MEDIA_TYPE_VIDEO) { mediaFile = new File(mediaStorageDir.getPath() + File.separator + \"VID_\"+ timeStamp + \".mp4\"); } else { return null; } return mediaFile;} 참조 https://developer.android.com/guide/topics/media/audio-capture.html https://developer.android.com/reference/android/media/MediaRecorder.html https://developer.android.com/guide/topics/media/camera.html#capture-video","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"mediarecorder","slug":"mediarecorder","permalink":"http://lazyrodi.github.io/tags/mediarecorder/"}]},{"title":"[JavaScript] Uh... Converter","slug":"2016-08-21-works-uh-converter","date":"2016-08-21T11:20:00.000Z","updated":"2016-08-21T14:54:37.613Z","comments":true,"path":"2016/08/21/2016-08-21-works-uh-converter/","link":"","permalink":"http://lazyrodi.github.io/2016/08/21/2016-08-21-works-uh-converter/","excerpt":"","text":"Document Word Parser Use : http://lazyrodi.github.io/misc/UhConverter.html Repository : https://github.com/lazyrodi/misc/tree/master/UhConverter Usage Select Input Data Type. Input code. You can input single data or array.i.e. array format is {1, 2, 3, 4, 5}. MUST input ‘{‘, ‘}’ and seperator ‘,’ Run You can see converted results. Example Single Array Binary 100011 {100011, 100100, 111010} Decimal 35 {35, 36, 58} Octal 43 {43, 44, 72} Hexadecimal 23 {23, 24, 3a} ASCII # {#, $, :}","categories":[{"name":"works","slug":"works","permalink":"http://lazyrodi.github.io/categories/works/"}],"tags":[{"name":"javascript","slug":"javascript","permalink":"http://lazyrodi.github.io/tags/javascript/"},{"name":"converter","slug":"converter","permalink":"http://lazyrodi.github.io/tags/converter/"}]},{"title":"GIT Troubleshooting","slug":"2016-08-15-troubleshooting-git","date":"2016-08-14T15:10:50.000Z","updated":"2016-08-14T15:28:29.044Z","comments":true,"path":"2016/08/15/2016-08-15-troubleshooting-git/","link":"","permalink":"http://lazyrodi.github.io/2016/08/15/2016-08-15-troubleshooting-git/","excerpt":"","text":"Gerrit admin을 잃어버렸을 때개인적으로 사용하는 gerrit에서만 사용하고 회사에서는 이런 방법으로 해서는 안될듯. 그런데 다른 방법을 모르겠음… gerrit/etc/gerrit.conf 파일을 다음과 같이 수정한다. 이 상태일 때는 아무나 로그인하여 권한을 변경할 수 있으니 매우 주의하고 작업시간을 최소화할 것. [auth] type = development_become_any_account 이후 gerrit에 로그인하고 계정 정보쪽 (우측 상단)을 누르면 Switch Account가 보이고, 이를 눌러 admin 계정 (1000000) 으로 로그인이 가능해진다. 그 다음에 Administrator group에 원하는 계정을 추가한다. gerrit/etc/gerrit.conf 파일을 원복시킨다.","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"TroubleShooting","slug":"Dev/TroubleShooting","permalink":"http://lazyrodi.github.io/categories/Dev/TroubleShooting/"}],"tags":[{"name":"git","slug":"git","permalink":"http://lazyrodi.github.io/tags/git/"},{"name":"troubleshooting","slug":"troubleshooting","permalink":"http://lazyrodi.github.io/tags/troubleshooting/"}]},{"title":"Gerrit 사용","slug":"2016-08-14-etc-use-gerrit","date":"2016-08-14T06:02:50.000Z","updated":"2016-08-14T16:58:40.809Z","comments":true,"path":"2016/08/14/2016-08-14-etc-use-gerrit/","link":"","permalink":"http://lazyrodi.github.io/2016/08/14/2016-08-14-etc-use-gerrit/","excerpt":"","text":"다른 포스트에서 이어집니다. GIT Server Installation Gitweb 사용 Gerrit 설치 프로젝트의 추가프로젝트를 추가하는 방법에는 기본적으로 세 가지가 있다고 한다. https://gerrit-documentation.storage.googleapis.com/Documentation/2.12.3/project-configuration.html Gerrit web page 상에서 Projects > Create Project를 통해 생성하는 방법 REST endpoint를 통해 생성하는 방법 SSH 명령어를 통해 생성하는 방법 나는 web page를 통해 생성을 시도해본다. Gerrit Web page를 통해 프로젝트 생성Projects > Create New Project 로 가서 Project Name에 temp를 입력하고 생성해본다. 이러면 원래 지정해두었던 ~/repository 디렉토리 안에 새로운 temp.git 이라는 저장소가 생성된다. 사용자 추가 (Server side)사용자의 추가는 gerrit 사용자 계정에서 아래 명령을 통해 추가한다. 이전에 admin을 추가했었다. $ htpasswd ~/opt/gerrit/etc/passwords user 해당 사용자로 처음 로그인을 시도하면 이름, 이메일 및 SSH public key를 넣으라고 나온다. 이름과 이메일을 넣고, SSH public key는 다음과 같이 생성하여 넣으면 된다. 만약, 이전에 smtp 설정을 하지 않았다면 새 사용자의 email 인증은 진행할 수 없다. SSH public key 생성 (Client side)아래와 같이 생성된 key의 내용을 복사하여 gerrit에 넣고 Add를 눌러 추가한다. $ cd ~$ mkdir .ssh$ cd .ssh$ ssh-keygen # 전부 그냥 엔터$ cat id_rsa.pub # 여기 나오는 내용이 SSH public key이다. Project clone (Client side)새로 추가한 user 계정에서 gerrit 접속 후 projects > List > temp로 가면 현재 프로젝트를 clone할 수 있는 주소를 알 수 있다. http와 ssh로 나뉘어 제공되며, 나는 ssh로 clone 해본다. git clone ssh://[email protected]:29418/temp push 1차 실패파일 생성 후 push를 시도해본다. $ touch b$ git add -A$ git commit -m \"[temp] touch b\"$ git pushCounting objects: 4, done.Delta compression using up to 2 threads.Compressing objects: 100% (2/2), done.Writing objects: 100% (2/2), 231 bytes | 0 bytes/s, done.Total 2 (delta 0), reused 0 (delta 0)remote: Branch refs/heads/master:remote: You are not allowed to perform this operation.remote: To push into this reference you need 'Push' rights.remote: User: userremote: Please read the documentation and contact an administratorremote: if you feel the configuration is incorrectremote: Processing changes: refs: 1, doneTo ssh://[email protected]:29418/temp ! [remote rejected] master -> master (prohibited by Gerrit)error: 레퍼런스를 'ssh://[email protected]:29418/temp'에 푸시하는데 실패했습니다 직접 push할 수 있는 권한이 없다. 검색을 해보면 이전에는 git review라는 명령어가 있었던 것 같은데 지금은 존재하지 않는다. $ git remote -v 해서 temp 저장소에 대한 정보를 보면 master의 merge가 refs/heads/master로 되어있는 것을 확인할 수 있다. 하지만 gerrit을 통해 code review를 받기 위해서는 refs/for/master 로 push를 해야한다고 한다. The refs/for/ prefix is used to map the Gerrit concept of “Pushing for Review” to the git protocol. 시키는대로 한다. $ git push origin HEAD:refs/for/refs/heads/master push 2차 실패또 실패했다. 다행인 것은 에러가 다르다. Counting objects: 4, done.Delta compression using up to 2 threads.Compressing objects: 100% (2/2), done.Writing objects: 100% (2/2), 231 bytes | 0 bytes/s, done.Total 2 (delta 0), reused 0 (delta 0)remote: Processing changes: refs: 1, doneremote: ERROR: missing Change-Id in commit message footerremote:remote: Hint: To automatically insert Change-Id, install the hook:remote: gitdir=$(git rev-parse --git-dir); scp -p -P 29418 [email protected]:hooks/commit-msg ${gitdir}/hooks/remote: And then amend the commit:remote: git commit --amendremote:To ssh://[email protected]:29418/temp ! [remote rejected] HEAD -> refs/for/refs/heads/master (missing Change-Id in commit message footer)error: 레퍼런스를 'ssh://[email protected]:29418/temp'에 푸시하는데 실패했습니다 Change-Id가 없단다. 추적을 위해 반드시 필요한 부분이니 또 시키는대로 해본다. $ scp -p -P 29418 [email protected]:hooks/commit-msg .git/hooks/$ git commit --amend #그냥 저장만 하고 나오자. git log를 쳐보면 Change-Id가 생성된 것을 확인할 수 있다. 기쁜 마음으로 git push origin HEAD:refs/for/refs/heads/master 해보자. 드디어 성공했다. Counting objects: 4, done.Delta compression using up to 2 threads.Compressing objects: 100% (2/2), done.Writing objects: 100% (2/2), 278 bytes | 0 bytes/s, done.Total 2 (delta 0), reused 0 (delta 0)remote: Processing changes: new: 1, refs: 1, doneremote:remote: New Changes:remote: http://192.168.0.99:80/1 [temp] touch bremote:To ssh://[email protected]:29418/temp * [new branch] HEAD -> refs/for/refs/heads/master Gerrit에서 확인하기브라우저로 가서 gerrit에 들어가본다. My > Changes 로 가면 방금 push한 commit을 확인할 수 있다. administrator 또는 reviewer group에 속한 사람의 계정으로 접속하면 코드를 확인 및 점수를 줄 수 있고 commit을 submit (merge)할 수 있다. 자신의 commit에 변경사항이 발생하면 email로도 알려준다. 좋은 세상이다. 참조 http://mytory.net/archives/12632","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"git","slug":"git","permalink":"http://lazyrodi.github.io/tags/git/"},{"name":"gerrit","slug":"gerrit","permalink":"http://lazyrodi.github.io/tags/gerrit/"}]},{"title":"MySQL Troubleshooting","slug":"2016-08-14-troubleshooting-mysql","date":"2016-08-14T01:10:50.000Z","updated":"2017-09-02T13:50:09.821Z","comments":true,"path":"2016/08/14/2016-08-14-troubleshooting-mysql/","link":"","permalink":"http://lazyrodi.github.io/2016/08/14/2016-08-14-troubleshooting-mysql/","excerpt":"","text":"ERROR 1045 (28000): Access Denied for user ‘root’@’localhost’ (using password: NO) $ sudo mysql -p 명령으로 비밀번호를 따로 입력하면 OK. could not connect to mysql can’t connect to mysql server on ‘127.0.0.1’ (10061) (code 2003) Windows 키 > 컴퓨터 관리 > 서비스 및 응용 프로그램 > 서비스 > MySQL57 이 실행 중인지 확인.","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"TroubleShooting","slug":"Dev/TroubleShooting","permalink":"http://lazyrodi.github.io/categories/Dev/TroubleShooting/"}],"tags":[{"name":"troubleshooting","slug":"troubleshooting","permalink":"http://lazyrodi.github.io/tags/troubleshooting/"},{"name":"mysql","slug":"mysql","permalink":"http://lazyrodi.github.io/tags/mysql/"}]},{"title":"Gerrit 설치","slug":"2016-08-14-etc-gerrit-installation","date":"2016-08-14T01:02:50.000Z","updated":"2016-08-14T16:56:47.053Z","comments":true,"path":"2016/08/14/2016-08-14-etc-gerrit-installation/","link":"","permalink":"http://lazyrodi.github.io/2016/08/14/2016-08-14-etc-gerrit-installation/","excerpt":"","text":"다른 포스트에서 이어집니다. GIT Server Installation Gitweb 사용 이전 포스트에서 삽질했던 GIT 저장소에 대한 내용을 gerrit에서 직접적으로 사용하진 않습니다. 설치 설치 가이드: https://gerrit-documentation.storage.googleapis.com/Documentation/2.12.3/install.html 유틸 설치 JDK7 이상 Apache2 Database H2, Apache Derby, PostgreSQL, MySQL, Oracle, SAP MaxDB, DB2 중 하나를 사용하면 된다. H2를 사용하면 gerrit의 기본 database라서 따로 설정할 것이 없다. $ sudo apt-get install oracle-java7-installer$ sudo apt-get install apache2$ sudo apt-get install libapache2-mod-proxy-html$ sudo apt-get install apache2-utils$ sudo a2enmod proxy$ sudo a2enmod proxy_http$ sudo service apache2 restart gerrit 설치 https://gerrit-releases.storage.googleapis.com/index.html 의 Release Notes를 통해 war 경로를 확인할 수 있다. 16년 8월 현재 최신버전은 2.12.3. https://gerrit-releases.storage.googleapis.com/gerrit-2.12.3.war 우선, gerrit을 위해 ‘gerrit’ 이라는 user를 하나 생성하였다. 설치 파일을 아래의 wget 명령어로 download받을 수 있으며, ~/Downloads 디렉토리를 생성하여 그곳에 받았다. $ wget https://gerrit-releases.storage.googleapis.com/gerrit-2.12.3.war 설치한다. init -d 이후의 경로는 설치하고 싶은 경로를 지정한다. $ java -jar gerrit-2.12.3.war init -d ~/opt/gerrit 나의 경우 설치 과정은 다음과 같다. gerrit@computer:~/Downloads$ java -jar gerrit-2.12.3.war init -d ~/opt/gerritUsing secure store: com.google.gerrit.server.securestore.DefaultSecureStore[2016-08-14 17:19:33,592] [main] INFO com.google.gerrit.server.config.GerritServerConfigProvider : No /home/gerrit/opt/gerrit/etc/gerrit.config; assuming defaults*** Gerrit Code Review 2.12.3***Create '/home/gerrit/opt/gerrit' [Y/n]? Y*** Git Repositories***Location of Git repositories [git]: /home/gerrit/repository/*** SQL Database***Database server type [h2]:*** Index***Type [LUCENE/?]:The index must be rebuilt before starting Gerrit: java -jar gerrit.war reindex -d site_path*** User Authentication***Authentication method [OPENID/?]: httpGet username from custom HTTP header [y/N]? yUsername HTTP header [SM_USER]:SSO logout URL : http://aa:[email protected]:80/login/Enable signed push support [y/N]?*** Review Labels***Install Verified label [y/N]?*** Email Delivery***SMTP server hostname [localhost]: smtp.gmail.comSMTP server port [(default)]: 465SMTP encryption [NONE/?]: SSLSMTP username [gerrit]: 계정@gmail.com계정@gmail.com's password : confirm password :*** Container Process***Run as [gerrit]:Java runtime [/usr/lib/jvm/java-7-oracle/jre]:Copy gerrit-2.12.3.war to /home/gerrit/opt/gerrit/bin/gerrit.war [Y/n]? YCopying gerrit-2.12.3.war to /home/gerrit/opt/gerrit/bin/gerrit.war*** SSH Daemon***Listen on address [*]:Listen on port [29418]:Gerrit Code Review is not shipped with Bouncy Castle Crypto SSL v152 If available, Gerrit can take advantage of features in the library, but will also function without it.Download and install it now [Y/n]?Downloading http://repo2.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.52/bcpkix-jdk15on-1.52.jar ... OKChecksum bcpkix-jdk15on-1.52.jar OKGerrit Code Review is not shipped with Bouncy Castle Crypto Provider v152** This library is required by Bouncy Castle Crypto SSL v152. **Download and install it now [Y/n]?Downloading http://repo2.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.52/bcprov-jdk15on-1.52.jar ... OKChecksum bcprov-jdk15on-1.52.jar OKGenerating SSH host key ... rsa... dsa... done*** HTTP Daemon***Behind reverse proxy [y/N]? yProxy uses SSL (https://) [y/N]?Subdirectory on proxy server [/]:Listen on address [*]: 192.168.0.99Listen on port [8081]:Canonical URL [http://127.0.0.1/]: http://192.168.0.99*** Plugins***Installing plugins.Install plugin commit-message-length-validator version v2.12.3 [y/N]? yInstall plugin download-commands version v2.12.3 [y/N]? yInstall plugin replication version v2.12.3 [y/N]? yInstall plugin reviewnotes version v2.12.3 [y/N]? yInstall plugin singleusergroup version v2.12.3 [y/N]? yInitializing plugins.No plugins found with init steps.Initialized /home/gerrit/opt/gerrit 위의 설정사항은 설치디렉토리/etc/gerrit.config 에 저장된다. Index rebuilt잘은 모르겠지만 검색 엔진에 대한 색인 과정인 것 같다. 이 명령어를 수행해줘야 하는 것 같다. $ java -jar gerrit-2.12.3.war reindex -d ~/opt/gerrit Gerrit 실행$ cd ~/opt/gerrit/bin$ ./gerrit.sh start 실행은 됐는데 브라우저로 확인할 수가 없다. apache 설정을 할 차례가 됐다. apache proxy 설정VirtualHost 파일 생성$ sudo vim /etc/apache2/sites-available/gerrit.conf <VirtualHost *:80> ServerName localhost ProxyRequests Off ProxyVia Off ProxyPreserveHost On <Proxy *> Order deny,allow Allow from all </Proxy> <Location /login/> AuthType Basic AuthName \"Gerrit Code Review\" Require valid-user AuthUserFile /home/gerrit/opt/gerrit/etc/passwords </Location> ProxyPass / http://192.168.0.99:8081/ ProxyPassReverese / http://192.168.0.99:8081/</VirtualHost> Site-enabled 설정$ cd /etc/apache2/sites-enabled$ sudo ln -s ../sites-available/gerrit.conf ./001-gerrit.conf$ sudo a2ensite 001-gerrit.conf 기본 설정파일의 포트를 변경포트 충돌로 인해 gerrit이 로딩이 안될 수 있으므로, sudo vim /etc/apache2/sites-available/000-default.conf 파일의 포트를 80에서 10080 등으로 변경한다. <VirtualHost *:10080> Gerrit 사용자 등록관리자 계정을 생성한다. htpasswd로 생성하는 계정은 apache에서 인증을 위해 사용하는 계정이며, gerrit은 처음 생성된 계정(uid 1000000)을 administrator로 인식한다. $ htpasswd -c /home/gerrit/opt/gerrit/etc/passwords \"admin\"passwords파일을 처음 생성할 때에만 -c 옵션을 사용하며 이후 다른 계정을 추가할 때에는 그냥 아래와 같이 추가한다.$ htpasswd /home/gerrit/opt/gerrit/etc/passwords \"usera\" Apache 재시작 및 gerrit 재시작$ sudo service apache2 restart$ cd ~/opt/gerrit/bin$ ./gerrit.sh restart 이후 접속을 시도하면, 사용자 이름 및 비밀번호를 묻는다. 위에서 설정한 admin / password 를 넣는다. 로그인 된다. 그런데 브라우저에따라 sign out이 안 되는 문제가 있다. 이 문제의 해결을 위해서는 설치 시 logout URL을 http://aa:[email protected]:80/login/ 처럼 넣어주면 된다. aa:aa 는 문자 그대로 aa:aa 그대로 넣어주면 된다. 이게 조금 이상한 버그인게… 사람마다 아래 중 하나로 해야 해결되는 케이스가 있다고 한다. http://aa:[email protected] http://aa:[email protected]:80 # 포트까지 http://aa:[email protected]:80/login/ # 뒤의 경로까지 logout URL을 처음에 설정을 못 하였다면 /etc/gerrit.config 파일을 열고 [auth] 아래에 다음과 같이 추가하면 된다. [auth]SSO logout URL : http://aa:[email protected]:80/login/ sign out이 안되는 문제의 기본적인 원인은 HTTP의 특성 (https://groups.google.com/forum/?fromgroups=#!topic/repo-discuss/7P73ZvK4OQQ 참조)이라고 한다. auth type을 다른 것들(oAuth, OpenID, etc.)로 지정하면 이런 문제는 발생하지 않는 듯 하다. 참조 https://www.gerritcodereview.com/ http://pseg.or.kr/pseg/infoinstall/1780 http://ahnseungkyu.com/194","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"git","slug":"git","permalink":"http://lazyrodi.github.io/tags/git/"},{"name":"gerrit","slug":"gerrit","permalink":"http://lazyrodi.github.io/tags/gerrit/"}]},{"title":"Gitweb 사용","slug":"2016-08-13-etc-use-gitweb","date":"2016-08-13T14:02:50.000Z","updated":"2016-08-14T08:15:07.471Z","comments":true,"path":"2016/08/13/2016-08-13-etc-use-gitweb/","link":"","permalink":"http://lazyrodi.github.io/2016/08/13/2016-08-13-etc-use-gitweb/","excerpt":"","text":"GIT Server Installation에서 설치한 내용을 GitWeb 을 통해 보는 방법이다.GitWeb은 말 그대로 GIT의 변경사항을 Web에서 편하게 볼 수 있게 도와주는 툴이다. 예시로는 https://android.googlesource.com/ 를 보는 것이 적절할 수 있다. GIT에서 기본적으로 지원해주는 내용이라 한 번에 써둘까 했는데, 그냥 포스트를 하나 더 작성하였다. 아래 명령을 수행하면 GitWeb이 실행되어 브라우저에서 확인할 수 있다. $ sudo git instaweb Gitweb은 lighttpd에 의존성이 있어서 내 PC에서는 에러가 발생하였다. 설치한다. 리눅스 버전에 따라 기본적으로 설치되어 있는 것도 있다고 한다. $ sudo apt-get install lighttpd 이후 다시 sudo git instaweb을 수행한 후 브라우저에서 127.0.0.1:1234 를 입력하면 Gitweb을 확인할 수 있다. GitWeb의 Description 설정은 GIT 저장소의 description파일을 수정하면 바뀐다. 참조 https://git-scm.com/book/ko/v2/Git-%EC%84%9C%EB%B2%84-GitWeb","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"git","slug":"git","permalink":"http://lazyrodi.github.io/tags/git/"},{"name":"gitweb","slug":"gitweb","permalink":"http://lazyrodi.github.io/tags/gitweb/"}]},{"title":"GIT server installation","slug":"2016-08-13-etc-git-server-installation","date":"2016-08-13T03:02:50.000Z","updated":"2016-08-14T08:09:24.893Z","comments":true,"path":"2016/08/13/2016-08-13-etc-git-server-installation/","link":"","permalink":"http://lazyrodi.github.io/2016/08/13/2016-08-13-etc-git-server-installation/","excerpt":"","text":"Ubuntu에서 GIT server를 구축해 보았다. Server side새 Project를 위한 계정 생성$ sudo adduser project-a git 설치위에서 생성한 project-a 계정으로 로그인한 후, $ sudo apt-get install git .git 저장소 생성임의의 디렉토리 ~/project-a.git를 생성한다. $ mkdir project-a.git$ cd project-a.git$ git init --bare --shared --bare: 빈 저장소(bare repository)를 생성한다. config 파일의 bare 속성이 true로 변경된다. bare 설정이 되어있어야 제대로 사용이 가능하다. GIT_DIR 환경설정이 따로 되어있는 경우가 아니라면 현재의 디렉토리를 빈 저장소로 설정한다.--shared: GIT 저장소의 push 권한을 설정한다. 이 옵션을 사용할 경우 core.sharedRepository 속성이 1로 설정된다. 이 옵션의 하위 속성으로는 false, true, umask, group, all, world, everybody, 0xxx가 있다. 놀랍게도(?) GIT server의 생성이 끝났다. Client side서버로부터 git clone하기위에서 생성한 저장소를 사용할 user의 아이디가 User2 라고 하자. 여기서는 같은 서버에 붙어있는 사용자이므로 주소를 127.0.0.1 로 사용한다. $ git clone [email protected]:/home/project-a/project-a.git 환경설정$ git config --global user.email \"[email protected]\"$ git config --global user.name \"User2\"$ git config push.default simple push.default: simple과 matching 두 가지 값을 사용할 수 있으며, 각각의 의미는 다음과 같다. simple: 현재 작업 중인 branch에만 push matching: 모든 branch에 대해 push 설정값들은 git config --global --list를 통해 확인할 수 있으며, 기본 설정을 건드리지 않았다면 /home/계정/.gitconfig에 내용이 저장되어 있다. push.default를 설정하지 않아도 크게 상관은 없지만, git push 시 다음과 같은 경고가 발생하기 때문에 미리 설정하는 것이 좋다. warning: push.default is unset; its implicit value is changing inGit 2.0 from 'matching' to 'simple'. To squelch this messageand maintain the current behavior after the default changes, use: git config --global push.default matchingTo squelch this message and adopt the new behavior now, use: git config --global push.default simpleWhen push.default is set to 'matching', git will push local branchesto the remote branches that already exist with the same name.In Git 2.0, Git will default to the more conservative 'simple'behavior, which only pushes the current branch to the correspondingremote branch that 'git pull' uses to update the current branch.See 'git help config' and search for 'push.default' for further information.(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode'current' instead of 'simple' if you sometimes use older versions of Git) 파일 만들어서 push 하기$ touch a$ git add .$ git commit -m \"[temp] make file 'a'.\"$ git push 하면, 에러가 난다. fatal: The remote end hung up unexpectedlyerror: 레퍼런스를 '[email protected]:/home/project-a/project-a.git'에 푸시하는데 실패했습니다 GIT 저장소인 /home/project-a/project-a.git에 User2 사용자의 write 권한이 없어서 문제가 발생한 것이다. 다시 Server sidegroup 추가 및 permission 부여해당 프로젝트 사용자들을 위해 project-a그룹에 사용자를 추가하자. $ sudo usermod -a -G project-a User2 그리고 다시 Client side다시 git push를 시도한다. 그리고 성공한다. Counting objects: 3, done.Writing objects: 100% (3/3), 219 bytes | 0 bytes/s, done.Total 3 (delta 0), reused 0 (delta 0)To [email protected]:/home/project-a/project-a.git * [new branch] master -> master 참조 https://git-scm.com/book/ko/v2","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"git","slug":"git","permalink":"http://lazyrodi.github.io/tags/git/"}]},{"title":"Big-O Complexity","slug":"2016-08-09-common-bigo","date":"2016-08-09T11:02:50.000Z","updated":"2016-08-10T15:59:55.514Z","comments":true,"path":"2016/08/09/2016-08-09-common-bigo/","link":"","permalink":"http://lazyrodi.github.io/2016/08/09/2016-08-09-common-bigo/","excerpt":"","text":"Big-O 분석법 (Big-O analysis)입력 값의 개수에 따라 알고리즘이 수행되는데 걸리는 시간을 바탕으로 알고리즘의 효율성을 평가하는 실행 시간 분석법. Big-O 분석의 적용 입력 값이 무엇인지 확인하고 어떤 것을 n으로 놓아야 할지 결정한다. 알고리즘에서 수행해야 할 연산 횟수를 n의 식으로 표현한다. 차수가 제일 높은 항만 남긴다. 모든 상수 인수를 없앤다. Big-O 알고리즘의 종류O(1)상수 실행 시간(constant running time) 가장 빠른 알고리즘이며 이 경우는 거의 없다. 123def constant(n): result = n * n return result O(log n)로그 알고리즘(logarithmic algorithm) 실행 시간이 입력 크기의 log에 비례해서 늘어나는 알고리즘. 123456def logarithmatic(n): result = 0 while n > 1: n //= 2 result += 1 return result O(n)선형 알고리즘(linear algorithm) 실행 시간이 입력크기에 비례하는 알고리즘. O(n) 12345def linear(n, A): for i in xrange(n): if A[i] == 0: return 0 return 1 O(n + m) 1234567def linear(n, m): result = 0 for i in xrange(n): result += i for j in xrange(m): result += j return result O(n log n)초선형 알고리즘(superlinear algorithm) 속도가 선형 알고리즘과 다항식 알고리즘의 중간쯤이다. O(n^2)이차 알고리즘(quadratic algorithm) 입력 크기의 제곱으로 시간이 늘어난다. 123456def quadratic(n): result = 0 for i in xrange(n): for j in xrange(i, n): result += 1 return result O(n^c)다항식 알고리즘(polynomial algorithm) 입력 크기가 늘어나면 실행 시간이 빠르게 늘어난다. O(c^n)지수 알고리즘(exponential algorithm) 다항식 알고리즘보다 실행 속도가 빠르게 늘어난다. O(n!)팩토리얼 알고리즘(factorial algorithm) 가장 느린 알고리즘으로 n의 값이 작다고 해도 사용이 힘든 수준으로 느려진다. 참조 프로그래밍 면접 이렇게 준비한다 - 한빛미디어 https://ko.wikipedia.org/wiki/%EC%A0%90%EA%B7%BC_%ED%91%9C%EA%B8%B0%EB%B2%95 https://codility.com/programmers/lessons/1/ https://en.wikipedia.org/wiki/Time_complexity","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"common","slug":"Dev/common","permalink":"http://lazyrodi.github.io/categories/Dev/common/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"big o","slug":"big-o","permalink":"http://lazyrodi.github.io/tags/big-o/"},{"name":"complexity","slug":"complexity","permalink":"http://lazyrodi.github.io/tags/complexity/"}]},{"title":"Linux Troubleshooting","slug":"2016-08-09-troubleshooting-linux","date":"2016-08-09T01:00:50.000Z","updated":"2016-08-14T04:03:42.195Z","comments":true,"path":"2016/08/09/2016-08-09-troubleshooting-linux/","link":"","permalink":"http://lazyrodi.github.io/2016/08/09/2016-08-09-troubleshooting-linux/","excerpt":"","text":"아래 글들은 Ubuntu에 대한 내용입니다. 노트북 절전모드 진입 시 네트워크가 끊어지는 문제 관리자 권한으로 /etc/systemd/logind.conf 파일에 HandleLidSwitch=ignore 를 추가한다. Login daemon을 재 시작한다. sudo restart systemd-logind 참조: http://askubuntu.com/questions/360615/ubuntu-server-13-10-now-goes-to-sleep-when-closing-laptop-lid/361087#361087 한글 깨짐 현상이 문제에 대한 해결책은 여기저기 다 다른 것 같지만… Language pack install $ apt-get install language-pack-ko$ apt-get install language-pack-ko-base Home 디렉토리(cd ~)의 .bashrc 또는 .bash_profile에 export LANG=ko_KR.UTF-8 추가 시스템 재시작 Putty 문제인 경우!!putty로 접속했을 때 깨지는 문제는 Ubuntu의 문제와 다를 수 있음. putty 실행 > Window > Translation > Remote character set에서 UTF-8을 선택하면 문제 해결. sudo 권한 또는 root 비밀번호 분실 시그룹을 잘못 지정해서 sudo 권한을 상실해서 난감했었다. 아래와 같이 Recovery mode로 진입하여 해결할 수 있다. Recovery mode 진입 부팅 시 좌측 Shift 키를 누르고 있으면 진입이 가능하다. 한성 노트북에서는 이게 잘 되지 않아서 F2로 CMOS 진입하니 부팅 순서에 ubuntu가 두 개가 있어서 다른 것으로 진입하니 grub mode로 진입이 되었다. (recovery mode)로 되어있는 것을 선택하여 진입하고, 선택 메뉴가 나오면 root Drop to root shell prompt 를 선택한다. 파티션의 write 권한 부여를 위해 remount한다. $ mount -rw -o remount / 만약 root 비밀번호를 잃어버린 거라면 passwd를 입력하여 비밀번호를 변경한다. 나의 경우는 sudo 권한을 가진 계정이 모두 없어져서 곤란한 상황이었기 때문에 $ visudo 를 수행하여 다음과 같이 root 아래 sudo권한을 원하는 계정을 추가하고 저장 후 reboot 하면 끝.# User privilege specificationroot ALL=(ALL:ALL) ALL계정 ALL=(ALL:ALL) ALL","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"TroubleShooting","slug":"Dev/TroubleShooting","permalink":"http://lazyrodi.github.io/categories/Dev/TroubleShooting/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://lazyrodi.github.io/tags/linux/"},{"name":"troubleshooting","slug":"troubleshooting","permalink":"http://lazyrodi.github.io/tags/troubleshooting/"}]},{"title":"Ubuntu server installation","slug":"2016-08-09-docthread-install-ubuntu","date":"2016-08-08T23:00:50.000Z","updated":"2016-08-09T10:38:46.820Z","comments":true,"path":"2016/08/09/2016-08-09-docthread-install-ubuntu/","link":"","permalink":"http://lazyrodi.github.io/2016/08/09/2016-08-09-docthread-install-ubuntu/","excerpt":"","text":"ssh 접속해서 딴 짓 하려고 서버를 설치할래다가 기존에 그냥 Desktop용을 사용하던 것이 있어서 거기에 openssh-server를 설치하기로 했습니다. 그 전의 과정까지는 그래도 스스로를 위해 남겨두려… DownloadUbuntu 공식 사이트에서 Server 버전을 다운로드 받습니다. ISO 파일이 다운로드 됩니다. Installing전체적인 과정은 Installing Ubuntu Server for general user페이지를 참조하세요. USB 만들기Rufus 다운로드https://rufus.akeo.ie/ 부팅 가능한 USB 만들기How to create a bootable USB stick on Windows에 잘 설명되어 있습니다. 설치하기설치하려는 PC에 USB꼽고 부팅해서 쭉쭉 설치. 요새는 뭐 왠만한 것들은 알아서 다 해줘서 혼자 공부하기 위한 설치라면 쭉쭉 진행 가능합니다. 리눅스 파티션리눅스와 유닉스 계열의 OS들은 Windows의 폴더 및 드라이브 개념이 없습니다. 루트 디렉토리를 기준으로 파일 및 디렉토리들이 위치하며, mount 명령을 통해 디렉토리와 HDD 등의 저장소를 연결하여 사용할 수 있습니다. 리눅스의 파티션은 각각 아래의 용도로 사용됩니다. 파티션 설명 / 루트 디렉토리.최상위 디렉토리이며 cd / 명령어를 통해 접근할 수 있습니다. /bin 명령어들이 들어있으며 여기에 있는 명령어들은 모든 사용자들이 사용 가능합니다.필수적인 명령어들 및 시스템 부팅과 관련된 파일들이 모여있습니다.커널 이미지인 vmlinuz가 여기에 위치해 있습니다. /dev 디바이스 파일들. /etc 여러가지의 시스템 설정 파일들. /home 일반 사용자의 home 디렉토리.hong이라는 사용자의 home 디렉토리는 일반적으로 /home/hong이 됩니다. 이 경로는 설정을 통해 변경 가능합니다.cd ~를 통해 접근할 수 있습니다. /lib 공유 라이브러리 /mnt 다른 파티션을 mount 할 때 이곳으로 합니다.일반적으로 cdrom 사용 시 /mnt/cdrom에 mount시켜서 사용합니다. /proc 프로세스 정보를 담고 있는 파일들.프로세스 정보는 ps 명령어로도 확인할 수 있습니다. /root Super user(root)의 home 디렉토리. /sbin Super user가 시스템을 운영하는데 필수적인 실행 파일들. /tmp Temporary 디렉토리. 임시 저장 파일들이 거쳐갑니다. /usr 사용자들을 위한 여러가지 프로그램 및 설정 파일들.리눅스/유닉스 디렉토리 구조 중 가장 크고 복잡하며 루트(/)와 비슷한 구조를 가집니다./usr/bin, /usr/lib, usr/sbin 등 루트(/)에 들어있는 것들이 여기에 비슷하게 다 들어있으며, 패키지 등을 설치할 때 일반적으로 이쪽으로 저장됩니다. /var 로그 파일 및 시스템 관리와 관련된 파일들을 여기에 저장합니다. Install 후 기본 설치하는 것들 (개인 취향)$ apt-get update # 업데이트 저장소에서 업데이트 패키지 목록을 갱신$ apt-get upgrade # 패키지 목록과 보유한 목록을 비교하여 실제 패키지 업데이트를 수행$ apt-get install vim$ apt-get install curl$ apt-get install git$ apt-get install samba # Windows와의 파일 공유를 위해, https://help.ubuntu.com/lts/serverguide/samba.html$ apt-get install screen # 터미널 하나로 multi processing을 하기 위해. Openssh 설치$ apt-get install openssh-server ssh port 변경기본은 22번 port를 사용하지만 보안을 위해 맘에 드는 port를 정해서 변경하세요. 딱히 중요한 정보가 안 들어가고 공부용이면 대충 쓰세요. 문제 생기면 밀어버리면 되니까요. 관련해서 실무를 해본적이 없어서 보안 관리자들이 사용하는 절차에 대해서는 잘 모릅니다만 일반적으로(그럼 보안이 왜 필요한건지…)는 +10000 해서 10022번 port를 사용한다고 합니다. /etc/services 파일에서 ssh 22/tcp 및 ssh 22/udp 의 port 번호를 변경합니다. /etc/ssh/sshd_config 파일에서 아래 부분을 변경합니다.# What ports, IPs and protocols we listen for Port 22 변경 후에는 service ssh restart 명령을 통해 재시작합니다. 사용자 추가$ adduser gildonghong SAMBA 설정SAMBA 사용자 추가 smbpasswd -a 'username' SAMBA 설정 /etc/samba/smb.conf 를 열고 [homes] 부분을 다음과 같이 수정합니다. 더 자세한 설정 방법은 ubuntu - samba 페이지를 참조하세요. [Homes] comment = Home Directories browseable = yes # no -> yes path = /home/$S # 각 사용자가 자신의 home으로 접속되도록 변경 read only = no # yes -> no 설정이 끝났다면 service smbd restart 를 실행하여 samba daemon을 재시작합니다. Windows에서 시작 > 실행 > \\\\서버주소\\아이디 로 접근할 수 있습니다. 절전모드 시 네트워크가 끊기는 문제노트북 등이 절전모드(suspend 상태)로 전환되면 네트워크가 끊겨버립니다. 이에 대한 해결은 노트북 절전모드 진입 시 네트워크가 끊어지는 문제에서 확인하실 수 있습니다. 참조 http://wp.smartdisk.org/?p=179&lang=ko https://www.linux.co.kr/home2/board/subbs/board.php?bo_table=lecture&wr_id=1342 https://kjvvv.kr/2764 http://storycompiler.tistory.com/31 https://www.linux.co.kr/home/lecture/?leccode=10644","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docthread","slug":"docthread","permalink":"http://lazyrodi.github.io/tags/docthread/"},{"name":"ubuntu","slug":"ubuntu","permalink":"http://lazyrodi.github.io/tags/ubuntu/"}]},{"title":"Eclipse Setting","slug":"2016-08-08-docthread-setting-eclipse","date":"2016-08-08T13:00:50.000Z","updated":"2016-08-08T15:41:44.631Z","comments":true,"path":"2016/08/08/2016-08-08-docthread-setting-eclipse/","link":"","permalink":"http://lazyrodi.github.io/2016/08/08/2016-08-08-docthread-setting-eclipse/","excerpt":"","text":"지극히 개인적인 Eclipse setting 정리. Window > Preferences > General > Show heap status 체크 하단에 heap memory 용량을 표시해주고, 휴지통 버튼 누르면 바로 memory를 확보할 수 있다. Window > Preferences > General > Appearance > Theme 번경 취향따라. Dark Window > Preference > General > Workspace Text file encoding을 Other로 선택한 후 UTF-8로 변경 Tab을 Space 바꾸기 (Indent 설정) Neon을 설치하니까 자동으로 Tab이 Space 4칸으로 조정이 되어있다. (안 그랬었던 것 같은데… 뭐 좋은게 좋은거니… 엣헴) 관련 설정은 Window > Preferences > General > Editors > Text Editors에 가면 Displayed tab width 에서 칸 수를 설정하고 Insert spaces for tabs 옵션의 체크를 통해 Tab으로 넣을 것인지 Space로 넣을 것인지 선택이 가능.","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docthread","slug":"docthread","permalink":"http://lazyrodi.github.io/tags/docthread/"},{"name":"eclipse","slug":"eclipse","permalink":"http://lazyrodi.github.io/tags/eclipse/"},{"name":"setting","slug":"setting","permalink":"http://lazyrodi.github.io/tags/setting/"}]},{"title":"StringTokenizer","slug":"2016-08-07-java-stringtokenizer","date":"2016-08-07T04:01:50.000Z","updated":"2016-08-08T14:22:18.413Z","comments":true,"path":"2016/08/07/2016-08-07-java-stringtokenizer/","link":"","permalink":"http://lazyrodi.github.io/2016/08/07/2016-08-07-java-stringtokenizer/","excerpt":"","text":"Algospot에서 이 문제 (https://algospot.com/judge/problem/read/ZEROONE) 를 풀려다 보니 IO가 느려서 시간 초과가 계속 떴다. 리플들을 보니 StringTokenizer를 이용해 시간을 줄이려고 노력했다는 것을 보았는데 난 부끄럽게도 이 Class를 본 적이 없으므로 찾아보았다. 근데 IO랑 뭔 상관인지는 잘 모르겠다. 그냥 split보다 빠르게 하려고 StringTokenizer를 사용한 것 같다. 아무튼 이 글과 관련 없이 함수 별 IO 속도 차이를 보기 위해서는 여기를 참고하시라. 결론 StringTokenizer: 형식이 정해진 문자열을 분리할 때 사용. String.split(): 형식이 정해지지 않은 문자열을 분리할 때 사용. JDK1.4에 추가되었음. 속도: StringTokenizer > String.split() String.split()은 내부적으로 정규표현식을 사용하기 때문에 많은 처리가 필요할 경우 심하게 느려짐. 주의점 StringTokenizer를 사용할 경우 parsing되는 데이터가 공백이라면 부적절하다. 데이터에 뭐라도 채워져 있어야 한다. 예시로 보자123456789101112131415161718192021222324252627282930import java.util.StringTokenizer;public class Main { public static void main(String[] args) { String people[] = new String[] { \"Matthew, 01011111111, Male, Student\", \"David, 01022222222, , Swimmer\", \"Maria, 01033333333, Female, Student, \" }; for (String a : people) { StringTokenizer st = new StringTokenizer(a, \", \"); String[] temp = a.split(\", \"); System.out.println(\"----------\"); System.out.println(\"** StringTokenizer\"); while (st.hasMoreElements()) { System.out.println(st.nextToken()); } System.out.println(\"** String.split()\"); for (String b : temp) { System.out.println(b); } System.out.println(\"----------\"); } }} 결과는 이렇다David에 대한 데이터를 보면 성별 정보를 빈 칸으로 해놓으니 StringTokenizer에서는 이를 무시해버렸다. Mariad의 마지막 데이터도 마찬가지이다. 123456789101112131415161718192021222324252627282930313233343536----------** StringTokenizerMatthew01011111111MaleStudent** String.split()Matthew01011111111MaleStudent--------------------** StringTokenizerDavid01022222222Swimmer** String.split()David01022222222Swimmer--------------------** StringTokenizerMaria01033333333FemaleStudent** String.split()Maria01033333333FemaleStudent ---------- 참조 http://okky.kr/article/32363 http://stylekai.tistory.com/105","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"java","slug":"Dev/java","permalink":"http://lazyrodi.github.io/categories/Dev/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://lazyrodi.github.io/tags/java/"},{"name":"stringtokenizer","slug":"stringtokenizer","permalink":"http://lazyrodi.github.io/tags/stringtokenizer/"}]},{"title":"Eclipse를 이용한 Python 사용","slug":"2016-08-05-docthread-setting-python-into-eclipse","date":"2016-08-05T13:00:50.000Z","updated":"2016-08-08T14:22:11.212Z","comments":true,"path":"2016/08/05/2016-08-05-docthread-setting-python-into-eclipse/","link":"","permalink":"http://lazyrodi.github.io/2016/08/05/2016-08-05-docthread-setting-python-into-eclipse/","excerpt":"","text":"PyDev 설치Help > Install new software > Add > Name: PyDevLocation: http://pydev.org/updates pyDev 체크 후 > Next > Next > I accept the … > Finish > Brainwy Software; pyDev; Brainwy 를 체크하여 certificate하고 > OK > restart OK New Project 생성할 때 PyDev Project를 선택할 수 있게 됨. 그냥 만들려고 하면 Please configure an interpreter before procedding.이 뜸. 아래 설정 참조. python interpreter 설정우선, http://python.org/ 에서 python을 PC에 설치하고. Window > Preference > PyDev > Interpreters > Python Interpreter 우 상단의 Quick Auto-Config를 선택하면 python path를 알아서 찾아줌. 이후 Apply > OK 다시 New Project 생성으로 가서 생성한다. Python Perspective 어쩌고 팝업 나오면 OK 해서 새 perspective를 추가한다. test.py 등으로 파일 생성하고, 코드 넣고, Ctrl + F11 로 빌드해서 아래쪽 콘솔에서 확인할 수 있다. 굳.","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docthread","slug":"docthread","permalink":"http://lazyrodi.github.io/tags/docthread/"},{"name":"python","slug":"python","permalink":"http://lazyrodi.github.io/tags/python/"},{"name":"eclipse","slug":"eclipse","permalink":"http://lazyrodi.github.io/tags/eclipse/"},{"name":"setting","slug":"setting","permalink":"http://lazyrodi.github.io/tags/setting/"}]},{"title":"직장생활","slug":"2016-08-05-docthread-worklife","date":"2016-08-05T10:47:50.000Z","updated":"2016-08-10T14:42:32.528Z","comments":true,"path":"2016/08/05/2016-08-05-docthread-worklife/","link":"","permalink":"http://lazyrodi.github.io/2016/08/05/2016-08-05-docthread-worklife/","excerpt":"","text":"입사새로운 입사자를 환영하는 방법, 웰컴키트 조직문화“1분도 아까워” 현대차 직원들의 점심 레이스zdnet - 문제 직원은 없고, 문제상사만 있습니다 업무직장에서 깨지며 배운 100% Real 보고서 스킬세계적인 기업 이끄는 CEO들의 사무실 10 퇴사두 번째 퇴사","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/tags/docs/"},{"name":"hire","slug":"hire","permalink":"http://lazyrodi.github.io/tags/hire/"},{"name":"fire","slug":"fire","permalink":"http://lazyrodi.github.io/tags/fire/"},{"name":"work","slug":"work","permalink":"http://lazyrodi.github.io/tags/work/"}]},{"title":"Codility","slug":"2016-08-05-common-codility","date":"2016-08-04T23:02:50.000Z","updated":"2016-08-09T10:40:00.000Z","comments":true,"path":"2016/08/05/2016-08-05-common-codility/","link":"","permalink":"http://lazyrodi.github.io/2016/08/05/2016-08-05-common-codility/","excerpt":"","text":"https://codility.com/programmers/lessons/ Iterations프로그램 내에서 반복되는 부분. 보통 for나 while. For12345678for some_variable in range_of_values: loop_bodyfor i in range(0, 100) : print ifor i in range(100) : print i While123456789101112131415while some_condition: loop_bodyresult = 0while n > 0: n = n // 10 result += 1a = 0b = 1while a <= n: print a c = a + b a = b b = c Collection을 사용한 반복12345678910111213141516171819202122232425262728293031323334days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']for day in days: print daydays = set(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])for day in days: print day// set을 사용할 경우 출력 순서가 set에서 지정한 순서대로 나오지는 않는다.''' Monday Tuesday Friday Wednesday Thursday Sunday Saturday'''// key-value 구조로 사용할 수도 있다.days = {'mon': 'Monday', 'tue': 'Tuesday', 'wed': 'Wednesday', 'thu': 'Thursday', 'fri': 'Friday', 'sat': 'Saturday', 'sun': 'Sunday'}for day in days: print day, 'stand for', days[day]'''wed stands for Wednesdaysun stands for Sundayfri stands for Fridaytue stands for Turesdaymon stands for Mondaythu stands for Thursdaysat stands for Saturday''' 문제BinaryGappython1차: 45점 (이 때는 잘 몰라서 링크 안 남겨둠)2차: 100점 - https://codility.com/demo/results/training8TW2WC-EH5/ Arrays하나의 공간에 여러 아이템을 저장할 수 있는 자료구조. Creation123shopping = ['bread', 'butter', 'cheese']shopping = [] # empty listshopping = [0] * 365 # value가 0인 365개의 array Accesing and Modifying12345shopping[1]temperatures[42] = 25shopping += ['eggs'] # 이런 식으로 array에 추가 가능. Iterating1234567def negative(temperatures): N = len(temperatures) days = 0 for i in xrange(N): if temperatures[i] < 0: days += 1 return days 위의 코드를 아래처럼 간단히 할 수 있다. 123456def negative(temperatures): days = 0 for t in temperatures: if t < 0: days += 1 return days Basic array operations1234567len([1, 2, 3]) == 3['Hello'] * 3 == ['Hello', 'Hello', 'Hello'][1, 2, 3] + [4, 5, 6] == [1, 2, 3, 4, 5, 6]'butter' in ['bread', 'butter', 'cheese'] == True 연습하기12345678def reverse(A): N = len(A) for i in xrange(N // 2): k = N - i - 1 A[i], A[k] = A[k], A[i] return AA.reverse() # python native 문제CyclicRotationpython1차: 37점 (이 때는 잘 몰라서 링크 안 남겨둠)2차: 100점 - https://codility.com/demo/results/training35CKZE-B4V/ OddOccurrencesInArraypython: 100점 - https://codility.com/demo/results/trainingR3ZUNQ-JXX/ Time ComplexityBIG O Complexity에 따로 정리.### TapeEquilibriumpython: 100점 - https://codility.com/demo/results/trainingWJH42B-PFB/### FrogJmppython: 100점 - https://codility.com/demo/results/training8A2TYQ-AAP/### PermMissingElempython: 100점 - https://codility.com/demo/results/trainingZDKEVT-JMK/# Counting ElementsO(n + m) 복잡도의 counting 알고리즘.배열 A에 들어있는 각 integer값이 몇 번이나 나오는지 count 배열에 카운팅한다.A = [1, 2, 3, 1, 3, 5] 라면, count = [0, 2, 1, 2, 0, 1] 이 된다.123456def counting(A, m): n = len(A) count = [0] * (m + 1) for k in xrange(n): count[A[k]] += 1 return count 다음과 같은 문제가 있다. A, B 두 개의 배열과 integer m이 주어지고, 각 원소 n은 0 <= n <= m 의 크기를 갖는다. 배열 A, B 에서 각각 하나의 원소를 swap하여 배열 A, B 각각의 합이 같은지 확인하고 아니면 다른 원소를 swap해보는 동작을 구현한다고 하자. 가장 간단한 방법은 모든 쌍을 swap하여 계속 비교하는 것이다. 이 경우 O(n^3)의 복잡도를 갖는다. 이보다 조금 나은 방법으로 처음에 전체의 합을 구한 뒤 swap 시 전체 합이 어떻게 변할지에 대해 체크하는 방법(아래 코드, O(n^2))이 있다. (실제로 swap이 발생하진 않는다.) 1234567891011121314def slow_solution(A, B, m): n = len(A) sum_a = sum(A) sum_b = sum(B) for i in xrange(n): for j in xrange(n): change = B[j] - A[i] sum_a += change sum_b -= change if sum_a == sum_b: return True sum_a -= change sum_b += change return False 위의 방법보다 더 좋은 방법은 배열 A 원소의 수를 세고 배열 A와 B 요소들의 합의 차 d를 계산하는 방법이다. 두 배열의 합의 차이인 d는 우리가 배열 A에서 어떤 값을 swap해야 하는지 알려준다. 왜냐하면 하나의 값을 swap할 때 두 개의 합이 동일해지기 때문이다. 배열을 카운팅 한 후 이 값을 구하는 동작은 상수 시간 내에 가능하다. 12345678910111213def fast_solution(A, B, m): n = len(A) sum_a = sum(A) sum_b = sum(B) d = sum_b - sum_a if d % 2 == 1: return False d //= 2 count = counting(A, m) for i in xrange(n): if 0 <= B[i] - d and B[i] - d <= m and count[B[i] - d] > 0: return True return False PermCheckpython1차: 30점 - https://codility.com/demo/results/trainingHBR2ZE-W52/2차: 70점 - https://codility.com/demo/results/trainingDW7FCK-495/ 아직 paneless 문제인데 여기부터 벌써 한계가 찾아오다니… ㅠㅠ MissingIntegerpython1차: 22점 - https://codility.com/demo/results/training2P6BG5-N6S/ FrogRiverOnepython1차: 54점 - https://codility.com/demo/results/training3K6KVK-6NB/2차: 100점 - https://codility.com/demo/results/trainingU2PU2U-K5K/ MaxCounters","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"common","slug":"Dev/common","permalink":"http://lazyrodi.github.io/categories/Dev/common/"}],"tags":[{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"interview","slug":"interview","permalink":"http://lazyrodi.github.io/tags/interview/"},{"name":"codility","slug":"codility","permalink":"http://lazyrodi.github.io/tags/codility/"}]},{"title":"Hexo 한국어 번역","slug":"2016-08-02-works-translate-hexo-site","date":"2016-08-02T10:02:50.000Z","updated":"2016-08-08T14:22:00.069Z","comments":true,"path":"2016/08/02/2016-08-02-works-translate-hexo-site/","link":"","permalink":"http://lazyrodi.github.io/2016/08/02/2016-08-02-works-translate-hexo-site/","excerpt":"","text":"Hexo.io에 한국어 설명이 없어서 직접 번역해 보았습니다. 사이트 접속 후 우 상단에서 언어를 한국어로 변경하시거나 https://hexo.io/ko/로 접속하시면 결과물을 확인하실 수 있습니다. 영어는 잘 못하지만 구글 번역기가 절 구원하시고 양이 많지 않아 하루면 끝날 줄 알았는데 꽤 많은 시간이 소요되었습니다. 매뉴얼 또는 튜토리얼 그리고 회사에서는 규격을 번역하는 일은 스스로에게 굉장히 도움이 되는 일이라고 생각하고 있습니다. 번역을 해보면서 얻을 수 있는 장점은 무궁무진합니다. 따로는 절대 공부하지 않을 영어를 직접 마주할 수 있고 한 번 정리해 둠으로써 다음에는 문서를 편하게 볼 수 있습니다. 심지어 자기 말투로 번역해두었기 때문에 남들의 번역서보다 가독성도 좋습니다(남들은 번역본을 욕할지언정). ‘이런 때에는 이런 단어와 표현을 사용하는구나’를 배울 수 있습니다. 작업을 완료했을 때 성취감을 얻을 수 있습니다. 완벽하진 않지만 번역을 통해 문서가 설명하려고 하는 규격이나 동작 방식 등을 전부 다 훑어봄으로써 개요를 파악할 수 있습니다. 그리고 이 문서의 어디쯤 무슨 내용이 있었는지 기억하여 검색을 편하게 할 수 있게되죠. 아마 제가 설명하지 못한 장점이 더욱 더 많이 있을겁니다. Hexo의 번역을 생각하게 된 이유는 우선 Hexo가 굉장히 맘에 들어서 사용하게 되었기 때문이고, 제가 가지고 있는 안 좋은 습관 떄문입니다. 영문을 매끄럽게 읽어나가지 못하기 떄문에 일 할 때에도 한글로 한 번 번역을 하는 과정을 거치고 그 다음에 내용을 머리로 생각하는 것이 안 좋은 습관입니다. 역량 부족이라고 해두겠습니다. 결론은 한글로 된 문서를 편하게 보고 싶었습니다. 번역을 할 때 어려움을 겪은 부분들을 정리해보면 다음과 같습니다. file, thread, method, database 등의 단어를 영어로 놔둘 것인지 ‘파일’, ‘스레드’, ‘메소드’, ‘데이터베이스’ 등으로 한글로 변환할지에 대한 고민. 마찬가지로 processing, reder, iterate, command 등의 표현을 어떻게 다룰 것인지에 대한 고민. 직역을 할지 의역을 할지에 대한 고민. 장난스런 말투를 사용할지 딱딱한 말투를 사용할지에 대한 고민. 해석도 안 되는데 구글 번역기도 날 도와주지 않을 때. 욕 먹을 고민부터 해야하는거 아니냐 하실 수도 있지만 스스로가 잘 못하는 것을 알기 때문에 그 또한 저한테 공부가 될 것이라 생각합니다. 위의 과정을 마주하면서 스스로 굉장히 많이 생각하게 되어 발전할 수 있는 기회가 되었습니다. 해결책도 이 공간에 턱하니 올려두면 좋겠지만 미숙해서 아직 답을 찾지 못하였습니다. 여러 번 시행착오를 겪으면 되지 않을까 싶습니다. 이미 나이를 많이 먹었지만 여하튼 번역에 대한 결과물은 다음과 같습니다. 원작자가 Pull Request를 받아줄 때 쾌감이 상당했습니다. 물론 코드가 아닌 번역이고, 원작자가 한국어를 모르니 검증 과정은 까다롭지 않았을 것이라 생각합니다. https://github.com/hexojs/site/pull/286 큰 일이건 작은 일이건 기분 좋은 경험이었습니다.","categories":[{"name":"works","slug":"works","permalink":"http://lazyrodi.github.io/categories/works/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://lazyrodi.github.io/tags/hexo/"},{"name":"translate","slug":"translate","permalink":"http://lazyrodi.github.io/tags/translate/"},{"name":"korean","slug":"korean","permalink":"http://lazyrodi.github.io/tags/korean/"}]},{"title":"GIT command for me","slug":"2016-07-24-etc-git","date":"2016-07-24T01:02:50.000Z","updated":"2017-08-27T09:17:27.497Z","comments":true,"path":"2016/07/24/2016-07-24-etc-git/","link":"","permalink":"http://lazyrodi.github.io/2016/07/24/2016-07-24-etc-git/","excerpt":"","text":"간단한 GIT command 정리.회사에서는 형상관리팀이 존재하고, repo와 gerrit을 사용하여 편하게 소스 관리를 하고있다. Command Description git config --global user.name <name> 사용자 이름을 설정한다. git config --global user.email <email> 사용자 이메일을 설정한다. git clone <url> 저장소(remote)로부터 소스를 가져온다. git branch <new-branch> Local에 새로운 branch를 만든다. git branch Local에 있는 branch들을 확인할 수 있다. git checkout <branch> 또는 <filename> <branch>를 사용할 경우 branch를 변경하고, <filename>인 경우 add를 통해 stage에 올린 file을 내릴 수 있다. git push origin <branch name> Local에서 생성한 branch를 저장소에 push한다. git diff 현재 수정사항이 무엇을 변경한 것인지 확인할 수 있다. git status 수정한 파일들의 현재 상태를 나타낸다. git add -A 수정한 소스들을 stage area에 올린다. git commit [-m “description”] 새 commit을 생성한다. -m 옵션을 사용하면 한 줄 짜리 commit 내용을 만들 수 있다. git commit --amend 한 번 commit 작성 후 다른 commit을 생성하고 싶지 않을 때, 수정하고 git add -A하고 git commit --amend를 하면 commit id를 새로 생성하지 않아도 된다. git push 작성한 commit(들)을 저장소로 업로드한다. git revert <commit id> push한 commit을 제거한다. git log [–author <username>] <username>이 반영한 commit들을 볼 수 있다. git blame <filename> <filename> line 별로 누가 반영한 것인지 확인할 수 있다. git merge <source branch> <source branch>의 내용을 현재 branch에 merge한다. git reset [–hard HEAD^] 강제로 HEAD^ 상태로 돌린다. HEAD^ 에는 commit id를 넣어도 된다. 그리고 아래는 Github를 사용하면서 배운 내용이다.원본 프로젝트를 fork해온 후 원본 프로젝트의 변경 사항을 merge하는 방법순차적으로 수행하면 된다. Command Description git remote add –track master upstream 원본저장소.git git remote는 새로운 원격 저장소를 추가하는 명령어이다. 원본 저장소를 upstream으로 등록한다. git fetch upstream upstream(원본 저장소)으로부터 data를 받아온다. git merge upstream/master git push 별도의 옵션을 붙이지 않으면 내 저장소로 변경 사항(merge)을 push한다. 참조 repo gerrit Atlassian GIT Cheatsheet http://www.cheat-sheets.org/saved-copy/git-cheat-sheet-v2.pdf http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf http://www.cheat-sheets.org/saved-copy/Git_Cheat_Sheet_grey.pdf http://www.cheat-sheets.org/saved-copy/github-git-cheat-sheet.pdf","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"git","slug":"git","permalink":"http://lazyrodi.github.io/tags/git/"}]},{"title":"SVG (Scalable Vector Graphic)","slug":"2016-07-23-etc-svg","date":"2016-07-23T12:02:50.000Z","updated":"2016-08-08T14:21:53.220Z","comments":true,"path":"2016/07/23/2016-07-23-etc-svg/","link":"","permalink":"http://lazyrodi.github.io/2016/07/23/2016-07-23-etc-svg/","excerpt":"","text":"Jekyll이나 Hexo를 이용할 때 SVG 포맷을 처음 보게 되어서 한 번 찾아보았다. SVG는 Scalable Vector Graphic 의 약자로 XML을 통해 Vector Image(단순하게 말해서 확대해도 깨지지 않는 이미지)를 표현할 수 있게 정의된 포맷이다. W3C에서 SVG를 정의하고 있고 Tutorial도 잘 정리해 두었다. 사용법은 굉장히 단순하게 보이지만 파고들면 역시나 쉽지 않다. (물론 시간만 있다면 Tutorial만 보고도 뭐든 그릴 수 있을 것 같긴 하다.) 뭘 그려볼까 하다가 LG의 CI를 그려보았다. 구성요소는 다음과 같다. 배경이 되는 빨간 circle G의 기본이 되는 하얀 circle 우 상단 1/4 영역의 하얀 색을 지우기 위한 mask역할의 path G의 가로줄 line 코 역할의 L을 그려줄 polyline 아래의 소스를 ~~~.svg 로 저장하고 html에서 <img> tag를 통해 사용할 수 있다. 12345678910111213141516171819<?xml version=\"1.0\" encoding=\"utf-8\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"80\" height=\"80\"> <!-- background circle --> <circle cx=\"40\" cy=\"40\" r=\"40\" fill=\"#C3013B\" /> <!-- inner circle --> <circle cx=\"40\" cy=\"40\" r=\"32\" style=\"fill:none;stroke:#FFFFFF;stroke-width:3\" /> <!-- masking --> <path d=\"M40,8 A32,32 0 0,1 72,39\" style=\"stroke:#C3013B;stroke-width:4;fill:none\" /> <line x1=\"50\" y1=\"40\" x2=\"73.5\" y2=\"40\" style=\"stroke:#FFFFFF;stroke-width:3\" /> <!-- eye --> <circle cx=\"25\" cy=\"27\" r=\"5\" fill=\"#FFFFFF\" /> <!-- L --> <polyline points=\"40,22 40,58 50,58\" style=\"fill:none;stroke:#FFFFFF;stroke-width:3\" /></svg> 그림의 결과는 아래와 같다. 참조 SVG Specification SVG Tutorial http://tutorials.jenkov.com/svg/index.html https://css-tricks.com/scale-svg/","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"svg","slug":"svg","permalink":"http://lazyrodi.github.io/tags/svg/"},{"name":"image","slug":"image","permalink":"http://lazyrodi.github.io/tags/image/"},{"name":"scalable","slug":"scalable","permalink":"http://lazyrodi.github.io/tags/scalable/"},{"name":"vector","slug":"vector","permalink":"http://lazyrodi.github.io/tags/vector/"},{"name":"graphic","slug":"graphic","permalink":"http://lazyrodi.github.io/tags/graphic/"}]},{"title":"Android Interview","slug":"2016-07-19-interview-android","date":"2016-07-19T10:00:00.000Z","updated":"2016-08-09T07:29:09.885Z","comments":true,"path":"2016/07/19/2016-07-19-interview-android/","link":"","permalink":"http://lazyrodi.github.io/2016/07/19/2016-07-19-interview-android/","excerpt":"","text":"Android application을 생성할 때 중요한 폴더 및 파일을 설명하라.ValueDescriptionsrc.java파일을 포함한다.genCompiler가 자동으로 생성하는 Resource파일인 R.java 파일을 가지고 있다. 이 파일은 수정해서는 아니된다.libraryandroid.jar 파일을 포함한다. 해당 Android application에 대한 library들이다.assetsHTML, text, database등의 파일을 포함한다.bin빌드 후 생성되는 .apk 파일을 포함한다.resApplication에서 사용하는 리소스 파일들을 포함한다. 다음 AndroidManifest.xml 파일을 설명하라. Package 이름(@string/app_name)을 포함한다. Version code가 1이고 이는 어플리케이션의 version(몇 번째 release인지)을 나타낸다. Version name이 1.0임을 알 수 있다. android.minSdkVersion이 8이고 이는 이 어플리케이션이 실행될 수 있는 Android OS의 최소 version을 나타낸다. MainActivity에 대한 activity 정보를 확인할 수 있다. Intent-filter를 통해 Main임을 확인할 수 있다. <?xml version=\"1.0\" encoding=\"utf-8\"?><manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.example.careerride\" android:versionCode=\"1\" android:versionName=\"1.0\"><uses-sdk android:minSdkVersion=\"8\" android:targetSdkVersion=\"18\" /><application android:allowBackup=\"true\" android:icon=\"@drawable/ic_launcher\" android:label=\"@string/app_name\" android:theme=\"@style/AppTheme\"><activity android:name=\"com.example.careerride.MainActivity\" android:label=\"@string/app_name\"><intent-filter><action android:name=\"android.intent.action.MAIN\" /><category android:name=\"android.intent.category.LAUNCHER\" /></intent-filter></activity></application></manifest> Activity에 대해 간단히 설명하라.Activity는 User interface를 보여준다. Android application 생성 시 하나의 activity가 생성되며, 기본 이름은 MainActivity이며 맘대로 이름을 붙일 수 있다. 기본적으로 Activity class를 상속받는다. Android Application은 0개 이상의 Activity를 가질 수 있으며 사용자와의 interaction을 위해 사용한다. Activity는 onCreate()로 시작하여 onDestroy()로 끝나는 Lifecycle을 가지고 있다. packagecom.example.careerride; //Application name careerrideimportandroid.os.Bundle; // Default packagesimportandroid.app.Activity; // Default packagesimportandroid.view.Menu;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);}@OverridepublicbooleanonCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true;}} Android Architecture의 key component는 무엇인가? Linux Kernel Libraries Android Framework Android Applications Intent에 대해 설명하라.Android의 기본 구성요소(Component)에는 Acivity, Service, ContentProvider, BroadcastReceiver 네 가지가 있는데 이 중 Activity, Service, BroadcastReceiver 사이에 data를 주고 받기 위해 intent를 사용한다. 다른 activity를 호출하거나 service를 시작하거나 broadcast할 수 있다. Intent는 명시적 intent와 암시적 intent 두 가지로 나눌 수 있다. 명시적 intent는 intent를 전달할 class 정보를 포함한다. Intent intent = newIntent (this, SecondActivity.class);startActivity(intent); 암시적 intent는 동작을 원하는 값을 action값으로 하여 URI, MIME type, category 등과 함께 던지면 Android system에서 적절한 application을 찾아서 원하는 동작을 시켜준다. 적절한 application을 찾는 과정에서 다른 application 내의 intent-filter를 검색하여 매칭되는지 확인한다. 암시적 intent를 던질 때 call, sms 등과 같은 특정한 동작을 원할 때에는 자신이 그 동작을 수행할 수 있는 permission을 가지고 있어야 한다. Intent i = newIntent(android.content.Intent.ACTION_VIEW,Uri.parse(“http://www.amazon.com”));startActivity(i);/**/Intent i = newIntent (android.content.Intent.ACTION_DIAL,Uri.parse(“tel:+9923.....”));startActivity(i);/**/Intent i = newIntent (android.content.Intent.ACTION_CALL,Uri.parse(“tel:+9923.....”));startActivity(i);/* permission */<uses-permissionandroid:name=”android.permission.CALL_PHONE”/><uses-permissionandroid:name=”android.permission.INTERNET”/> Android에서 SMS를 발신하기 위해서는 어떻게 해야 하는지 설명하라. “send sms” Button을 생성한다. AndroidManifest.xml 에 permisson을 설정한다. 다음과 같은 메소드를 만들어서 SMS를 발신한다.publicvoidsendmySMS(View v){ SmsManager sms = SmsManager.getDefault(); sms.sendTextMessage(\"5556\", null, \"Hello from careerRide\", null, null);} SMSManager class에 대해 설명하라.SMS를 발신할 때 사용하는 class이며, 직접적으로 초기화하여 사용할 수 없고 getDefault()를 사용해야 한다. 발신하는 메소드는 sendTextMessage()인데 다음과 같은 파라미터들을 갖는다. destinationAddress: 수신자의 전화번호 scAddress: Service center address (null로 설정해도 된다.) text: 메시지 내용 sentIntent: 메시지가 발신되었음을 알려주는 intent. deliveryIntent: 메시지가 상대방에게 도착하였음을 알려주는 intent. 어플리케이션에서 delivery status option을 체크해줘야 한다. publicvoidsendmySMS(View v){ SmsManager sms = SmsManager.getDefault(); sms.sendTextMessage(\"5556\", null, \"Hello from careerRide\", null, null);} 당신의 application에서 어떻게 Messaging application을 사용할 수 있겠는가?SMS 발신 permission을 얻은 후, Intent를 던지면 Default SMS Application으로 설정되어 있는 Application을 통해 SMS를 발신할 수 있다. Intent intent = new Intent (android.content.Intent.ACTION_VIEW);intent.putExtra(\"address\", \"5556; 5558;\"); // Send the message to multiple recipient.itent.putExtra(\"sms_body\", \"Hello my friends!\");intent.setType(\"vnd.android-dir/mms-sms\");startActivity(intent); Android에서 사용할 수 있는 Data storage에는 어떤 것들이 있는가? SharedPreferences SQLite ContentProvider File Storage Cloud Storage SharedPreference에 대해 예시와 함께 설명하라.SharedPreference는 Android storage 중 가장 간단한 방법을 가진다. Data를 XML 파일에 저장하는데 Key-Value pair로 한다. Primitive data type을 사용할 수 있다. (boolean, float, int, long, string) XML 파일은 data/data/자신의 패키지명(com.a.b)/shared-prefs/ 디렉토리에 저장된다. A Activity에서 저장한 내용도 B Activity에서 가져다가 사용할 수 있다. 저장SharedPreferences sf = getSharedPreferences(\"MyData\", MODE_PRIVATE); // \"Mydata\" XML file, MODE_PRIVATE은 이 어플리케이션에서만 사용하겠다는 의미.SharedPreferences.Editor ed = sf.edit();ed.putString(\"name\", txtusername.getText().toString());ed.putString(\"pass\", txtpassword.getText().toString());ed.commit(); 읽어오기getString(value, default) 형식으로 읽어오면 되며, value가 SharedPreference에 존재하지 않을 때 “default” 값을 가져온다. 그리고 아래 코드에서는 name이나 pass 중 하나라도 못 읽어오면 “No data is found” Toast를 발생시킨다. public static final String DEFAULT = \"N/A\";SharedPreferences sf = getSharedPreferences(\"MyData\", Context.MODE_PRIVATE);String Uname = sf.getString(\"name\", DEFAULT);String UPass = sf.getString(\"pass\", DEFAULT);if(name.equals(DEFAULT) || Pass.equals(DEFAULT)) { Toast.makeText(this, \"No data is found\", Toast.LENGTH_LONG).show();} else { Txtusername.setText(Uname); Txtpassword.setText(UPass) ;} 출처 http://www.careerride.com/android-interview-questions.aspx","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"interview","slug":"Dev/interview","permalink":"http://lazyrodi.github.io/categories/Dev/interview/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"interview","slug":"interview","permalink":"http://lazyrodi.github.io/tags/interview/"}]},{"title":"Intent","slug":"2016-07-17-android-intent","date":"2016-07-16T23:00:00.000Z","updated":"2016-08-08T14:21:40.268Z","comments":true,"path":"2016/07/17/2016-07-17-android-intent/","link":"","permalink":"http://lazyrodi.github.io/2016/07/17/2016-07-17-android-intent/","excerpt":"","text":"Android의 기본 Application component에는 다음 네 가지가 있다. ComponentsDescriptionActivity사용자 인터페이스가 있는 화면 하나를 뜻한다.ServiceBackground에서 실행되는 component이다.오랫동안 실행되는 작업이나 원격 프로세스를 위한 작업이 주로 이루어지며 사용자 인터페이스를 제공하지 않는다.(ex. 음악 재생, 파일 다운로드)Content ProviderFile system, Database 등의 저장소를 통합 관리한다.권한이 허가된 경우 다른 application에서 접근 및 수정까지도 가능하다.Broadcast RecieverSystem이나 application은 특정한 상태나 data에 대해 broadcast를 날릴 수 있는데 이를 수신하여 처리한다. 위 기본 요소 중 Activity, Service, Broadcast Reciever는 Intent 라는 비동기 메시지가 전달되어 활성화된다. IntentMessage Object의 일종으로 intent를 사용하여 다른 component들에게 작업을 요청할 수 있다. 기본적으로는 다음 세 가지 목적으로 사용한다. Activity의 시작 Service의 시작 Broadcast의 전달 Activity의 시작 Intent를 startActivity()로 전달하면 새 activity가 실행된다. Activity의 실행 결과를 돌려받고 싶다면 startActivityForResult()를 호출한다. 돌려받은 결과는 onActivityResult()를 구현하여 처리할 수 있다. 결과 또한 intent로 수신한다. Service의 시작 Intent를 startService()로 전달하면 일회성 작업이 수행된다. Client-Server interface로 설계된 service라면 intent를 bindService()로 전달하면 바인딩하여 사용할 수 있다. Broadcast의 전달 Intent를 sendBroadcast(), sendOrderedBroadcast(), sendStickyBroadcast() 중 하나에 담아서 전달한다. 모든 application들에 message를 전달할 때 사용한다. (system에서 전달할 때는 특정 application을 지정해서 전달할 수도 있다.) Intent의 기본 요소ComponentName, Action, Data, Category, Extra, Flags 가 있다. ComponentNameOptional 항목으로 Implicit intent로 사용할 경우 반드시 이름을 명시해야 한다. Service를 시작하는 경우에는 무조건 이 항목을 지정해야 한다. 그렇지 않으면 해당 intent에 어느 service가 응답할지 확신할 수 없고 사용자고 어떤 service가 시작되는지 알 수 없게 된다. ComponentName으로는 application의 패키지명이 포함된 Full-Qualified class name을 사용해야 한다. (ex. com.example.Example.Activity). Intent 생성자를 사용하거나 setComponent(), setClass(), setClassName()을 사용하여 ComponentName을 설정할 수 있다. Action수행할 작업을 나타내며, 특정 application에서 커스터마이징한 action name을 사용할 수도 있지만 일반적으로는 Intent class나 다른 Framework class가 정의한 Action 상수를 사용한다. 커스터마이징할 경우 패키지명을 Prefix(접두사)에 포함시켜야 한다. static final String ACTION_TIMETRAVEL = \"com.example.action.TIMETRAVEL\"; Activity를 시작할 때 아래 두 가지 ACTION을 사용할 수 있다. ACTION_VIEW startActivity()를 사용하며, 해당 Activity가 사용자에게 표시할 정보를 가지고 있을 때(ex. 갤러리) 사용한다. ACTION_SEND Shared intent라고도 하며, startActivity()를 사용한다. 사용자가 다른 application을 통해 공유할 수 있는 data를 가지고 있을 때(ex. 이메일) 사용한다. Intent 생성자를 사용하거나 setAction()을 사용하여 Action을 설정할 수 있다. DataAction을 수행할 data 또는 해당 data의 MIME type을 참조하는 URI Object이다. 일반적으로 action명을 보면 data를 추측할 수 있는데 예를 들어 action이 ACTION_EDIT라면 data에는 편집할 문서의 URI가 들어있어야 한다. Intent 생성 시 URI와 함께 Data type(MIME type)의 지정이 중요하다. 만약 이미지 처리용 application이 있을 때, URI가 비슷하다 할지라도 이 application에서는 오디오 처리를 할 수는 엇다.따라서 MIME type을 지정해주는 것이 좋다. Data URI만 설정하려면 setData()를 사용하고, MIME type만 설정하려면 setType()을 사용하면 된다. 두 가지 모두 사용할 경우 setData()와 setType()은 서로의 값을 덮어버리는 특성이 있으므로 반드시 setDataAndType()을 사용해야 한다. 일반적으로 Action과 Data는 다음과 같은 짝을 갖는 경우가 많다. ActionURIDescriptionACTION_VIEWcontent://contacts/people/11번 사람에 대한 정보를 표시한다.ACTION_DIALcontent://contacts/people/1Dialer에 1번 사람의 번호를 채워서 보여준다.ACTION_VIEWtel:123Dialer에 ‘123’을 채워서 보여준다.ACTION_DIALtel:123Dialer에 ‘123’을 채워서 보여준다.ACTION_EDITcontent://contacts/people/11번 사용자에 대한 정보를 수줭한다.ACTION_VIEWcontent://contacts/people/주소록 리스트를 띄운다. 이 리스트에서 특정 사용자를 선택할 경우 ACTION_VIEW content://contacts/N이 시작된다. 위의 두 가지 주 속성에 더하여, 아래에 설명할 몇 가지의 부 속성을 사용할 수 있다. CategoryOptional 항목이며 Intent를 처리해야 하는 component에 대한 추가 정보를 담고있다. 몇 가지 보편적인 Category의 예이다. CATEGORY_LAUNCHER Application 진입 시 최초의 Activity임을 의미한다. CATEGORY_ALTERNATIVE Data의 일부에 대해 사용자가 사용할 수 있는 대체 action들에 대한 목록을 포함할 것을 의미한다. CATEGORY_BROWSABLE 대상 Activity가 스스로 Web browser에게 자신을 시작할 권한을 주며, link를 통해 참조된 data를 표시하게 한다. 이미지, 이메일, 메시지 등이 해당된다. Category 지정은 addCategory()를 통해서 설정한다. Type Intent data의 명시적인 type(MIME type)을 정의한다. 일반적인 경우 type은 data 자신으로부터 추측할 수 있다. Component Intent에 사용하기 위한 component class의 명시적인 이름을 정의한다. 일반적으로 이 항목은 intent 내의 다른 정보(action, data/type, category)에 의해 정의되고 이를 다룰 수 있는 component에 매칭된다. 만약 이 속성이 세팅될 경우 아무 동작도 수행하지 않으며 이 component는 그 자체로만 사용된다. 이 속성을 정의함으로 인해 모든 다른 Intent 속성들은 optional 항목이 된다. Extras요청한 작업을 수행하기 위한 추가 정보를 담고 있다. Key-Value pair로 이루어져 있다. putExtras() 메소드를 사용하며, 모든 extra data를 갖는 Bundle을 생성하여 직접 putExtras()로 삽입할 수도 있다. 예를 들어, ACTION_SEND로 이메일을 전송할 경우, “받는 사람”을 EXTRA_EMAIL로 지정하고, “제목”을 EXTRA_SUBJECT로 할 수 있다. Intent class는 표준화된 data type들에 대해 많은 EXTRA_*를 지원하고 있다. 자신의 application만의 특정 extra key를 사용해야 할 경우 Package name을 prefix(접두사)로 포함시켜야 한다. static final String EXTRA_GIGAWATTS = \"com.example.EXTRA_GIGAWATTS\"; FlagsIntent class에서 정의하고 있으며, Meta-data 역할을 수행한다. Android system에 activity를 시작할 방법을 알려줄 수도 있고, activity를 시작한 후 어떻게 처리해야 하는 지도 알려줄 수 있다. 지정은 setFlags() 로 하면 된다. Intent의 typeIntent type은 Explicit, Implicit 두 가지가 있다. Explicit intent는 무엇을 할지, Implicit intent는 누구에게 던질지가 중요하다. application의 보안을 위해 Service의 시작 시에는 항상 Explicit intent만 사용하고 Intent filter는 선언하지 않도록 하라. Explicit(명시적) intent새 activity를 시작하거나 service를 시작할 때 사용한다. (ex. File download) 일반적으로 application 안에서 component를 시작할 때 사용한다. 시작할 component의 이름을 Fully-Qualified class name (ex. Abc.Class)으로 지정한다. Explicit intent를 사용할 경우 system이 즉시 지정된 component를 시작한다. 아래 예시는 웹에서 파일을 다운로드 하도록 한 DownloadService를 시작하는 코드이다. // Executed in an Activity, so 'this' is the Context// The fileUrl is a string URL, such as \"http://www.example.com/image.png\"Intent downloadIntent = new Intent(this, DownloadService.class);downloadIntent.setData(Uri.parse(fileUrl));startService(downloadIntent); Implicit(암시적) intent특정 component가 뭔지는 모르지만 현재 application이 수행할 수 없는 일반적인 작업을 다른 application의 component가 처리할 수 있도록 한다. Implicit intent를 사용하면 system이 시작시킬 적절한 component를 찾게 된다. 이 때, intent의 내용을 다른 application들의 Manifest file에 선언된 Intent filter와 대조하는 작업을 거치고, 해당 기능을 수행할 수 있는 application이 여러개가 있다면, 사용자가 선택할 수 있도록 화면을 띄워준다. Intent filter란 Manifest file에 선언한 해당 component가 수신하고자 하는 Intent type에 대한 내용이다. 다른 application들이 여기에 선언한 내용을 기반으로 내 application의 기능을 사용할 수 있게 된다. Intent filter에 아무 것도 선언하지 않는다면 explicit intent로만 수행할 수 있다. 아래 예시는 URI를 사용하지 않고 “text/plain” 정보를 통해 extra 정보를 지정한 후 ACTION_SEND를 통해 implicit intent를 날리는 것이다. // Create the text message with a stringIntent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);sendIntent.setType(\"text/plain\");// Verify that the intent will resolve to an activityif (sendIntent.resolveActivity(getPackageManager()) != null) { startActivity(sendIntent);} Implicit intent 사용 시 주의할 점은 startActivity()를 통해 날려도 처리할 application이 전혀 표시되지 않을 수 있다. 이 경우 호출 실패는 물론 application이 죽는다. 어떤 Activity에서라도 해당 intent를 확실히 수신할 수 있도록 하려면 위의 코드처럼 resolveActivity()를 호출하여 미리 확인하자. 결과가 null이 아닌 경우 해당 intent를 처리할 수 있는 application이 최소 하나는 있다는 것을 의미한다. Implicit intent에 응답하는 application이 하나 이상일 경우, 사용자가 수행될 application을 선택할 수 있는데 이 때 띄워주는 메뉴를 app chooser(앱 선택기)라 한다. Android는 사용자에게 항상 같은 application을 사용할 수 있는 옵션을 제공한다. 하지만 특정한 케이스에서, 사용자가 항상 다른 application을 사용해야 한다면 앱 선택기를 명시적으로 표시할 필요가 있다. 앱 선택기를 항상 표시하기 위해 아래와 같이 chooser intent생성 후 createChooser()를 사용하면 된다. Intent sendIntent = new Intent(Intent.ACTION_SEND);...// Always use string resources for UI text.// This says something like \"Share this photo with\"String title = getResources().getString(R.string.chooser_title);// Create intent to show the chooser dialogIntent chooser = Intent.createChooser(sendIntent, title);// Verify the original intent will resolve to at least one activityif (sendIntent.resolveActivity(getPackageManager()) != null) { startActivity(chooser);} Intent filter내 application이 수신할 수 있는 Implicit intent가 어떤 것이 있는지 알리려면 application Component에 대한 하나 이상의 intent filter를 Manifest file에 선언해야 한다. Explicit intent는 Component가 어떤 intent filter를 선언했든 무관하게 항상 정의해둔 곳으로 전달된다. 각 Intent filter는 다음과 같은 세 가지 요소 중 하나 이상을 사용하여 허용할 intent type을 정의할 수 있다. <action> 허용된 intent 작업을 name 속성에서 선언한다. Literal string이며 Class 상수가 아니다. <data> 허용된 data type을 선언한다. Data URI(scheme, host, port, path 등)와 MIME type 의 여러가지 내용 중 하나 이상의 속성을 사용한다. <category> 허용된 intent category를 name 속성에서 선언한다. Literal string이며 Class 상수가 아니다. Explicit intent를 수신하기 위해서는 Intent filter 내에 반드시 CATEGORY_DEFAULT를 포 함시켜야 한다. startActivity(), startActivityForResult() 메소드들은 모든 intent를 CATEGORY_DEFAULT를 선언한 것처럼 취급하기 때문에 이 Category를 intent filter에 선언하지 않으면 Activity의 어떤 암시적 intent도 확인되지 않는다. 아래 예시는 Data type이 text인 ACTION_SEND intent를 수신하겠다는 뜻이다. Intent filter 내에 선언한 것들과 하나라도 맞지 않으면 intent가 application으로 전달되지 않는다. <activity android:name=\"ShareActivity\"> <intent-filter> <action android:name=\"android.intent.action.SEND\"/> <category android:name=\"android.intent.category.DEFAULT\"/> <data android:mimeType=\"text/plain\"/> </intent-filter></activity> Intent filter에 등록하지 않았다고 하더라도 다른 application에서 내 application Component의 경로를 알아챌 경우 내 component를 수행할 수 있게 된다. 이 경우를 방어하기 위해 Manifest의 안에 android:exported = false를 설정하면 된다. 아래는 Android developer 사이트에서 제공하는 소셜 공유 application의 Manifest file이다. <activity android:name=\"MainActivity\"> // MainActivity 가 application의 Entry point(진입 지점)이다. <!-- This activity is the main entry, should appear in app launcher --> <intent-filter> <action android:name=\"android.intent.action.MAIN\" /> // 여기가 주요 진입 지점이며, 어떠한 intent도 바라지 않는 다는 것을 의미한다. <category android:name=\"android.intent.category.LAUNCHER\" /> // 이 Activity의 icon이 system의 application 시작 관리자에 배치되어야 한다는 것을 나타낸다. // 여기서는 icon을 지정하지 않았으므로 system은 <application> 에서 가져온 아이콘을 사용한다. </intent-filter></activity><activity android:name=\"ShareActivity\"> // 컨텐츠 공유를 목적으로 만든 Activity이다. <!-- This activity handles \"SEND\" actions with text data --> <intent-filter> <action android:name=\"android.intent.action.SEND\"/> // 다른 application에서 이 action과 <category android:name=\"android.intent.category.DEFAULT\"/> // type이 text/plain인 category를 이 application으로 던졌을 때 이 activity를 수행한다. <data android:mimeType=\"text/plain\"/> </intent-filter> <!-- This activity also handles \"SEND\" and \"SEND_MULTIPLE\" with media data --> <intent-filter> <action android:name=\"android.intent.action.SEND\"/> <action android:name=\"android.intent.action.SEND_MULTIPLE\"/> <category android:name=\"android.intent.category.DEFAULT\"/> <data android:mimeType=\"application/vnd.google.panorama360+jpg\"/> <data android:mimeType=\"image/*\"/> <data android:mimeType=\"video/*\"/> </intent-filter></activity> Pending IntentPendingIntent는 Intent의 wrapper이다. 다른 application에 권한을 위임하여 그 안에 들어있는 intent를 마치 본인 application의 자체 프로세스에서 실행하는 것처럼 사용하는 것이다. 뭔 말인가 싶다. 현재의 application A가 PendingIntent를 만들어서 다른 application이나 컴포넌트에 “내가 너에게 이 intent를 전달할 수 있는 권한을 줄테니 이따가 나 대신 좀 보내줘.” 라고 하는 것이다. 주요 사용 사례는 다음과 같다. 사용자가 이 application의 notification을 통해 task를 수행할 때 intent가 실행되도록 한다(Android system의 NotificationManager가 Intent를 실행한다.). 사용자가 이 application의 Application Widget으로 task를 수행할 때 intent가 실행되도록 한다(Mainscreen application에 Intent를 실행한다.). 지정된 시간에 intent가 실행되도록 선언한다(Android system의 AlarmManager가 Intent를 실행한다.). 예를 들어, 아래 코드는 실행하면 Notification bar에 새 notification이 등록되고 사용자가 이 notification을 터치했을 때 MainActivity로 진입한다. NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);Notification.Builder mBuilder = new Notification.Builder(this);mBuilder.setSmallIcon(R.mipmap.ic_launcher);mBuilder.setTicker(\"Notification.Builder\");mBuilder.setWhen(System.currentTimeMillis());mBuilder.setNumber(1);mBuilder.setContentTitle(\"Notification.Builder Title\");mBuilder.setContentText(\"Notification.Builder Massage\");mBuilder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);mBuilder.setContentIntent(pIntent);mBuilder.setAutoCancel(true);mBuilder.setPriority(Notification.PRIORITY_MAX);nm.notify(1, mBuilder.build()); 각 Intent object는 특정한 유형의 component(Activity, Service, BroadcastReceiver)가 처리하도록 설계되어 있다. 따라서 PendingIntent도 이러한 사항을 염두에 두고 생성해야 한다. PendingIntent를 사용하는 경우 직접적으로 startActivity() 등의 호출을 사용하지 않으므로 적절하게 세팅해줘야 한다. Activity 시작 Intent: PendingIntent.getActivitiy() Service 시작 Intent: PendingIntent.getService() BroadcastReceiver 시작 Intent: PendingIntent.getBroadcast() Intent 살펴보기system이 Activity를 시작하라는 implicit intent를 수신하면 system은 해당 intent에 대한 최선의 activity를 검색한다. 이 때 판단 근거가 아래 세 가지이다. Intent Action Intent Data (URI와 data type) Intent Category Action test이 Filter를 통과하려면 아래 나열된 작업 중 하나와 일치해야 한다. <intent-filter> <action android:name=\"android.intent.action.EDIT\" /> <action android:name=\"android.intent.action.VIEW\" /> ...</intent-filter> Category testIntent 내의 모든 Category가 filter 내의 category와 일치해야 한다. Filter 내의 category 수가 더 많은 것은 상관 없다. Intent의 category가 아무 것도 선언되어 있지 않으면 모두 통과할 수 있다. <intent-filter> <category android:name=\"android.intent.category.DEFAULT\" /> <category android:name=\"android.intent.category.BROWSABLE\" /> ...</intent-filter> Android는 CATEGORY_DEFAULT category를 startActivity() 및 startActivityForResult()에 전달된 모든 implicit intent에 적용한다. 따라서 Activity가 implicit intent를 수신하기 위해서는 intent filter 내에 “android.intent.category.DEFAULT” category 선언이 반드시 포함되어 있어야 한다. Data testURI 구조 및 Data type(MIME type)에 대해 나타내는데, URI의 각 부분에 대해서는 별도의 속성 (scheme, host, port, path)이 사용될 수 있다. <intent-filter> <data android:mimeType=\"video/mpeg\" android:scheme=\"http\" ... /> <data android:mimeType=\"audio/mpeg\" android:scheme=\"http\" ... /> ...</intent-filter> content://com.example.project:200/folder/subfolder/etc scheme: content host: com.example.project port: 200 path: folder/subfolder/etc 위의 네 가지 속성에는 linear한 종속 관계가 존재한다. scheme이 지정되지 않으면 host를 무시한다. host가 지정되지 않으면 port를 무시한다. scheme과 host 둘 다 지정되지 않으면 path를 무시한다. Intent의 URI이 Filter의 URI와 비교될 때에는 filter 내에 포함된 URI와 부분적으로 비교된다. filter가 scheme만 지정한 경우, 해당 scheme을 가지는 모든 URI는 filter와 매칭된다. filter가 scheme과 authority를 지정하고 path를 지정하지 않는 경우, 같은 scheme과 authority를 갖는 모든 URI는 path와 관계 없이 filter를 통과한다. filter가 scheme, authority, path를 모두 지정할 경우, 같은 scheme, authority, path를 가진 URI만이 filter를 통과할 수 있다. path에는 * 을 사용할 수 있다. Data의 유효성을 판별하기 위해서는 intent의 URI, MIME type과 filter의 URI, MIME type을 모두 비교해야 한다. a. URI와 MIME type을 모두 갖지 않는 intent는 URI와 MIME type을 아무 것도 정의하지 않은 filter만을 통과할 수 있다. b. URI를 갖고 MIME type을 갖지 않는 intent는 filter의 URI와 일치하고 filter가 MIME type을 지정하지 않은 경우 통과할 수 있다. c. URI가 없고 MIME type만 갖는 intent는 filter가 URI를 지정하지 않고 MIME type을 가지고 있을 때 통과할 수 있다. d. URI와 MIME type을 모두 갖는 intent는 filter에 나열된 type과 매치되는 경우에만 통과한다. Intent의 URI가 filter의 URI와 일치하거나 content: 또는 file:을 가지고 있는 경우, 그리고 filter가 URI를 정의하지 않는 경우 통과할 수 있다. 다른 말로, filter가 MIME type만 가지고 있을 경우 component는 content:와 file:을 당연히 지원하는 것으로 여겨진다. 규칙 d는 component가 file 또는 content provider로부터 local file을 가지고 올 수 있다는 기대를 가지고 반영된다. 따라서, 이러한 filter는 data type만 나열해도 되고 content:와 file: scheme을 명시적으로 작성하지 않아도 된다. 아래 예시는 Content provider로부터 image data를 가지고 와서 표시할 수 있다는 의미를 갖는다. <intent-filter> <data android:mimeType=\"image/*\" /> ...</intent-filter> 아래 예시는 Network에서 Video data를 검색할 수 있다는 의미를 갖는다. <intent-filter> <data android:scheme=\"http\" android:type=\"video/*\" /> ...</intent-filter> Intent matching Intent를 Intent filter와 비교를 해보면 target component를 활성화 시킬 수 있을 뿐만 아니라 단말의 component set에 대한 정보를 발견할 수 있다. 예를 들어, Home application이 application 런쳐를 채우기 위해 ACTION_MAIN action과 CATEGORY_LAUNCH category를 갖는 intent filter들을 찾아볼 수 있다. 개발한 application에서 비슷한 방법을 사용할 수 있는데, Packagemanager는 query...() 메소드들을 가지고 있고 이는 특정 intent로 접근할 수 있는 모든 component를 return한다. 이와 비슷한 것들로 resolve...() 메소드들이 있다. 이는 intent를 응답하기 위한 최적의 component를 정의한다. 예를 들어, queryIntentActivities()는 intent가 argument로써 통과할 수 있는 모든 activity의 list를 return하고, queryIntentServices()는 service에 대한 list를 return한다. 두 가지 메소드 모두 component를 활성화 시키지는 않고 단지 list만 나열할 뿐이다. BroadcastReceiver에서 사용하는 것은 queryBroadcastReceivers()가 있다. 출처 https://developer.android.com/guide/components/fundamentals.html https://developer.android.com/reference/android/content/Intent.html https://developer.android.com/guide/components/intents-filters.html https://developer.android.com/guide/components/tasks-and-back-stack.html https://developer.android.com/reference/android/app/PendingIntent.html","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"intent","slug":"intent","permalink":"http://lazyrodi.github.io/tags/intent/"}]},{"title":"Singleton","slug":"2016-07-16-pattern-singleton","date":"2016-07-16T09:42:50.000Z","updated":"2016-08-08T14:21:29.380Z","comments":true,"path":"2016/07/16/2016-07-16-pattern-singleton/","link":"","permalink":"http://lazyrodi.github.io/2016/07/16/2016-07-16-pattern-singleton/","excerpt":"","text":"Singleton은 유일한 Instance를 만들어 사용하는 방법이다. 생성자를 private으로 하여 다른 곳에서 Instance를 직접 생성할 수 없으며, public getInstance()를 통해서만 instance를 가져가서 사용할 수 있다. 객체 = Object = Compile된 각각의 .class file 개체 = Instance = Compile된 .class file을 new 연산자를 사용하여 Memory에 Loading한 상태 기본적으로는 이런 느낌이다. 123456789101112131415public class Iamsingle { private static Iamsingle mInstance; private Iamsingle() { } public static Iamsingle getInstance() { if (mInstance == null) { mInstance = new Iamsingle(); // 누군가가 만들어둔 Instance가 없다면 만들어서 돌려줍시다. } return mInstance; }} 필요성설정 파일 등 여러 개가 있으면 혼란스러울 수 있는 경우 Singleton을 통해 하나의 자원만 사용할 수 있게 한다. 전역적으로 접근이 가능해야 하지만 단 하나만 있어야 한다. 전역 변수는 여러 개가 생성될 수 있기 때문에 Singleton을 사용하는 것이다. 싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있도록 하기 위한 패턴입니다. - Head First Design Patterns 또한, Global Class는 어플리케이션이 시작될 때 항상 로딩되지만 Singleton Class는 시작되는 지점을 개발자가 지정할 수 있다. Trouble shootingMultiThreading 문제 해결위쪽에서 표현한 코드는 MultiThread 환경에서 추가적인 Instance의 생성을 완전히 방어할 수 없다. 고로, Thread 사용 시 기본적인 방어 방법인 Syncronized를 붙여보기로 한다. 123456789101112131415public class Iamsingle { private static Iamsingle mInstance; private Iamsingle() { } public static synchronized Iamsingle getInstance() { if (mInstance == null) { mInstance = new Iamsingle(); // 누군가가 만들어둔 Instance가 없다면 만들어서 돌려줍시다. } return mInstance; }} Synchronized로 인한 성능 문제 해결Synchronized로 인한 MultiThreading 시의 성능은 엄청나게 떨어진다. 고로, Instance를 미리 생성해 보도록 한다. 1234567891011public class Iamsingle { private static Iamsingle mInstance = new Iamsingle(); private Iamsingle() { } public static synchronized Iamsingle getInstance() { return mInstance; }} 문제를 해결하는 또 다른 방법DCL (Double-Checking Locking) 이라는 방법으로 Instance가 생성되었으면 그냥 넘기고, 없는 경우에만 synchronized 를 걸어주어 Instance를 요청하는 Thread들에 부담을 덜어주는 방법이다. volatile keyword는 Java 코드의 변수(Variable)를 메모리에 저장(Store) 하라는 의미이다. volatile 변수는 CPU cache를 사용하지 않고 Main memory에 직접 read/write 한다. volatile에 대한 자세한 내용은 여기를 참조해 보자. 결론적으로 여기서는 CPU Cache를 통할 때 변수의 무결성을 보장할 수 없으므로 volatile을 통해 Main memory에 직접 접근하겠다는 뜻이다. 1234567891011121314151617181920public class Iamsingle { private volatile static Iamsingle mInstance; private Iamsingle() { } public static Iamsingle getInstance() { if (mInstance == null) { // 여기까지는 MultiThread 방어가 되지 않은 상태. synchronized(this) { if (mInstance == null) { // 위의 첫 번째 null check를 통과한 thread가 대기하고 있다가 들어왔을 확률이 있기 때문에 한 번 더 check. mInstance = new Iamsingle(); } } } return mInstance; }} 마무리 무분별하게 리팩토링 하겠다고 Singleton 남발하지 말 것. 전역 설정이 필요할 때 등 꼭 필요할 때만 사용할 것. 위에서 나열한 방법들 중 해당 어플리케이션에 적절한 것을 사용할 것. 단일 Thread를 사용하는 프로그램에서 굳이 synchronized로 성능 저하를 시킬 필요는 없다. 출처 http://noesse.tistory.com/28 Head First Design Patterns - 한빛미디어, O’Reilly.","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"pattern","slug":"Dev/pattern","permalink":"http://lazyrodi.github.io/categories/Dev/pattern/"}],"tags":[{"name":"java","slug":"java","permalink":"http://lazyrodi.github.io/tags/java/"},{"name":"pattern","slug":"pattern","permalink":"http://lazyrodi.github.io/tags/pattern/"},{"name":"singleton","slug":"singleton","permalink":"http://lazyrodi.github.io/tags/singleton/"}]},{"title":"[Reference] My Favorite Colors","slug":"2016-07-16-works-my-favorite-colors","date":"2016-07-16T08:20:00.000Z","updated":"2016-08-08T14:21:35.115Z","comments":true,"path":"2016/07/16/2016-07-16-works-my-favorite-colors/","link":"","permalink":"http://lazyrodi.github.io/2016/07/16/2016-07-16-works-my-favorite-colors/","excerpt":"","text":"Color reference that I like. Link : http://lazyrodi.github.io/misc/MyFavoriteColors.html","categories":[{"name":"works","slug":"works","permalink":"http://lazyrodi.github.io/categories/works/"}],"tags":[{"name":"ui","slug":"ui","permalink":"http://lazyrodi.github.io/tags/ui/"},{"name":"design","slug":"design","permalink":"http://lazyrodi.github.io/tags/design/"},{"name":"color","slug":"color","permalink":"http://lazyrodi.github.io/tags/color/"}]},{"title":"Java Thread","slug":"2016-07-13-java-thread","date":"2016-07-13T11:42:50.000Z","updated":"2016-08-08T14:21:22.683Z","comments":true,"path":"2016/07/13/2016-07-13-java-thread/","link":"","permalink":"http://lazyrodi.github.io/2016/07/13/2016-07-13-java-thread/","excerpt":"","text":"Thread란 프로그램 실행 시 프로세스 내부에 존재하는 수행 단위를 말한다. Java에서는 두 가지의 Thread의 구현 방법이 있다. Thread를 상속받아서 사용하는 방법 이 경우 다른 Class의 상속이 불가능하다. Runnable interface를 구현하는 방법 일반적인 방법이다. 다른 Class의 상속이 가능하다. Thread 생성Thread 는 상태 변환을 통해 아래 그림과 같은 Lifecycle을 갖는다. 동작상태설명객체 생성NEWThread 객체의 생성 / start() 호출 전실행 대기RUNNABLE실행 상태로 언제든 갈 수 있는 상태일시정지WAITING다른 Thread가 통지할 때까지 기다리는 상태TIMES_WAITING주어진 시간동안 기다리는 상태BLOCKED사용하고자 하는 객체의 Lock이 해제될 때까지 기다리는 상태종료TERMINATED실행을 마친 상태 Thread를 상속받아 사용하는 방법 Thread로 수행될 Task class를 생성한다. 123456789101112public class Task extends Thread { String mName; Task(String str) { mName = str; } public void run() { // Thread 시작 시 수행된다. System.out.println(\"run \" + mName); }} Thread를 만들어 실행한다. start()가 호출되면 새로운 Thread가 작업을 실행하는데 필요한 Call Stack을 생성하고 run()을 호출하여 Stack에 저장한다. run() 메소드는 Thread scheduler에게 전달된다. 12345678910111213public class Main { public static void main(String[] args) { Task tA = new Task(\"a\"); Task tB = new Task(\"b\"); Task tC = new Task(\"c\"); tA.start(); // start()를 호출하면 앞에서 만든 Task의 run()이 실행된다. tB.start(); tC.start(); }} Runnable interface를 구현하여 사용하는 방법 Thread로 돌아갈 class를 생성한다. 123456789101112public class Task implements Runnable { String mName; Task(String str) { mName = str; } public void run() { System.out.println(\"run \" + mName); }} 새 Thread 객체를 생성하여 start() 한다. run() 으로 실행하면 단순히 Task 객체의 run() 메소드가 수행되는 것 뿐이다. start()로 해야 별도의 Thread가 생성되어 수행된다. Thread가 제대로 생성되어 수행되는지는 Eclipse의 debug 모드 등에서 threadStatus 등 thread 관련 항목들이 제대로 수행되는지 확인한다. 12345678910111213141516public class Main { public static void main(String[] args) { Task tA = new Task(\"a\"); Task tB = new Task(\"b\"); Thread t1 = new Thread(tA); Thread t2 = new Thread(tB); Thread t3 = new Thread(new Task(\"c\")); t1.start(); t2.start(); t3.start(); }} 아래와 같이 구현도 가능하다.123456789101112public class Main { public static void main(String[] args) { Thread t = new Thread(new Runnable() { public void run() { System.out.println(\"run\"); } }); t.start(); }} join()Thread의 수행 시간이 오래 걸릴 때, Thread보다 Main 함수가 먼저 종료되는 경우가 있다. 이런 경우 join() 메소드를 사용하면 모든 Thread들이 종료된 후 Main 함수가 종료된다. 예를 들어, 아래 코드를 실행한 결과를 보면 fin이 먼저 호출되는 것을 볼 수 있다. 123456789101112131415161718192021222324252627282930public class Main { public static void main(String[] args) { Task tA = new Task(\"a\"); Task tB = new Task(\"b\"); Thread t1 = new Thread(tA); Thread t2 = new Thread(tB); Thread t3 = new Thread(new Task(\"c\")); Thread t4 = new Thread(new Task(\"d\")); Thread t5 = new Thread(new Task(\"e\")); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); System.out.println(\"fin\"); } }// 결과run arun cfinrun drun erun b 다음과 같이 join()을 사용할 경우, Thread들이 모두 실행된 이후에 fin이 찍힌다. 123456789101112131415161718192021222324252627282930313233343536373839404142public class Main { public static void main(String[] args) { Task tA = new Task(\"a\"); Task tB = new Task(\"b\"); Thread t1 = new Thread(tA); Thread t2 = new Thread(tB); Thread t3 = new Thread(new Task(\"c\")); Thread t4 = new Thread(new Task(\"d\")); Thread t5 = new Thread(new Task(\"e\")); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); try { t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\"fin\"); } }// 결과run arun drun erun brun cfin SynchronizedThread가 함수를 통해 전역변수에 변형을 가할 경우, 각 Thread가 돌면서 값이 잘못 변하는 경우가 있다. 만약 아래와 같은 코드가 있다고 하면 (여기서는 10개지만 더 많이…) 마지막에 결과값이 10이 안 찍히고 9가 찍히는 것을 볼 수 있다. 물론 실행할 때마다 값이 다르다. 내가 만들어서 그런지 예시가 거지같다. 12345678910111213141516171819202122232425public class Task implements Runnable { String mName; static int no = 0; Task(String str) { mName = str; } public static void inc() { no++; } public void run() { try { Thread.sleep(1000); inc(); } catch(Exception e) { } System.out.println(\"run \" + mName); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061public class Main { public static void main(String[] args) { Task tA = new Task(\"a\"); Task tB = new Task(\"b\"); Thread t1 = new Thread(tA); Thread t2 = new Thread(tB); Thread t3 = new Thread(new Task(\"c\")); Thread t4 = new Thread(new Task(\"d\")); Thread t5 = new Thread(new Task(\"e\")); Thread t6 = new Thread(new Task(\"f\")); Thread t7 = new Thread(new Task(\"g\")); Thread t8 = new Thread(new Task(\"h\")); Thread t9 = new Thread(new Task(\"i\")); Thread t10 = new Thread(new Task(\"j\")); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); t7.start(); t8.start(); t9.start(); t10.start(); try { t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join(); t7.join(); t8.join(); t9.join(); t10.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(tA.no); System.out.println(\"fin\"); }}// 결과run a no: 0run d no: 7run c no: 0run i no: 6run j no: 5run e no: 3run f no: 4run g no: 2run b no: 1run h no: 89fin 위의 문제가 발생하는 이유는 예를 들면 하나의 Thread가 no를 3에서 4로 증가시키고 있을 때 다른 Thread가 접근하고, 그 Thread도 3에서 4로 증가시키게 된다. 그러면 inc()가 두 번 수행되었더라도 no는 4에 머물러있게 된다. 이러한 문제를 방지하기 위해 no++ 과정을 synchronized(동기화) 시켜주면 된다. 하나의 Thread가 synchronized 키워드 안의 내용을 수행 중이라면 다른 Thread는 그 자원에 접근할 수 없게 된다. 12345678910111213141516171819202122232425public class Task implements Runnable { String mName; static int no = 0; Task(String str) { mName = str; } synchronized public static void inc() { no++; } public void run() { try { Thread.sleep(1000); inc(); } catch(Exception e) { } System.out.println(\"run \" + mName); }} Thread PoolThread를 생성하기 위해서는 시간과 메모리가 소요된다. Java는 JVM(Java Virtual Machine) 위에서 돌아가고 JVM은 Thread의 생성 개수를 제한하지 않는다. 때문에 Thread를 과도하게 생성한다면 성능 저하는 물론 Memory leak이 발생한다. Thread의 무분별한 생성을 막기 위해 쓰레드 관리 방식인 Thread Pool을 사용한다. Thread Pool은 Thread를 허용된 개수 안에서 사용할 수 있도록 제약한다. 이 제약은 JVM이 하는 것이 아니라 어플리케이션에서 해야 한다. JDK 1.5 이전에는 개발자가 직접 만들어서 사용했으며, 1.5부터 java.util.concurrent 를 통해 지원하게 되었다. Excutors.newFixedThreadPool(n) 최대 Thread 수가 n 개인 Pool. 동시에 일어나는 업무량이 비교적 일정할 때 사용한다. Excutors.newCachedThreadPool() Thread 수의 제한을 두지 않는다. 새로운 Thread 시작 요청이 들어올 때마다 Thread를 하나씩 생성한다. 수행이 종료된 Thread들이 바로 사라지지 않고 1분동안 살아있다가 다른 작업 요청이 없다면 사라지게 된다. 짧고 반복되는 작업에 사용한다. Executors.newSingleThreadExecutor() 하나의 Thread를 생성한다. 주로 Thread 작업 중 예외가 발생한 경우 예외 처리를 위한 Thread 생성 용으로 사용한다. 선언은 ExecutorService 로 한다. Excutors.newFixedThreadPool(n)123456789ExecutorService executorService = Executors.newFixedThreadPool(2);// Thread 생성 요청.// Task는 수행할 class 명.// 세 번 요청하면 세 번째 Thread는 앞의 두 개 중 하나가 종료될 때까지 수행되지 않는다.executorService.execute(new Task(\"name\"));executorService.shutdown(); // 추가적인 Thread 요청을 거부한다.while (!executorService.isTreminated()) { // 모든 Thread가 완료될 때까지 대기한다.} Excutors.newCachedThreadPool()123456ExecutorService executorService = Executor.newCachedThreadPool();executorService.execute(new Task(\"name\"));executorService.shutdown();while (!executorService.isTreminated()) {} Executors.newSingleThreadExecutor()123456789101112131415ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(()->{ try { Thread.sleep(1000); } catch (Exception e) { }; }});executorService.shutdown();while (!executorService.isTreminated()) {} 출처 http://blog.naver.com/2feelus/220728222140 http://blog.naver.com/kksssii/220676622695","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"java","slug":"Dev/java","permalink":"http://lazyrodi.github.io/categories/Dev/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://lazyrodi.github.io/tags/java/"},{"name":"thread","slug":"thread","permalink":"http://lazyrodi.github.io/tags/thread/"}]},{"title":"Hexo로 변경","slug":"2016-07-11-life-change-to-hexo","date":"2016-07-11T13:00:00.000Z","updated":"2016-08-05T12:40:20.619Z","comments":true,"path":"2016/07/11/2016-07-11-life-change-to-hexo/","link":"","permalink":"http://lazyrodi.github.io/2016/07/11/2016-07-11-life-change-to-hexo/","excerpt":"","text":"원래 사용하던 Jekyll에서 Hexo로 변경하였다. 이 테마(hueman)가 상당히 완성도가 높아보여서 따라하느라 변경하였는데 Jekyll보다 지원하는 기능이 화려하다. (혹은 내가 Jekyll도 제대로 못 썼겠지.) 맘에 든다. 설명도 꽤 간단하고 알아먹기 쉽게 제공되어서 좋다. Jekyll에서 가져온 글들이 markdown간 호환이 100% 되지는 않아서 테이블과 그림 작업이 더 필요하지만… 그래도 뭐 굳.","categories":[{"name":"life","slug":"life","permalink":"http://lazyrodi.github.io/categories/life/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"http://lazyrodi.github.io/tags/hexo/"}]},{"title":"Android permission","slug":"2016-07-10-android-permission","date":"2016-07-10T10:47:50.000Z","updated":"2016-08-08T14:21:14.611Z","comments":true,"path":"2016/07/10/2016-07-10-android-permission/","link":"","permalink":"http://lazyrodi.github.io/2016/07/10/2016-07-10-android-permission/","excerpt":"","text":"안드로이드는 privilege-separated OS이다. 각각의 어플리케이션들은 서로 다른 system ID(Linux의 User ID 또는 Group ID)로 구분된다. 안드로이드 시스템의 보안 기능은 세분화 되어있으며 Permission Mechanism에 의해 동작한다. 안드로이드 시스템은 시스템 자체의 무결성(Integrity)과 개인 정보 보호를 위해 각 어플리케이션이 sandbox(보호된 영역) 내에서만 동작하도록 제한해 두었다. 어플리케이션이 자신의 sandbox 밖의 리소스, 파일 등의 사용을 원할 때에는 명시적으로 해당 permission에 대해 요청하고 획득해야 한다. Permission은 특정 프로세스가 특정 기능을 수행하기 위해 필요한 권한을 의미하며, 사용자가 허가를 해야 사용할 수 있다. 안드로이드의 System permission은 두 가지 종류로 나눌 수 있다. (Normal and Dangerous Permissions 참조) Normal permissions 다른 프로세스, 데이터, 사용자에게 악영향을 주지 못하는 권한들. Dangerous permissions 파일 저장 및 운용, 주소록 접근 등 다른 요소에 악영향을 줄 수도 있는 권한들. 안드로이드 시스템은 어플리케이션이 Normal permissions을 요청하면 자동으로 권한을 부여하고, Dangerous permissions를 요청하면 사용자에게 Dialog를 통해 확인(Dangerous permissions)을 받은 후 허가를 해준다. 사용자에게 확인을 받는 동작은 안드로이드 버전에 따라 다르게 동작한다. Android 5.1 (Lollipop) 이하 어플리케이션 설치 시 사용할 permission들에 대해 사용자의 허가를 받음. Android 6.0 (Marshmallow) 이상 Runtime에 permission을 부여할지 묻는다. System PermissionsSecurity architecture안드로이드는 기본적으로 Permission이 없으면 하나의 어플리케이션이 다른 어플리케이션, OS, user에게 위해를 가할 수 없다. 예를 들어, permission 없이는 주소록이나 이메일 등의 접근할 수 없고 다른 어플리케이션에 속한 파일에 접근할 수 없다는 뜻이다. 어플리케이션은 필요로 하는 permission에 대해 Application Manifest 파일에 static하게 선언해야 한다. <uses-permission> 태그를 이용하며, 아래의 예시는 SMS의 발신 권한을 획득하기 위한 선언이다. 1234567891011<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.example.snazzyapp\"> <uses-permission android:name=\"android.permission.SEND_SMS\"/> <application ...> ... </application></manifest> 어플리케이션 Sandbox는 어플리케이션이 어떻게 빌드 되었던 간에 독립적으로 동작하며 어플리케이션의 형태(Java, native, hybrid)에 관련 없이 같은 방식, 같은 강도의 보안을 가지고 있다. Permission의 요청이 실패할 경우 SecurityException을 return받게 된다. 하지만 요청이 실패한다고 해서 반드시 이 exception이 발생하는 것은 아니다. 예를 들어, sendBroadcast(Intent) 메소드는 각각의 receiver로 전달되는 데이터에 대해 permission check를 진행하지만 값이 return될 때 permission failure가 발생했었는지 여부는 알 수 없게 된다. 그래도 대부분 permission failure에 대한 정보는 system log에 남기 때문에 이상 여부를 확인할 수 있다. 사용 가능한 Permission의 종류는 Manifest.permission에서 확인할 수 있다. 아래와 같은 특정한 상황에서는 어플리케이션 동작 중에 별도의 permission을 요구하게 된다. 시스템에 대한 호출 시 어플리케이션이 특정 기능을 무단으로 사용하는 것을 방지하기 위해 Activity가 시작될 때 다른 어플리케이션의 activity가 임의로 시작되는 것을 막기 위해 Broadcast의 전송/수신 시 내가 보낸 Broadcast를 누가 받았는지, 누가 나에게 Broadcast를 보냈는지 제어하기 위해 Content provider에 접근 시 Service를 시작하거나 Binding 할 때 Application Signing모든 APK들은 개발자의 private key을 이용한 인증과정을 통해 서명된 상태여야 한다. 서명은 특별히 인증기관 등의 허가를 받는 것은 아니며, 개발자 스스로 생성한 key로 서명할 수 있다. 서명의 목적은 제작자가 누구인지 구분하는 것이다. (복제 방지, 악용 방지) 서명을 통해 시스템은 어플리케이션이 signature-level permissions (normal, dangerous, signature, signatureOrSystem)에 접근할 수 있는 권한을 부여/거부한다. 어플리케이션이 다른 어플리케이션과 동일한 Linux ID를 사용하려 할 때에도 서명 정보를 필요로 한다. User ID와 File access어플리케이션 설치 시 안드로이드는 각각의 package에 각각 다른 Linux user ID를 부여한다. 이는 상수값이며 해당 package가 죽을 때까지 가지고 있는다. 보안 권한은 Process level에 존재하고, 일반적으로 서로 다른 두 개의 package가 하나의 process에서 수행되는 일은 없다. 보통 서로 다른 Linux user로 동작한다. 하지만 AndroidManifest.xml의 manifest tag 내에 sharedUserId 속성을 사용하게 되면 서로 다른 package들이 같은 user ID를 사용할 수도 있다. 이 방법을 통해, 보안을 목적으로 하는 두 개의 package가 하나의 어플리케이션인척 행동하게 된다. 이 때 user ID 및 file에 대한 권한을 동일하게 갖는다. 보안을 유지하기 위해 동일한 서명을 사용한 어플리케이션 중 단 두 개의 어플리케이션만 동일한 user ID를 사용할 수 있다. 어플리케이션이 저장한 data들은 해당 어플리케이션의 user ID에 할당되며, 기본적으로 다른 package에서는 접근이 불가하다. 어플리케이션이 새로 만들거나 저장하는 파일들에 대한 메소드는 다음과 같다. getSharedPreferences(String, int) openFileOutput(String, int) openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory) 만약 다른 package들이 read/write를 할 수 있게 하려면 MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE flag를 사용해야 한다. 이 flag들을 사용하여도 소유권은 변하지 않는다. Automatic permission adjustments시간이 지나면서(새로운 sdk가 나오면) 이전에는 없던 permission이 만들어지기도 한다. 해당 permission이 만들어지기 전에 생성된 어플리케이션은 당연히 권한 없이도 이를 사용할 수 있다는 가정 하에 개발되었기 때문에 관련 기능을 사용하려면 에러가 날 수도 있다. 에러를 방지하기 위해 안드로이드는 Manifest에 새 permission에 대한 코드를 추가한다. 추가할지 말지 결정하는 근거가 되는 것이 targetSdkVersion 이며, 이 값이 permission이 생성된 버전보다 낮다면 안드로이드는 permission을 추가한다. 예를 들어, WRITE_EXTERNAL_STORAGE permission은 공유된 저장소에 접근하는 것을 제한하기 위해 API level 4에 만들어졌다. 만약 targetSdkVersion이 3보다 낮다면 이 permission은 자동으로 추가된다. 만약 이 경우처럼 자동으로 permission이 추가되는 경우, Google Play에서 이러한 권한들에 대해 보여주게 된다. (실제로 그 어플리케이션에서 사용하지 않더라도 보여준다.) 사용하지 않는 permission을 제거하기 위해서는 어플리케이션의 targetSdkVersion을 항상 최신화하여 유지하는 것이 좋다. Build.VERSION_CODES를 통해 각 버전 별로 업데이트 된 permission 목록을 확인할 수 있다. Permission groups모든 dangerous Android system permission은 permission group에 속해있다. Permission이 같은 group에 속한 경우 사용자에게 한 번만 사용 허가를 받으면 같은 group에 속한 다른 permission들도 사용할 수 있다. 예를 들어, READ_CONTACTS에 대한 권한을 사용자로부터 허가받아 부여받았다면, WRITE_CONTACTS 권한은 자동으로 부여받는다. [표 1. Dangerous permissions and permission groups] |————-+——————————|| CALENDAR | READ_CALENDAR || | WRITE_CALENDAR || CAMERA | CAMERA || CONTACTS | READ_CONTACTS || | WRITE_CONTACTS || | GET_ACCOUNTS || LOCATION | ACCESS_FINE_LOCATION || | ACCESS_COARSE_LOCATION || MICROPHONE | RECORD_AUDIO || PHONE | READ_PHONE_STATE || | CALL_PHONE || | READ_CALL_LOG || | WRITE_CALL_LOG || | ADD_VOICEMAIL || | USE_SIP || | PROCESS_OUTGOING_CALLS || SENSORS | BODY_SENSORS || SMS | SEND_SMS || | RECEIVE_SMS || | READ_SMS || | RECEIVE_WAP_PUSH || | RECEIVE_MMS || STORAGE | READ_EXTERNAL_STORAGE || | WRITE_EXTERNAL_STORAGE | Permission의 정의 및 제한Permission을 적용하기 위해서는 AndroidManifest.xml에 <permission> 태그를 사용하여 정의해야 한다. 아래의 예시는 어플리케이션이 자신이 가진 activity 중 하나를 시작하는 권한을 다른 어플리케이션에게 부여하기 위해 permission을 선언한 것이다. 123456789<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.example.myapp\" > <permission android:name=\"com.example.myapp.permission.DEADLY_ACTIVITY\" android:label=\"@string/permlab_deadlyActivity\" android:description=\"@string/permdesc_deadlyActivity\" android:permissionGroup=\"android.permission-group.COST_MONEY\" android:protectionLevel=\"dangerous\" /> ...</manifest> 참고로 모든 package가 동일한 인증서로 서명하지 않은 한, 여러 package가 동일한 이름의 permission을 선언할 수는 없다. 하나의 Package가 permission을 선언한 경우 시스템은 같은 permission 이름을 가진 다른 package가 시스템에 설치되는 것을 막는다. 물론 이미 설치되어 있는 package와 동일한 서명을 가지고 있다면 설치를 허가한다. 이런 충돌을 피하기 위해 안드로이드에서는 reverse-domain-style naming을 추천한다. ex. com.example.myapp.ENGAGE_HYPERSPACE 위의 예시에서 하나하나 살펴보면; protectionLevel Mandatory 항목이다. 시스템이 어플리케이션이 이 권한을 필요로 하는 이유 또는 이 권한을 가지고 있는 것들이 무엇인지 사용자에게 알려주기 위한 속성이다. 아래 AndroidManifestPermission_protectionLevel 을 살펴보자. android:permissionGroup Optional 항목이다. 시스템이 권한에 대해 사용자에게 보여줄 때에만 사용된다. 대부분의 경우, standard system group(android.Manifest.permission_group)으로 설정하게 된다. CALENDAR CAMERA CONTACTS LOCATION MICROPHONE PHONE SENSORS SMS STORAGE 개발자가 직접 정의할 수도 있다. 하지만 왠만하면 기존에 있는 것으로 사용하자. 안드로이드 폰에서 설정 > 앱 > 앱 설정 > 앱 권한 으로 가면 permissionGroup으로 묶여있는 것을 확인할 수 있다. android:label android:description label 및 description은 사용자가 permission list 확인 시 보여줄 제목 및 상세 내용이다. 새로 추가해야 한다면 가능한 짧게 작성하라. 아래는 CALL_PHONE permission에 대한 label 및 description 예시이다. $ adb shell pm list permissions 명령어를 통해 현지 시스템에서 사용하고 있는 권한들을 확인할 수 있다. 12345<string name=\"permlab_callPhone\">directly call phone numbers</string><string name=\"permdesc_callPhone\">Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers.</string> AndroidManifestPermission_protectionLevel|———————–+——————-+———————|| Constant | Value | Description || normal | 0 | 낮은 위험도를 가진 permission. 어플리케이션이 다른 어플리케이션에 별다른 영향을 주지 않는 부분으로 접근을 시도한다. 시스템은 이 권한을 요청하는 어플리케이션에게 권한을 자동적으로 부여한다. || dangerous | 1 | 높은 위험도를 가진 permission. 어플리케이션이 개인 정보 및 디바이스를 나쁜 목적으로 사용이 가능하다. 시스템은 이 권한을 자동으로 부여하지 않고 사용자에게 허가를 요구하는 dialog 를 띄워서 허가를 받는다. || signature | 2 | 같은 인증서로 서명된 어플리케이션에게만 이 권한을 허가한다. 인증서가 동일한 것이 판명되면 시스템은 자동적으로 권한을 부여한다. || signatureOrSystem | 3 | 안드로이드 시스템 이미지 내의 package 또는 같은 인증서로 서명된 어플리케이션에게만 권한을 허가한다. 이 옵션은 피해야 하며, Signature protection level은 대부분의 요구를 충족시켜야 하고 어플리케이션이 어디에 설치되었건 정확하게 동작해야 한다. || privileged | 0x10 | base permission type으로부터 파생된 추가적인 flag로 이 permission은 시스템 이미지에 설치된 privileged app들에 부여될 수 있다. 이 옵션은 피해야 하며, Signature protection level은 대부분의 요구를 충족시켜야 하고 어플리케이션이 어디에 설치되었건 정확하게 동작해야 한다. || system | 0x10 | “privileged”와 동일하다. 이전에 사용하던 용어. || development | 0x20 | base permission type으로부터 파생된 추가적인 flag로 개발용 어플리케이션에 선택적으로 부여될 수 있다. || appop | 0x40 | base permission type으로부터 파생된 추가적인 flag로 제어 권한을 위한 app op과 밀접한 관련을 가지고 있음. || pre23 | 0x80 | base permission type으로부터 파생된 추가적인 flag로 Marshmallow 이전 버전의 API level을 갖는 어플리케이션들에게 자동으로 부여된다. (runtime permission 부여를 지원하지 않는 버전들) || installer | 0x100 | base permission type으로부터 파생된 추가적인 flag로 시스템 어플리케이션 권한을 가진 어플리케이션들에게 자동으로 부여된다. || verifier | 0x200 | base permission type으로부터 파생된 추가적인 flag로 시스템 어플리케이션 권한을 가진 어플리케이션들에게 자동으로 부여된다. || preinstalled | 0x400 | base permission type으로부터 파생된 추가적인 flag로 시스템 이미지 위에 미리 설치되는 어플리케이션들(privileged 어플리케이션을 포함한)에게 자동으로 부여된다. || setup | 0x800 | base permission type으로부터 파생된 추가적인 flag로 setup wizard 어플리케이션에게 자동으로 부여된다. | Custom permission, 이렇게 사용하라. 여러 어플리케이션을 묶어서 제품군을 설계할 때, 각각의 permission이 단 한번만 정의될 수 있도록 설계해야 한다. 서로 다른 어플리케이션이 같은 인증서로 서명한 경우 signature check를 통해 중복으로 permission을 정의하는 것을 막을 수 있다. 하나의 제품군을 개발하는 경우 이 모든 제품군의 permission을 관리하는 하나의 pacakage를 개발하는 것이 좋다. 이 package는 특별히 서비스를 제공하진 않고 permission을 관리할 뿐이며, 제품군에 속한 다른 어플리케이션들은 만 사용하게끔 한다. AndroidManifest.xml의 permission 제한개발자는 AndroidManifest.xml을 통해 시스템 또는 어플리케이션의 모든 컴포넌트에 접근 제한을 걸 수 있다. 이를 위해 컴포넌트 별로 android:permission 속성을 선언하고 해줘야 한다. Activity permissions () 관련된 activity를 실행할 수 있는 권한을 나타낸다. 이 permission은 Context.startActivity()와 Activity.startActivityForResult() 실행 시 체크된다. 만약 호출한 측이 permission을 가지지 않았다면 SecurityException이 발생한다. Service permissions () 관련된 service에 bind할 수 있는 권한을 나타낸다. 이 permission은 Context.startService(), Context.stopService(), Context.bindService() 실행 시 체크된다. 만약 호출한 측이 permission을 가지지 않았다면 SecurityException이 발생한다. BroadcastReceiver permissions () 관련된 receiver로 broadcast를 보낼 수 있는 권한을 나타낸다. Context.sendBroadcast()가 return될 때 permission 체크가 이루어지며, 시스템은 broadcast를 정해진 receiver로 전달하려 한다. Permission이 없는 경우 호출한 측으로 result가 제대로 전달되지 않고 exception이 전달된다. 이 Permission은 Context.registerReceiver를 통해 동적으로 등록할 수도 있다. 다른 방법으로 Context.sendBroadast() 호출 시 permission을 명시하여 broadcast를 수신할 수 있는 BroadcastReceiver를 제한할 수 있다. ContentProvider permissions () ContentProvider 내의 data에 접근하는 것을 제한할 수 있다(ContentProvider는 URI permission이라 불리는 중요한 추가 보안 기능을 가지고 있다.). 다른 컴포넌트들과는 다르게 두 개의 permission attribute를 설정할 수 있다. write 권한을 가지고 있다고 해도 read할 수 없다. android:readPermission android:writePermission 최초 provider를 찾을 때(retrieve) permission에 대해 체크하며 두 개의 permission 모두 없다면 SecurityException이 발생한다. ContentResolver.query()는 read permission을 요구하며, ContentResolver.insert(), ContentResolver.update(), ContentResolver.delete()는 write permission을 요구한다. Sending Broadcast 시의 Permission 제한BroadcastReceiver를 regist하는 것(Context.registerReceiver())에 더하여 broadcast를 보낼 때 필요한 permission을 지정할 수도 있다. Context.sendBroadcast() 호출 시 permission string을 포함하여 보내면 해당 permission을 가지고 있는 receiver만 수신할 수 있다. receiver와 broadcaster 모두 permission을 가지고 있어야 한다. Permission을 제한하는 다른 방법다른 프로세스가 현재 프로세스의 Service 등을 호출했을 때 Context.checkCallingPermission()을 사용할 수 있으며 return값인 PERMISSION_GRANTED, PERMISSION_DENIED 로 호출한 프로세스가 권한을 가지고 있는지 확인할 수 있다. Permission 체크를 위한 두 가지 방법이 더 있다. 다른 프로세스의 PID(Process Identifier)를 알고있다면 Context.checkPermission(String, int, int)를 사용할 수 있다. 다른 어플리케이션의 Package name을 알고있다면 PackageManager.checkPermission(String, String)을 사용할 수 있다. URI PermissionContent provider 사용 시 standard permission system에 기술된 내용은 충분하지 않다. Content provider는 직접적인 client는 물론 다른 어플리케이션이 동작하기 위한 특정한 URI를 다룰 때 read/write permission을 통해 스스로를 보호하기를 원한다. 대표적인 예로, Mail 어플리케이션에서의 파일 첨부 기능을 보자. Mail로의 접근은 user-data의 보호를 위해 permission으로 막혀있다. Image 첨부 기능의 URI가 Image viewer에 제공되어 있다 하더라도, Image viewer는 permission이 없기 때문에 첨부 파일을 열 수 없다. 이 문제의 해결책이 per-URI permission이다. Activity가 시작되거나 result가 activity로 return될 때 Caller는 Intent.FLAG_GRANT_READ_URI_PERMISSION 또는 Intent.FLAG_GRANT_WRITE_URI_PERMISSION을 설정할 수 있다. 이는 별도로 Content provider의 data 접근을 위한 permission을 가질 필요 없이 Intent 내의 특정 data URI를 통해 activity permission을 받겠다는 의미이다. 이 동작 방식은 파일 열기, 주소록 선택 등의 사용자와의 상호 작용 발생 시 임시로 권한을 허가하는, 일반적인 capability-style 모델을 가능케 한다. 또한, 어플리케이션이 쓸데없는 permission을 갖게지 않게하는데 핵심적인 역할을 한다. URI permission의 권한 부여을 위해 해당 URI들을 가지고 있는 Content provider는 자신이 이 기능을 구현하고 있다는 것을 알리기 위해 android:grantUriPermissions 속성을 선언하거나 <grant-uri-permissions> tag를 선언해야 한다. 아래와 같은 방식으로 Flag를 세팅할 수 있다. 123456startActivity( new Intent(Intent.ACTION_VIEW) .setDataAndType(uri, type) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) ); Permission 사용하기Permission 확인어플리케이션에서 Dangerous permission의 사용을 요청할 경우 사용할 때마다 permission을 얻은 상태인지 체크 해야하며, ContextCompat.checkSelfPermission() 메소드를 사용하면 된다. 아래는 캘린더 작성 permission에 대한 Android의 예시이다. Permission을 획득했다면 PackageManager.PERMISSION_GRANTED가 return될 것이며, permission이 없다면 PERMISSION_DENIED가 return될 것이다. 123// Assume thisActivity is the current activityint permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR); Permission 요청하기사용자가 한 번 거부한 permission에 대해서는 설명을 추가(ex. Don’t ask again 체크박스)해야 하는데 한 번 거부한 permission인지 알 수 있는 방법은 shouldShowRequestPremissionRationale() 메소드를 사용하는 것이다. 만약 이전에 사용자가 DENY처리한 적이 있다면 true가 return된다. 어플리케이션이 permission을 아직 안 가지고 있을 경우 requestPremissions()를 호출해야 한다. 아래 코드는 READ_CONTACTS 기능에 대해 permission을 가지고 있는지 확인하며, permission이 없다면 요청한다. 1234567891011121314151617181920212223242526// Here, thisActivity is the current activityif (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) { // Show an expanation to the user *asynchronously* -- don't block // this thread waiting for the user's response! After the user // sees the explanation, try again to request the permission. } else { // No explanation needed, we can request the permission. ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS); // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an // app-defined int constant. The callback method gets the // result of the request. }} Permission request responsePermission을 사용자에게 요청 시 dialog box를 보여주게 된다. 사용자가 allow/deny를 입력하면 시스템은 어플리케이션의 onRequestPermissionsResult() 메소드를 호출한다. 어플리케이션에서 보낸 requestPermission()에 대해 동일한 request code가 도착하는지 확인하기 위해 callback 함수인 onRequestPermissionResult()를 override 해야한다. 아래 예시는 READ_CONTACTS 권한을 요청하고 callback 받는 것이다. 123456789101112131415161718192021222324@Overridepublic void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_READ_CONTACTS: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the // contacts-related task you need to do. } else { // permission denied, boo! Disable the // functionality that depends on this permission. } return; } // other 'case' lines to check for other // permissions this app might request }} Permission 잘 사용하기Intent의 사용을 고려하라App에서 task를 수행하기 위한 방법은 크게 두 가지가 있다. App이 task를 스스로 수행하게 하는 것 Intent를 던져서 다른 어플리케이션이 task를 수행하게 하는 것 사진을 찍고 싶으면 CAMERA permission을 요청하여 카메라 제어와 관련된 모든 permission을 가지고 올 수 있다. 하지만 ACTION_IMAGE_CAPTURE intent를 날려서 사진을 찍을 수도 있다. 이 intent를 던지면 카메라 app을 설정하라는 dialog가 발생한다. 만약 default app으로 설정된게 있으면 안 뜬다. 사용자가 사진을 찍고 나면 onActivityResult()를 통해 사진을 callback받을 수 있다. 이 두 가지 방법은 이 세상 이치가 그렇듯 각각의 장단점이 있었으니… Permission 사용 시 App이 모든 제어권을 갖지만 그에 따른 모든 UI를 설계해야 하기 때문에 App이 복잡해진다. 한 번 permission을 받으면 계속 사용할 수 있지만 사용자가 permission 획득을 거부한다면 어플리케이션은 무용지물이 된다. Intent 사용 시 수행 동작들에 대한 UI를 구성할 필요가 없으며 intent를 처리하는 어플리케이션이 준비한 UI를 사용한다. 하지만 이는 곧 UX를 제공할 수 없다는 말이 된다. 사용자가 default app을 지정하지 않았다면 system은 app을 고르는 화면을 보여줄 것이다. 사용자가 default handler를 지정하지 않으면 동작이 수행될 때마다 dialog를 띄워줄 것이다. 꼭 필요한 permission만 요청하라Android 6.0 이상의 버전에서는 permission이 필요한 기능을 새로 수행할 때마다 사용자에게 수행할 것인지 묻는다. 사용자 입장에서 여러가지 기능에 대해 자꾸 물어보면 짜증나니까 꼭 필요한 permission만 사용하고 요청하도록 한다. 어플리케이션의 core가 되는 기능이 아니라면 왠만하면 intent를 던져서 사용하자. 쓸데없이 permission을 많이 가져가지 말아라한 번에 너무 많은 permission을 요청할 경우, 어플리케이션이 종료될 수도 있다. 어플리케이션 구동에 있어서 꼭 필요한 permission은 어플리케이션이 처음 실행될 때 획득할 수 있게 처리하는 것이 좋다. 예를 들어, 사진 어플리케이션이 처음 구동될 때 카메라 제어에 대한 permission을 받는 것이 좋지만 READ_CONTACTS등의 permission은 주소록을 통한 공유 등 그 기능이 처음 수행될 때 받는 것이 좋다는 이야기이다. 그 Permission이 왜 필요한지 사용자에게 설명하라requestPermissions()를 호출하면 permission dialog가 뜬다. 하지만 왜 이 permission을 획득해야 하는지는 알려주지 않는다. Permission을 요청하면 사용자는 왜 이 permission이 필요한지 궁금해진다. 예를 들어, 사진 어플리케이션이 geotag를 위해 location service를 필요로 할 때, 무턱대고 location permission을 요청하면 사용자들은 의아하게 생각한다. 따라서 requestPermissions()를 호출하기 전에 사용자에게 설명해줄 필요가 있다. 사용자에게 알려주는 방법 중 하나는 app tutorial을 넣는 것이다. Demo를 보여줌으로써 사용자가 이 permission이 왜 필요한지 느낄 수 있다. Permission에 대해 test하는 방법API level 23 이상의 단말에서는 아래 방법으로 permission과 관련된 문제 있는 코드를 확인할 수 있다. 어플리케이션의 현재 permission 및 관련 경로를 확인한다. Permission이 필요한 service를 수행하거나 data에 접근을 시도한다. 여러가지 permission을 얻었다 잃었다 하면서 테스트한다. 예를 들어, 카메라 어플리케이션은 CAMERA, READ_CONTACTS, ACCESS_FINE_LOCATION이 필요하다고 할 때, 이 세 가지를 껐다 켰다 하면서 테스트를 진행한다. ADB를 통해 다음 항목들을 시험해볼 수 있다. Permission list를 확인하는 방법12$ adb shell pm list permissions -d -g permission을 획득/제거하는 방법12$ adb shell pm [grant|revoke] <permission-name> ...","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"},{"name":"permission","slug":"permission","permalink":"http://lazyrodi.github.io/tags/permission/"}]},{"title":"OSI 7 layer and TCP/IP model","slug":"2016-07-04-osi7-and-tcpip","date":"2016-07-04T06:47:50.000Z","updated":"2016-08-08T14:21:09.923Z","comments":true,"path":"2016/07/04/2016-07-04-osi7-and-tcpip/","link":"","permalink":"http://lazyrodi.github.io/2016/07/04/2016-07-04-osi7-and-tcpip/","excerpt":"","text":"10년이 넘도록 외워지지 않는 Network 기초… OSI 7 layerOSI (Open System Interconnection) 7 Layer 는 Network Communication 시 발생하는 충돌들을 완화하기 위해 ISO에서 표준화된 Network 구조로 제시한 기본 모델이다. ISO/IEC 7498 Spec. ISO/IEC 7498-1: The Basic Model ISO 7498-2: Security Architecture ISO/IEC 7498-3: Naming and addressing ISO/IEC 7498-4: Management framework [7] 응용 계층 (Application) Application Software에 API 제공 Data type: Message Equipment: L7 ~ L5 Switch, Gateway (IP + TCP / UDP + Packet) Protocol: DNS, SNMP [6] 표현 계층 (Presentation) Network Security (번역기 역할) 암호화, 압축, 변환 Data type: Message Equipment: L7 ~ L5 Switch, Gateway (IP + TCP / UDP + Packet) Protocol: MPEG, JPG, MIME [5] 세션 계층 (Session) Socket Program 동기화, 통신 선로 구축 및 유지 Session 연결, 관리, 종료 Data type: Message Equipment: L7 ~ L5 Switch, Gateway (IP + TCP/UDP + Packet) Protocol: 전송 모드 결정 (반이중, 전이중, 직렬, 병렬, 동기, 비동기) RTP (Real-time Transport Protocol) [4] 전송 계층 (Transport) 데이터 전송 보장 흐름 제어 (정지 대기, 슬라이드) QoS (Quality of Service) Data type: Segment Equipment: L4 Switch (IP+TCP/UDP, Load balancing) Protocol: TCP, UDP [3] 네트워크 계층 (Network) 통신 경로 설정 (Routing): 중계 기능 담당, 교환 혼잡 제어 IPv4, IPv6 Data type: Packet Equipment: L3 Switch (IP 주소 참조), Router (유선, 무선) Protocol: IP, ARP, RARP, 회선 교환, 패킷 교환 [2] 데이터 링크 계층 (Datalink) 오류 제어, Frame 형식 정의 및 생성, MAC 제어 에러 검출 및 정정, 흐름 제어 Data type: Frame Equipment: L2 Switch MAC 주소 참조 Bridge (segment) Switch (frame) Protocol: FEC, ARQ, H-ARG, 해밍코드 [1] 물리 계층 (Physical) 물리적 연결 설정, 해제 Data 코딩, 변조 방식 (AM, FM, PM) Data 부호화 방식 (ASK, FSK, PSK) Multiplexing (TDM, FDM) Data 전송 속도 (bps, Baud) Data type: Bit stream Equipment: Repeater/Hub Protocol: 전기적 신호, 절차적 규격, 맨체스터 코드 OSI 7 Layer가 적용된 Protocol들에는 다음과 같은 것들이 있음. Bluetooth USB SATA PCI-E Zigbee Thunderbolt 인터넷에 떠다니는 그림인데 원래 출처를 모르겠습니다. 문제가 되면 삭제하겠습니다. TCP/IP위 그림에서 보듯 TCP/IP는 OSI 7 Layer 대신 4개의 Layer로 정의해 둔 것이다. Network를 상호 연결시켜 정보를 전송하는 기능을 가진 여러 개의 Protocol을 집합으로 묶어둔 것이 특징이다. TCP/IP 각각의 Protocol이 하는 기능은 다음과 같다. 구분기능ProtocolDescription응용계층FTPFTP파일 전송 프로토콜TFTP소형 파일 전송 ProtocolE-mailSMTP메일 서버 간의 메일 송수신POP3메일 클라이언트와 메일 서버 간의 메일 송수신IMAP4POP3의 단점을 보완함 (원격 서버의 계층 구조를 유지한다.)TelnetTelnet다른 시스템으로 로그인하는 기능인터넷HTTPWeb service protocolSNMP데이터 흐름 정보 (네트워크 및 Host 상태 모니터링)DNSHost 이름에 대한 IP 주소 통보기능NFSSUN에서 개발한 분산형 파일 시스템전송계층End-to-End Data 전송TCP 연결 지향 ProtocolUDP비연결 지향 ProtocolSCTP신뢰성 제공 (UDP+TCP), 4Way handshaking 사용인터넷계층OSI의 Network 계층IP목적지까지 패킷 전달 (IPv4, IPv6)Routing (경로 설정)IPxPC용 Network, 경로 선택, 패킷 처리Gateway (Network 간 Data 연결)ARP논리 주소 IP로 물리 주소 IP(MAC)를 탐색RARP물리 주소 IP로 논리 주소 IP를 탐색BOOTB디스크 없는 시스템 부팅 시 사용DHCP임시 IP 주소 할당 기술ICMPIP 계층 이용 Message 교환 (ping)IGMP인터넷 그룹 관리, Multicast 그룹을 router에게 통보링크계층기존 표준 기술 지원Etehrenet기존의 모든 표준 Protocol과 기술적 호환성 유지X.25 출처 OSI OSI model 정보관리기술사 & 컴퓨터시스템응용기술사 - 성안당","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"network","slug":"Dev/network","permalink":"http://lazyrodi.github.io/categories/Dev/network/"}],"tags":[{"name":"network","slug":"network","permalink":"http://lazyrodi.github.io/tags/network/"},{"name":"osi7","slug":"osi7","permalink":"http://lazyrodi.github.io/tags/osi7/"},{"name":"tcp/ip","slug":"tcp-ip","permalink":"http://lazyrodi.github.io/tags/tcp-ip/"}]},{"title":"Jekyll에 Disqus 적용하기","slug":"2016-07-04-etc-disqus","date":"2016-07-04T04:47:50.000Z","updated":"2016-08-08T14:20:54.002Z","comments":true,"path":"2016/07/04/2016-07-04-etc-disqus/","link":"","permalink":"http://lazyrodi.github.io/2016/07/04/2016-07-04-etc-disqus/","excerpt":"","text":"이 문서는 Jekyll 시절에 작성한 것으로 현재 이 사이트에서 유효하지 않음. Disqus는 각종 Framwork을 이용한 웹사이트에 Comment를 달게 해주는 서비스이다. 사용자는 Disqus, Facebook, Twitter, Google 계정을 이용하여 comment를 남길 수 있다. 지원하는 Framework 이랄까 Platform이랄까… Universal Code Wordpress Blogger Tumblr Squarespace TypePad MovableType Drupal Joomla 목표github.io에 [Jekyll][Jekyll]을 이용해서 Static page들로 운영(?)을 하고 있는데, 각 post에 comment를 달아보려 한다. 1. Disqus 가입Disqus 에 가입한다. 가입 과정에 Installation이 있다. Universal Code를 선택한다. 2. Jekyll에 적용이후 /layouts/post.html 의 하단에 Disqus에서 제공하는 소스를 넣으면 끝난다. 너무 빨리 끝나서 놀랐다. (…) 12345678910111213141516171819202122<div id=\"disqus_thread\"></div><script> /** * RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS. * LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables */ /* var disqus_config = function () { this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable }; */ (function() { // DON'T EDIT BELOW THIS LINE var d = document, s = d.createElement('script'); s.src = '//lazyrodi.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); })();</script><noscript>Please enable JavaScript to view the <a href=\"https://disqus.com/?ref_noscript\" rel=\"nofollow\">comments powered by Disqus.</a></noscript> this.page.url과 this.page.identifier는 Disqus thread가 중복되어 생성되는 것을 방지하기 위해 설정하는데 다음과 같이 하면 된다. url에는 본인의 것을 넣으면 된다. 아래 \\는 코드가 반영되어 버려서 어쩔 수 없이 넣었다. 실제 소스에 반영할 땐 제거할 것. 12this.page.url = \"http://lazyrodi.github.io\\{\\{ page.url }}\";this.page.identifier = \"\\{\\{ page.url }}\"; 3. Comment count 표시하기thread 별로 달린 comment의 개수를 확인하여 post list나 각 post의 상단 등에 표시하기 위해서는 아래의 과정이 필요하다. </body> 전에 아래 코드 추가 default.html 에서 </body> 전에 아래 코드를 넣는다. 원래 의도대로면 count를 표시하고 싶은 page에만 넣으면 된다.12<script id=\"dsq-count-scr\" src=\"//lazyrodi.disqus.com/count.js\" async></script> count를 표시하기 원하는 page에 아래 코드를 넣는다. 12345<!-- post.html --><a href=\"\\{\\{ page.url }}index.html#disqus_thread\" data-disqus-identifier=\"\\{\\{ page.url }}\"></a><!-- index.html --><a href=\"\\{\\{ post.url }}index.html#disqus_thread\" data-disqus-identifier=\"\\{\\{ post.url }}\"></a> 주절주절local에서만 발생하는 이슈인지는 모르겠지만 Disqus 서버와의 동기화 속도가 조금 느린 것 같다. comment를 달았다가 삭제하면 한참 1 comment 상태로 남아있다. comment를 한 번 달아야 thread가 생성되어 그 전에는 0 comment 표시가 되지 않는 문제(?)가 있다. 페이지를 JavaScript로 억지로 구현해 두었는데 refresh 문제인지 그쪽엔 html code까지는 같은데 반영이 안 된다. ㅠㅠ","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"jekyll","slug":"jekyll","permalink":"http://lazyrodi.github.io/tags/jekyll/"},{"name":"disqus","slug":"disqus","permalink":"http://lazyrodi.github.io/tags/disqus/"}]},{"title":"[Java] Queue","slug":"2016-07-03-java-queue","date":"2016-07-03T04:47:50.000Z","updated":"2016-08-08T14:20:48.883Z","comments":true,"path":"2016/07/03/2016-07-03-java-queue/","link":"","permalink":"http://lazyrodi.github.io/2016/07/03/2016-07-03-java-queue/","excerpt":"","text":"Queue의 종류Queue는 공통적으로 Front, Rear 두 개의 pointer를 갖는다. Queue는 Array나 Linked List를 사용하여 구현한다. Simple or linear Queue 일반적으로 Linked-list로 구현된다. FIFO(First In First Out)의 기본을 기킨다. Circular Queue 말 그대로 head와 rear가 이어져있는 구조. Priority Queue 각 개체(node, item, etc.)가 Priority를 가지고 있어서 First in은 성립하지만 Priority가 높은 녀석이 먼저 나오게 된다. Dequeue Double-Ended Queue를 의미하며 front (head) 쪽과 back (tail) 쪽 모두에서 추가/삭제될 수 있는 구조를 말한다.Head-tail linked list 라고도 불리운다. java.util.QueueJava에서는 기본적으로 Queue를 지원한다. (실제로 이걸 사용한 코드는 적어도 회사에서는 본 적 없다.) 이 Queue는 Java Collections Framework에 속한 interface로 세부 내용들은 구현 해주어야 한다. Java Collections Framework에 속한 interface들은 다음과 같은 것들이 있다. java.util.Collection에 속한 것들 java.util.Set java.util.SortedSet java.util.NavigableSet java.util.Queue java.util.concurrent.BlockingQueue java.util.concurrent.TransferQueue java.util.Degue java.util.concurrent.BlockingDegue java.util.Map에 속한 것들 java.util.SortedMap java.util.NavigableMap java.util.concurrent.ConcurrentMap java.util.concurrent.ConcurrentNavigableMap 이걸 바로 사용하기 위해서는 아래와 같은 방식으로 사용 용도에 맞게 초기화 해주어야 한다. 12345Queue<Integer> aQueue = new LinkedList<Integer>();Queue<Integer> bQueue = new PriorityQueue<Integer>();Queue<Integer> cQueue = new LinkedBlockingQueue<Integer>();Queue<Integer> dQueue = new ArrayBlockingQueue(20);Queue<Integer> eQueue = new PriorityBlockingQueue<Integer>(); 해보자기본적인 add (혹은 push)와 poll (혹은 pop)을 Linked List로 구현해보자. Node사용할 Node를 만든다. 123456789101112public class Node<T> { private T item; Node<T> next; Node(T item) { this.item = item; } public T getItem() { return item; }} QueueNode를 이용한 Queue를 생성한다. 1234567891011121314151617181920212223242526272829303132333435363738public class MyQueue<T> { Node<T> head; Node<T> rear; MyQueue() { head = null; rear = null; } public void add(T item) { if (head == null) { head = new Node<T>(item); rear = head; } else { Node<T> newNode = new Node<T>(item); rear.next = newNode; rear = newNode; } } public T poll() { Node<T> tmp = null; T ret = null; ret = (head != null) ? head.getItem() : null; if (head == rear) { head = null; } else { tmp = head; head = head.next; tmp = null; } return ret; }} Use그리고 사용하자. 아래 예시에서는 3개를 넣고 4개를 빼기 때문에 NullPointerException이 발생한다. 1234567891011121314151617181920public class Main { public static void main(String[] args) { MyQueue<Integer> mQueue = new MyQueue<Integer>(); mQueue.add(1); mQueue.add(2); mQueue.add(3); try { System.out.println(mQueue.poll()); System.out.println(mQueue.poll()); System.out.println(mQueue.poll()); System.out.println(mQueue.poll()); } catch (NullPointerException e) { System.out.println(\"null\"); } }} 출처아래의 글들을 교재삼아 작성하였습니다. http://krishneshsingh.blogspot.kr/p/what-is-queue-explain-types-of-queue.html javadoc-queue javadoc-collection","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"java","slug":"Dev/java","permalink":"http://lazyrodi.github.io/categories/Dev/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://lazyrodi.github.io/tags/java/"},{"name":"algorithm","slug":"algorithm","permalink":"http://lazyrodi.github.io/tags/algorithm/"},{"name":"data structure","slug":"data-structure","permalink":"http://lazyrodi.github.io/tags/data-structure/"},{"name":"queue","slug":"queue","permalink":"http://lazyrodi.github.io/tags/queue/"}]},{"title":"Regular Expression (정규표현식)","slug":"2016-07-02-etc-regular-expression","date":"2016-07-02T14:02:50.000Z","updated":"2016-08-08T14:20:40.562Z","comments":true,"path":"2016/07/02/2016-07-02-etc-regular-expression/","link":"","permalink":"http://lazyrodi.github.io/2016/07/02/2016-07-02-etc-regular-expression/","excerpt":"","text":"Regular Expression(정규 표현식)은 줄여서 REGEX(레젝스) 라고 부르기도 한다. 특정한 패턴의 문자열을 검색하고 필요하면 편집하기 위해 사용한다. 정규 표현식은 다음과 같이 분류할 수 있다. POSIX Regular Expression (UNIX 계열 표준 정규 표현식) BRE (Basic Regular Expression) ERE (Extended Regular Expression) BRE에 추가적인 메타 문자 등을 제공 PCRE (Perl Compatible Regular Expression) ?, +, {} 등의 메타 문자는 ERE에서만 사용 가능하다. Meta character (메타 문자) a.k.a Wild card characterIT용어사전에서 정의하고 있는 메타 문자의 사전적인 의미는 다음과 같다. 정규 표현식에서의 메타 문자는 다른 문자의 정보를 운반하기 위해 프로그램 소스나 데이터에 끼워 넣는 문자. 예를 들면 C언어 프로그램에 사용된 백슬레시가 그 예인데, 이 문자 다음에 오는 문장은 확장 문자열(escape sequence)에 속하는 부분으로서 주변 장치와 프로그램에 대해서 어떤 명령을 수행하도록 하는 제어 문자로 사용된다.[네이버 지식백과] 메타 문자설 명비 고 .모든 문자 |왼쪽 또는 오른쪽과 일치cat|dog []문자 집합 중 하나와 일치[a-z]면 a-z 중 하나 [^]문자 집합을 제외하고 일치[^a-z]면 a-z가 아닌 것 중 하나 *문자가 0개 이상 반복될 때 +문자가 1개 이상 반복될 때 {n}문자가 n번 반복될 때a{3} {m, n}문자가 m번 이상 n번 이하 반복될 때 {n,}문자가 n번 이상 반복될 때 \\Escape [\\b]Back space \\fForm Feed \\nLine Feed \\rCarrige Return \\tTab \\vVertical Tab \\d0~9 사이의 숫자 하나[0-9] \\D숫자를 제외한 문자 하나[^0-9] \\w대소문자와 밑줄을 포함하는 모든 영숫자[a-zA-Z0-9] \\W영숫자가 아니거나 밑줄이 아닌 모든 문자[^a-zA-Z0-9] \\s모든 공백 문자[\\f\\n\\r\\t\\v]\\S공백 문자가 아닌 모든 문자[^\\f\\n\\r\\t\\v]\\x16진수 표현\\x0A (=ascii 10 == \\n)\\08진수 표현\\011 (=ascii 9 == \\t)\\c제어문자\\cZ (Ctrl+Z)^, \\A문자열의 시작 ([] 밖에 있을 때) $, \\Z문자열의 끝 \\< 단어의 시작과 일치 \\> 단어의 끝과 일치 \\b 단어 경계와 일치 \\B 단어 경계가 아닐 때 일치 () group 또는 back reference를 정의한다. ?= 전방탐색 ?<= 후방탐색 ?! 부정형 전방탐색 ?<! 부정형 후방탐색 \\l 다음에 오는 글자를 소문자로 변환 \\L \\E를 만날 때까지 모든 문자를 소문자로 변환\\u 다음에 오는 글자를 대문자로 변환 \\U \\E를 만날 때까지 모든 문자를 대문자로 변환\\E \\L 또는 \\U 의 End point 줄 바꿈 Windows: \\r\\n UNIX, LINUX: \\n 읽는 법 !: Exclamation point “: Quotation mark ‘: Apostrophe `: Grave .: Period ^: Caret *: Asterisk -: Hyphen _: Underscore ~: Tilde &: Ampersand (): Parenthesis {}: Brace []: Bracket <>: Chevron Back reference 매칭된 결과를 다시 사용하는 패턴 ()로 묶인 패턴 매칭을 \\# 형태로 재사용할 수 있다. #은 숫자. \\1, \\2, \\3 … ex. (a) = \\1 의 매칭 결과는 a = a 가 된다. HTML Tag를 parsing할 때 굉장히 효율적이다. ex. <(table)>[.]*<\\/\\1> REGEX in JAVAJAVA에서 정규 표현식을 사용하여 Pattern을 찾기 위해서는 아래와 같이 코드를 사용한다. 1234567891011121314151617181920import java.util.regex.Matcher;import java.util.regex.Pattern;public class Main { public static void main(String[] args) { String data = \"dog, cat and wolfwolfwolfwolfwolf\"; Pattern p = Pattern.compile(\"^(dog), (cat) and (wolf){3}\"); Matcher m = p.matcher(data); if (m.find()) { System.out.println(m.group(0)); // 매칭된 full string System.out.println(m.group(1)); // 매칭된 full string 내의 첫 번째 group System.out.println(m.group(2)); System.out.println(m.group(3)); System.out.println(m.group(4)); // 이 예제에서 4번째 group은 없으므로 exception 발생 } }} 위 코드의 수행 결과는 다음과 같다. 1234567dog, cat and wolfwolfwolfdogcatwolfException in thread \"main\" java.lang.IndexOutOfBoundsException: No group 4 at java.util.regex.Matcher.group(Unknown Source) at com.lazyrodi.Main.main(Main.java:19) 위 코드에서 일치하는 다음 문장을 찾기 위해서는 m.find() 를 한 번 더 수행하면 다음 매칭되는 문장을 찾는다. Pattern Class 및 Matcher Class에 대한 자세한 정보는 JavaDoc (Pattern, Matcher)을 참조하자. Tiphttps://regexper.com 라는 웹사이트에서 정규 표현식을 시각적으로 확인할 수 있게 도와주고 있다. 개인적으로 짱짱맨 사이트라고 생각한다. JavaScript에 대한 정규표현식 사용은 Mozilla 사이트에서 잘 설명하고 있다. 예시 11^(dog), (cat) and (cat){3} Regexper 결과 1 출처아래의 글들을 교재삼아 작성하였습니다. Regular Expression - Mozilla Pattern Class - JavaDoc Matcher Class - JavaDoc","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"common","slug":"Dev/common","permalink":"http://lazyrodi.github.io/categories/Dev/common/"}],"tags":[{"name":"regex","slug":"regex","permalink":"http://lazyrodi.github.io/tags/regex/"},{"name":"regular expression","slug":"regular-expression","permalink":"http://lazyrodi.github.io/tags/regular-expression/"}]},{"title":"Memory management","slug":"2016-07-01-common-memory-management","date":"2016-07-01T11:00:00.000Z","updated":"2016-08-05T12:40:33.860Z","comments":true,"path":"2016/07/01/2016-07-01-common-memory-management/","link":"","permalink":"http://lazyrodi.github.io/2016/07/01/2016-07-01-common-memory-management/","excerpt":"","text":"CPU가 접근 가능한 저장소는 CPU 내부의 Register그리고 Main memory이다. CPU는 여기에서 command를 load하여 processing 한다. 보통 CPU는 register에 1 clock cycle 내에 접근이 가능하다. 하지만 만약 command가 Main memory에 있을 경우 1 clock이상의 시간이 소요되어 stall(지연)된다. 이러면 낭비가 심해지기 때문에 CPU와 Main memory 사이에 Cache를 두어 문제를 해결한다. Program은 Binary execution file 형태로 디스크에 저장되어 있으며 실행되기 위해서는 Main memory로 올라와서 Process가 되어야 한다. Process는 효율적인 실행을 위해 디스크와 Main memory 사이를 왔다갔다 하는데 이 때 Main memory에 올라오기 위해 대기하고 있는 Process들의 집합을 Input Queue라 부른다. 코드에서 사용하는 symbol 형태(ex. pointer, *a)는 Compiler에 의해 Relocatable address(재배치 가능 주소)로 binding되고 이는 linkage editor 또는 loader에 의해 Absolute address(절대 주소, 물리 주소)로 binding된다. Binding은 시점에 따라 세 가지로 나눌 수 있다. Compile time binding Compile time에 Memory 내의 위치를 직접 알 수 있으면(process가 R번지부터 시작한다는 사실) Compiler는 Absolute code(절대 코드)를 생성할 수 있다. 하지만 이 위치가 변경되어야 한다면 이 코드는 다시 compile되어야 한다. Load time binding Compile time에 적재 장소를 알 수 없다면 Compiler는 Binary code를 Relocatable code로 만들어야 한다. Relocatable code는 시작 주소가 변경되더라도 사용자 코드를 다시 reload만 하면 된다. Excution binding Process가 실행되는 중간에 memory 내의 한 segment에서 다른 segment로 옮겨질 수 있을 때 “바인딩이 실행 시간까지 허용되었다” 라고 이야기 하며, 이것이 가능하려면 특별한 Hardware를 이용해야 한다. CPU가 생성하는 주소를 Logical address(논리 주소)라 하며, Memory가 다루는 주소를 Physical address(물리 주소)라 한다. Compile time binding, Load time binding에서는 Logical address와 Physical address가 같지만 Excution binding에서는 서로 다르다. 이 때 사용하는 Logical address를 Virtual address라고 부른다. Virtual address 프로그램 실행 중 Physical address로 바뀌어야 하는데 이 바꾸는 작업을 Mapping이라 하며 MMU (Memory Management Unit)에서 작업이 이루어진다. Dynamic Loading(동적 적재) Physical memory 크기의 제한때문에 큰 program의 경우 미리 memory에 모두 올라와있지 못한다. 이를 해결하지 위해 Dynamic Loading이 사용된다. 각 routine은 실제 호출되기 전까지는 memory에 올라오지 않고 relocatable 상태로 disk에 대기하고 있다가 Main program이 memory에 올라와 실행되고 이 routine이 다른 routine을 호출하게 되면 호출된 routine이 이미 memory에 laoding되어 있는지 확인한다. 없으면 Relocatable linking loader가 호출되어 요구된 routine을 memory로 가져오고 이러한 변화를 테이블에 기록해 둔다. 그 후 CPU 제어는 중단되었던 routine으로 보내진다. Swapping Process가 실행되기 위해서는 memory에 있어야 하지만 항상 점유하고 있을 수는 없다. 따라서 실행되는 도중에 임시로 Sub memory로 보내졌다가 다시 Main memory로 돌아올 수 있다. Swapping을 변형하여 우선순위를 두어 처리하는 것을 Roll-in, Roll-out 이라고도 부른다. Swapping system은 Context-switching time이 상당히 오래 걸린다. Fragmentation memory의 이곳 저곳을 점유하다보면 크기에 따라 중간중간 비는 공간이 나타나게 되고 이를 Fragmentation이라 부른다. Fragmentation 문제를 해결하기 위해 두 가지 기법을 사용할 수 있다. Paging Segmentation Paging Physical memory는 frame이라는 같은 크기의 block들로 나누어져 있고 Logical memory는 page라는 같은 크기의 block들로 나누어져 있다. CPU에서 나오는 모든 주소는 Page number(p)와 Offset(d)로 구성된다. Page number는 Page table에 access할 때 사용되며 Page table은 Main memory에서 각 page가 점유하는 주소를 가지고 있다. 이 주소에 Offset을 더하면 원하는 Physical address가 된다. {:class=”img-responsive”} Frame 및 Page의 크기는 Hardware에 의해 정의되며 Page의 크기는 일반적으로 512 byte ~ 16 MB 사이이며 2의 제곱으로 증가한다. Logical address의 크기가 2^m 이고 page가 2^n의 크기를 갖는다면 Logical address의 상위 m-n 비트는 page number를 나타내고, 하위 n 비트는 page offset을 나타낸다. {:class=”img-responsive”} Segmentation 사용자는 하나의 Program을 subroutine, procedure, function 또는 module들을 가지고 있는 것으로 생각하고, Table, Array, Stack 등의 다양한 변수를 사용한다. 이러한 사용자가 바라보는 Memory의 관점을 그대로 지원하는 Memory Management 기법이다. 쉬운 구현을 위해 Segment name 대신 Segment number가 System에 의해 매겨지고 Segment는 number로 불리운다. 때문에 Logical address는 <segment-number, offset>으로 구성된다. {:class=”img-responsive”} Virtual Memory Process 전체가 Memory에 올라오지 않더라도 실행이 가능하게 하는 기법이다. Physical memory로부터 사용자 관점의 Logical memory를 분리시켜 Main memory를 균일한 크기의 저장 공간으로 구성된 엄청나게 큰 배열로 추상화시켜 준다. 장점 (Physical) Memory size의 제약으로부터 자유로워진다. File의 공유를 쉽게 해주고 Shared memory 구현을 가능케 한다. Process 생성을 효율적으로 처리할 수 있는 Mechanism을 제공한다. 많은 program을 동시에 수행 가능하고 이에 따라 응답 시간(response time, turnaround time)은 늘어나지 않으면서도 CPU 이용률(utilization)과 처리율(throughput)이 높아진다. swap하는데 필요한 입/출력 횟수가 줄어들어 program들이 상대적으로 빠르게 실행된다. 단점 구현하기 어렵다. 잘못 사용하면 성능이 크게 저하된다. 앞에서 본 Memory management 기법 중 Dynamic loading은 process 전체를 memory에 올려야 한다는 제약을 어느정도 막아주긴 하지만 프로그래머가 추가적인 작업을 해야한다. 앞서 본 것처럼 page frame들로 인해 Physical memory는 연속적인 공간이 아닐 수 있다. MMU(Memory Management Unit)는 Physical memory를 Logical memory로 mapping한다. Virtual Memory의 각 area는 다음과 같이 사용된다. Stack main() 함수, Callback 함수의 주소, 지역변수, 파라미터, return value가 저장되는 영역이다. Sparse 빈 공간을 포함한 Stack과 Heap 사이의 영역이다. Heap 동적 메모리 할당 영역이다. Data 전역변수, 정적변수(static), 배열, 구조체, 상수가 저장되는 영역이다. Data 영역은 두 가지 영역으로 구분할 수 있다. Data 초기값이 있는 경우 BSS (Block Started by Symbol) 초기값이 없는 경우우 Code 작성한 Code가 저장되는 영역이다. {:class=”img-responsive”} Code, Data, Stack 영역은 Compiler가 Memory의 크기를 결정한다. Heap 영역의 크기는 개발자에 의해 프로그램 동작 시 결정된다. Stack의 지역변수는 사용 후 소멸하므로 데이터 용량이 불확실하다. 따라서 밑에서부터 올라가면서 값이 채워진다. Heap overflow Heap 영역이 Stack 영역을 침범한 경우 Stack overflow Stack 영역이 Heap 영역을 침범한 경우 아래 그림은 wikipedia에서 설명하고 있는 동적 할당에 대한 그림이다. {:class=”img-responsive”} 출처아래의 글들을 교재삼아 작성하였습니다. http://blog.naver.com/k5248/220724196178 https://ko.wikipedia.org/wiki/%EB%8F%99%EC%A0%81_%EB%A9%94%EB%AA%A8%EB%A6%AC_%ED%95%A0%EB%8B%B9 Operating System Concepts (공룡책)","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"common","slug":"Dev/common","permalink":"http://lazyrodi.github.io/categories/Dev/common/"}],"tags":[{"name":"computer memory","slug":"computer-memory","permalink":"http://lazyrodi.github.io/tags/computer-memory/"}]},{"title":"Fundamental of Database","slug":"2016-04-13-db-fundamental-of-database","date":"2016-06-30T11:00:00.000Z","updated":"2016-08-05T12:41:01.380Z","comments":true,"path":"2016/06/30/2016-04-13-db-fundamental-of-database/","link":"","permalink":"http://lazyrodi.github.io/2016/06/30/2016-04-13-db-fundamental-of-database/","excerpt":"","text":"Database는 간단히 데이터들의 모임 이라고 보면 된다. 아래의 속성들을 충족해야 한다. 의존성 일관성 보안성 경제성 무결성 결론뜬금없이 두괄식 정리를 해본다. Database장점단점HDB구조가 간단하고 판독이 용이하다.데이터 상호 간의 유연성이 부족하다.구현, 수정, 검색이 용이하다.검색 경로가 한정되어 있다.데이터 액세스 속도가 빠르다.삽입/삭제 연산이 매우 복잡하다.데이터의 사용량을 쉽게 예측 가능하다.다 대 다 관계를 처리하기 어렵다.NDB상하 종속적인 관계를 쉽게 해결한다.구성, 설계가 복잡하다.데이터의 종속성을 해결하지 못한 시스템이다.RDB데이터의 일관성을 보증할 수 있음. (Transaction)대량 데이터 처리 시 성능 이슈.정규화를 전제로 하기 때문에 갱신 시의 비용이 적게 든다.갱신이 발생한 Table의 인덱스 생성이나 스키마 변경에 대한 처리 이슈.JOIN 등 복잡한 검색 조건으로 검색이 가능함.Column을 확실히 정의하기 어려운 경우에 대한 처리 이슈.간결하고, 보기 편하다.단순히 빨리 결과를 조회하고자 할 때의 성능 이슈.OODBNoSQL대량의 Record 처리에 유리하다.JOIN 연산이 불가능하다. Database의 구성요소 Database Schema DBMS (Database Management System) Database Language Database Storage User Database의 종류 File System - File type HDB (Hierachical Database, 계층형 데이터베이스) - Segment type NDB (Network Database, 네트워크형 데이터베이스) - Record type RDB (Relational Database, 관계형 데이터베이스) - Table type OODB (Object-Oriented Database 객체지향형 데이터베이스) - Class type NoSQL (Not only SQL) Database의 종류File System말 그대로 파일시스템을 의미한다. HDB (Hierachical Database) 트리구조를 기반으로 하는 계층형 데이터 모델을 사용한다. 데이터는 트리 형태로 구성되며 각 데이터 요소(Entity)들은 상하 관계를 나타내는 Link로 구성된다. 제품 Adabas GT.M IMS MUMPS Cache Metakit Multidimensional hierachical toolkit Mumps compiler DMSII FOCUS NDB (Network Database) 그래프 구조를 기반으로 하는 네트워크형 데이터 모델을 사용한다. 이는 Entity와 Entity 간의 Relationship을 그래프로 연결한다. HDB와 비슷하지만 부모(상위 Entity)를 여러 개 가질 수 있다. 제품 IDS IDMS RDM Embedded RDM Server 터보이미지 유니박 DMS-1100 RDB (Relational Database) 현재까지 가장 안정적이고 효율적인 Database로 알려져있다. Entity를 Table로 사용하고 Entity 간의 공동 Attribute를 이용하여 서로 연결하는 독립된 형태의 데이터 모델이다. SQL을 사용한다. 제품 Oracle (Oracle) MS-SQL Serve (Microsoft) MySQL (Oracle -SunMicroSystems-) DB2 (IBM) Infomix (IBM) Sybase (Sybase) Derby (Apache) SQLite (Opensource) OODB (Object-Oriented Database) Class, Object, Attribute, Method, Instance, Capsulation, Inheritance 등을 기반으로 데이터를 구조화하는 데이터 모델이다. 비지니스형 데이터 타입만 처리되는 RDBMS의 기본적인 제한점을 극복하기 위해 고안되었다. NoSQL (Not only SQL) SQL을 사용하지 않는다는 뜻이다. Schema가 없다. 대용량 데이터 처리에 유리하고 분산 처리가 가능하여 Cloud computing에 유리하다. 종류 Key/Value type Memchached Tokyo Tyrant Flare Roma Redis Document type: 여러가지 형태의 값들을 모아둔 논리적 구조. MongoDB CouchDB Big Table (Column) type: RDB는 Row 단위로 데이터를 관리하지만 Colume type은 Column 단위로 데이터를 관리한다. HBase Casandra Hypertable Schema (Meta-Data)Database의 구조 및 제약 조건에 대해 전반적으로 기술한 것을 Schema라고 한다. Entity(Table), Attribute(Field), Relationship 및 데이터 조작 시 데이터 값들이 갖는 제약 조건 등에 대해 정의한다. Schema는 Data Dictionary에 저장된다. 쉽게 말해 Data structure가 어떻게 생겼는지 정의하며, RDB에서는 아래 코드처럼 생겼다. 12345678910Player ( name CHAR(20), number INTEGER(4));Status ( number INTEGER(4), point INTEGER(4), trophy CHAR(20)); DBMS기존 파일 시스템이 갖는 데이터 종속성과 중복성 문제를 해결하기 위해 제안된 시스템으로 모든 Application들이 Database를 공용으로 사용할 수 있도록 관리한다. DBMS의 궁극적 목표는 데이터의 독립성이다. 논리적 독립성: Application과 Database를 독립시켜 Data에 변경이 발생하여도 Application은 변경되지 않는다. 물리적 독립성: Application과 Storage를 독립시켜 디스크에 변경이 발생하여도 Application은 변경되지 않는다. DBMS의 필수 기능 정의 (Definition): Type 및 Structure에 대한 정의, 이용 방식, 제약 조건을 정의한다. 조작 (Manipulation): 데이터의 검색, 갱신, 삽입, 삭제 등의 처리를 위해 User와 Database 사이의 Interface를 제공하는 것을 의미한다. 제어 (Control): 데이터의 정확성, 무결성, 보안 및 권한 검사, 병행수행 제어 등의 기능을 정한다. Releationship (관계) 일 대 일 일 대 다 다 대 다 Key조건을 만족하는 Tuple을 찾거나 순서대로 정렬할 때 Tuple들을 구분할 수 있는 기준이 되는 Attribute를 의미한다. Candidate Key (후보키)Relation을 구성하는 Attribute들 중에서 Tuple을 식별하기 위해 사용하는 Attribute들의 부분 집합을 의미한다. Primary Key로 사용할 수 있는 Attribute이다. ex) 주민 Relation에서의 주민등록번호 또는 지문 유일성 (Unique): 하나의 Key 값으로 하나의 Tuple만을 식별할 수 있어야 한다. 최소성 (Minimality): 모든 Record들을 유일하게 식별하는데 꼭 필요한 Attribute들로만 이루어져야 한다. Primary Key (기본키)Candidate Key 중 선택한 Main Key를 의미하며 NULL 이 될 수 없다. Alternate Key (대체키)Candidate Key가 두 개 이상일 때 Primary Key를 제외한 나머지 Candidate Key들을 의미하며 보조키라고도 부른다. Super Key (슈퍼키)하나의 Table 내에 있는 Attribute들의 집합으로 구성된 Key를 말한다. Table을 구성하는 모든 Tuple 중 Super Key로 구성된 Attribute의 집합과 동일한 값은 나타나지 않는다. Super Key는 Table을 구성하는 모든 Tuple에 대해 유일성은 만족시키지만 최소성은 만족시키지 못한다. Foreign Key (외래키)두 개의 Table이 Relationship을 맺고있을 때 A의 Primary Key와 같은 B의 Attribute 를 Foreign Key 라 부른다. 즉, 아래 그림에서 A가 B를 참조한다고 하면 B의 주민등록번호는 Primary Key가 되고 A의 주민등록번호는 Foreign Key가 된다. Field가 적어서 예시가 적절하지 않은 것도 같고… {:class=”img-responsive”} Foreign Key를 사용하면 실수로 Data를 삭제하는 것을 막을 수 있다. 다른 Table의 Foreign Key로 참조하고 있는 Row는 Table에서 삭제할 수 없기 때문이다. 이를 참조 무결성(Reference Integrity)이라고 부른다. Integrity (무결성) 이란 데이터를 보호하고 항상 정상인 상태를 유지하는 것을 말한다. 개체 무결성 Table에서 Primary Key를 구성하는 Attribute는 NULL이나 중복값을 가져서는 안 된다. 참조 무결성 Foreign Key의 값은 NULL이나 참조 Table의 Primary Key값과 동일해야 한다. 즉, Table은 참조할 수 없는 Foreign Key의 값을 가질 수 없다. (실수로 삭제하는 것을 예방할 수 있는 이유이다.) Foreign Key와 참조하려는 Table의 Primary Key는 Domain과 Attribute의 개수가 같아야 한다. Transaction한 단위를 이루는 일련의 연관된 데이터베이스 조작. 하나의 Transaction에 속하는 작업 중 하나라도 실패하면 Transaction 전체가 실패한 것으로 간주하여 변경한 내용을 모두 원래대로 되돌려 놓는다. 이를 Rollback이라 한다. 모든 작업이 성공적으로 처리되면 모든 변경 내용을 한꺼번에 반영하고 이를 Commit이라 한다. Transaction의 특성 (ACID) Atomicity (원자성) Transaction에 포함된 모든 작업이 성공적으로 처리되지 않으면 어떠한 작업도 처리되지 않아야 한다. Consistency (일관성) Transaction의 시작 전과 종료 후의 Database가 일관된 상태를 유지해야 한다. 참조 무결성이 깨져서는 안된다. Isolation (고립성) 하나의 Transaction에서 Database를 변경한 내용은 Transaction이 Commit될 때까지 다른 어떤 Query나 Transaction과도 고립되어야 한다. Durability (영속성) Commit이 이루어지면 Transaction에 의해 변경된 내용은 영구적으로 유지되어야 한다. DBMS는 Database의 현재 상태가 유실되지 않도록 시스템 충돌 등의 문제로부터 복구할 수 있는 방안을 갖춰야 한다. 용어 Table Relation(관계) 또는 Entity(개체)라 부른다. Column(열, ↕) 과 Row(행, ↔) 으로 구성된다. Field Attribute(속성)이라고도 한다. Table의 Column을 의미한다. Degree(차수) Attribute의 수를 뜻한다. Record Tuple 이라고도 하며 Table의 Row를 의미한다. Cardinality 하나의 Relation을 구성하는 Tuple(Record)의 수를 의미한다. Domain 하나의 Attribute가 가질 수 있는 같은 type의 Atomic(원자) 집합 을 의미한다. 예) 학생 relation에서 학년의 domain은 1 ~ 6 이다. DBMS (Database Management System) DDL (Data Definition Language) 테이블을 생성하고 삭제하는 언어를 뜻한다. CREATE, ALTER, DROP DML (Data Manipulation Language) = 서브 언어 User가 데이터를 처리할 수 있게 도와주는 도구. SELECT, INSERT, UPDATE, DELETE DCL (Data Control Language) 데이터의 보호, 관리를 위해 사용된다. COMMIT, ROLLBACK, GRANT, REVOKE Scale up 사용 중인 서버를 고성능으로 바꿔 처리 능력을 향상시키는 방법으로 비용이 발생하지만 소스에 대한 변경이 없다. Scale down 저가의 여러 장비를 사용하여 능력을 향상시키는 방법으로 소스에 대한 수정이 필요하다. 주로 NoSQL에서 제공하는 방식이다. 출처아래의 글들을 교재삼아 작성하였습니다. http://dreamzelkova.tistory.com/393 http://blog.naver.com/bobojisu/220561467061 http://ourcstory.tistory.com/30 http://cafe.naver.com/junes81/5895 https://ko.wikipedia.org/wiki/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4_%EB%AA%A8%EB%8D%B8","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"database","slug":"Dev/database","permalink":"http://lazyrodi.github.io/categories/Dev/database/"}],"tags":[{"name":"database","slug":"database","permalink":"http://lazyrodi.github.io/tags/database/"}]},{"title":"File IO(Input/Output)","slug":"2016-06-29-java-fileio","date":"2016-06-29T12:02:50.000Z","updated":"2016-08-08T14:20:32.866Z","comments":true,"path":"2016/06/29/2016-06-29-java-fileio/","link":"","permalink":"http://lazyrodi.github.io/2016/06/29/2016-06-29-java-fileio/","excerpt":"","text":"JAVA에서의 파일 입출력은 Stream(데이터의 흐름)을 통해 이루어진다. Stream은 다음과 같이 나눌 수 있다. 흐름의 방향 Input Output Data Type Byte Character Byte streamByte stream의 경우 아래와 같은 Class들을 사용할 수 있다.[ Tutorials Point 에서 퍼온 그림 ] 아래 소스는 input.txt를 8-bit Byte 단위로 data를 읽어서 output.txt 에 복사한다. 12345678910111213141516171819202122232425262728293031import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class FileIO { public static void main(String[] args) throws IOException { FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream(\"input.txt\"); out = new FileOutputStream(\"output.txt\"); int c; while ((c = in.read()) != -1) { out.write(c); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } }} Character streamCharacter stream은 16-bit unicode 단위로 data를 처리한다. Byte stream과 다른 점은 FileReader와 FileWriter를 사용한다는 것이다. 이 두 가지 Class는 내부적으로는 FileInputStream과 FileOutputStream을 사용하지만 한 번에 2 byte 씩 처리한다는 것이 차이점이다. 아래 소스는 input.txt를 16-bit, 2 Byte 단위로 data를 읽어서 output.txt 에 복사한다. 123456789101112131415161718192021222324252627282930import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileIO { public static void main(String[] args) throws IOException { FileReader in = null; FileWriter out = null; try { in = new FileReader(\"input.txt\"); out = new FileWriter(\"output.txt\"); int c; while ((c = in.read()) != -1) { out.write(c); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } }} Scanner위에서 본 것과 같이 입력 파일 처리 시 Byte 또는 Character 단위로 read 하기 때문에 (물론 BufferedReader는 Line 단위로 읽을 수 있지만) Parsing이 상당히 귀찮아진다. Java 5부터 추가된 Scanner를 사용하면 귀찮은 작업을 건너뛸 수 있다. 변수 및 메소드에 대한 정의는 여기를 참조하자. 다양한 type을 지원하며, 정규표현식 (REGEX-Regular Expression-)도 지원한다. 주의할 점은 Scanner 또한 File을 다루기 때문에 close()를 해줘야 한다는 것과 character type과 관련된 메소드는 제공하지 않는다는 것이다. 아래 코드는 input.txt에서 내용을 읽어서 parsing 후 콘솔로 출력한다. 12345678910111213141516import java.io.File;import java.io.IOException;import java.util.Scanner;public class FileIO { public static void main(String[] args) throws IOException { Scanner sc = new Scanner(new File(\"input.txt\")); // File 대신 System.in 을 받을 수도 있다. while (sc.hasNext()) { System.out.println(sc.next()); } sc.close(); }} 추가정보 flush() - Buffer의 내용을 출력(흘려보내고)하고 비운다. close() - 메모리가 낭비되지 않도록 close()를 호출해줘야 하고, close()가 호출 될 때 자동으로 flush()가 수행된다. File Class - File Class가 가지는 메소드들에 대해서는 여기를 참조하자. BufferedInputStream - JavaDoc - BufferedInputStream BufferedOutputStream - JavaDoc- BufferedOutputStream BufferedReader - JavaDoc - BufferedReader BufferedWriter - JavaDoc - BufferedWriter BufferedReader에는 BufferedInputStream에는 없는 readLine() 메소드가 존재하고 BufferedWriter에는 BufferedOutputStream에는 없는 newLine() 메소드가 존재한다. 출처아래의 글들을 교재삼아 작성하였습니다. Tutorials Point - Java java-api","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"java","slug":"Dev/java","permalink":"http://lazyrodi.github.io/categories/Dev/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://lazyrodi.github.io/tags/java/"},{"name":"fileio","slug":"fileio","permalink":"http://lazyrodi.github.io/tags/fileio/"}]},{"title":"Opensource License","slug":"2016-06-28-etc-opensource-license","date":"2016-06-28T09:42:50.000Z","updated":"2016-08-08T14:20:28.465Z","comments":true,"path":"2016/06/28/2016-06-28-etc-opensource-license/","link":"","permalink":"http://lazyrodi.github.io/2016/06/28/2016-06-28-etc-opensource-license/","excerpt":"","text":"Software 지적재산권 저작권 특허권 상표권 영업비밀 저작권 (Copyright) 창작물에 대해 창작자(저작자)가 취득하는 권리로 창작과 동시에 권리가 발생한다. 소프트웨어도 작성 시 저작권이 발생하며 저각권은 개인 또는 소속 회사에 부여된다. 저작권자의 허락 없이는 해당 저작물을 복제, 배포 수정할 수 없다. 특허권 (Patent) 발명에 관하여 발명자(특허권자)가 갖는 독점배타권을 말한다. 출원을 통해 등록되어야만 권리가 발생한다. 언어와 관계 없이 방식이 동일하면 소프트웨어 특허에 걸린다. 상표권 (Trademark) 지정상품에 대해 등록상표를 사용할 독점적인 권리를 말한다. 특허와 마찬가지로 등록해야 한다. 상표를 사용하기 위해서는 반드시 상표권자의 허락을 받아야 한다. 영업비밀 비공개 소프트웨어어같은 경우가 해당한다. 영업비밀의 경우 공개되면 법적으로 보호받기 어렵다. 저작권특허권권리발생창작과 동시에 발생특허출원, 심사, 등록권리내용인격권 (공표권, 성명표시권, 동일서유지권)독점배타권 실시권효력범위표현(코드)의 실질적 유사성아이디어 (알고리즘, 기능)의 동일성 Software License 오픈소스에도 지적재산권이 있다. 일반적으로 느슨한 License를 적용하여 수정, 복제, 배포에 별 제약을 두지 않지만 몇 가지 지켜야 할 사항이 있다. 개발자, 기여자, 저작권 정보 표시 코드를 수정한 경우 수정한 정보 표시 수정자, 수정일 등을 기입하여 원본과 구별한다. License 정보 제공 일반인이 해당 License를 잘 이해할 수 있도록 License 정보를 표시 Copyleft (동일한 License로 재 배포)할 것 GPL 등이 대표적이며, 수정한 소스를 배포할 때에도 이전과 동일한 License를 사용해야 한다. Copyleft License들은 소프트웨어 배포 시 소스코드까지 함께 배포할 것을 요구한다. BSD (Berkeley Software Distribution) 배포되는 프로젝트들이 미국 정부의 재원을 사용했기 때문에 소스코드를 공개하지 않아도 된다. 다만 배포 시 저작권 표시, 보증 책임이 없음을 표시해야 한다. 이것만 지킨다면 상용 소프트웨어에도 무제한 사용이 가능하다. 아래 내용이 포함되어야 한다. 1234567891011121314151617181920212223242526The BSD LicenseThe following is a BSD license template. To generate your own license, change the values of OWNER, ORGANIZATION and YEAR from their original values as given here, and substitute your own. Also, you may optionally omit clause 3 and still be OSD conformant.Note: On January 9th, 2008 the OSI Board approved the \"Simplified BSD License\" variant used by FreeBSD and others, which omits the final \"no-endorsement\" clause and is thus roughly equivalent to the MIT License.Historical Note: The original license used on BSD Unix had four clauses. The advertising clause (the third of four clauses) required you to acknowledge use of U.C. Berkeley code in your advertising of any product using that code. It was officially rescinded by the Director of the Office of Technology Licensing of the University of California on July 22nd, 1999. He states that clause 3 is \"hereby deleted in its entirety.\" The four clause license has not been approved by OSI. The license below does not contain the advertising clause.This prelude is not part of the license.= Regents of the University of California= University of California, Berkeley= 1998In the original BSD license, both occurrences of the phrase \"COPYRIGHT HOLDERS AND CONTRIBUTORS\" in the disclaimer read \"REGENTS AND CONTRIBUTORS\".Here is the license template:Copyright (c) , All rights reserved.Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:· Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.· Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.· Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.` Apache License 아파치 재단 (ASF: Apache Software Foundation)의 모든 소프트웨어에 적용되는 License로 소스 공개 의무가 없다. 단, "Apache" 라는 이름에 대한 상표권을 침해하면 안되고 특허권에 대한 내용이 포함되어 있다. Apache 2.0에 특허 관련 조항이 들어가서 GPL 2.0과의 결합이 어려웠는데 GPL 3.0에서는 이 문제가 해결되어 Apache 코드와 GPL 3.0 코드의 결합이 가능해졌다. 아래 내용이 포함되어야 한다. 12345678910111213Copyright [yyyy] [name of copyright owner]Licensed under the Apache License, Version 2.0 (the \"License\");you may not use this file except in compliance with the License.You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an \"AS IS\" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License. GPL 2.0 (General Public Lecense) GPL 2.0이 적용된 코드를 사용할 경우 "본 제품(SW)은 GPL 라이센스 하에 배포되는 SW인 ㅇㅇㅇ를 포함합니다." 등의 문구를 매뉴얼 혹은 그에 준하는 매체에 포함시키고 GPL 전문을 첨부해야 한다. 코드를 수정하거나 새로운 소프트웨어를 Link(Static, Dynamic 모두) 하는 경우 소스코드 제공 의무 가짐 Object code, Excutable form으로 배포하는 경우 소스코드 자체를 함께 배포하거나 제공받을 수 있는 방법을 기술 자신의 특허를 구현한 코드인 경우 특허 사용료를 받을 수 없음 타인의 특허를 구현한 코드인 경우 특허권자가 사용자에게 사용료를 받지 않겠다고 한 경우에만 배포 가능 예외케이스 GNU Classpath Project와 Java Platform의 경우 소스코드를 공개하지 않고 배포 가능 GPL 2.0은 아래 문구들이 포함되어야 한다. 12One line to give the program's name and a brief idea of what it does.Copyright (C) <year> <name of author> 추가로 아래 문구들이 더 필요하다. 파일 하나짜리 프로그램일 경우 123456789101112This program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program. If not, see <http://www.gnu.org/licenses/>. 파일이 프로그램의 일부일 때 1234567891011121314This file is part of XXXXX.Foobar is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.Foobar is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with Foobar. If not, see <http://www.gnu.org/licenses/>. GPL 3.0 GPL 2.0에 비해 변경점은 다음과 같다. GPL 3.0 소스코드를 특정 제품에 포함시키거나 함께 배포하는 경우, 해당 소스에 설치 정보를 함께 제공해야 한다. 다만, ROM에 설치되는 경우(그 누구도 수정코드를 제품에 설치할 수 없는 경우)에는 설치 정보를 제공하지 않아도 된다. DRM (Digital Rights Management)과 관련된 각국 법률에 의해 보호되는 이익을 포기해야 한다. 특허와 관련하여 원 소스코드를 개선하여 배포한 기여자의 경우 자신이 기여한 부분에 대해서는 비차별적이고 특허 사용료가 없다는 내용의 License를 제공해야 한다. 특허와 관련하여 Licensee 등으로부터 특허 소송이 제기되는 경우 소송을 제기한 날에 특허소송을 제기한 Licensee의 Opensource software license는 종료된다. Apache 2.0 및 Affero GPL과 양립이 가능하다. LGPL 2.1 (Lesser General Public License) 일부 Library에 대하여 GPL보다 소스코드의 공개 정도를 완화된 형태로 사용할 수 있도록 만든 License이다. 상용 소프트웨어 개발자들이 코드 공개때문에 오픈 소스를 사용하지 않을까봐 조금 완화하였다. 소프트웨어 배포 시 저작권 표시, 보증책임 없음 표시, LGPL에 의해 배포된다는 사실을 명시 LGPL Library의 일부를 수정하는 경우 수정한 코드 공개 LGPL Library에 Link(Static, Dynamic)할 경우 해당 소스 공개 불필요. 단, 사용자가 Library 수정 후 동일한 실행 파일을 생성할 수 있도록 Static Linking시에는 Object code를 제공해야 함. GPL과 동일한 저작권 표시를 해야한다. 단, GPL에서의 '프로그램' 을 '라이브러리'로 한다. 라이브러리를 수정한 것이므로... MIT (Massachusetts Institute of Technology) 미국 MIT에서 학생들을 돕기 위해 개발한 License로 MIT를 개조한 제품은 반드시 오픈 소스로 배포해야 한다는 규정이 없다. 아래와 같은 저작권 문구를 포함해야 한다. 12Copyright (c) <year> <copyright holders>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 이 외에도 MPL(Mozilla Public License), CDDL(Common Development and Distribution License), CPL(Common Public License), EPL(Eclipse Public License) 등 무진장 많아서 더는 못쓰겠다. 그때그때 찾아보자. 아래 표는 한국저작권협회에서 제공하는 자료이다. {:class=”img-responsive”} 정리 BSD (Berkeley Software Distribution) 저작권 표시 필요함 코드 공개 의무 없음 저자 및 기여자들의 이름을 홍보용으로 사용할 수 없음 상용으로 사용 가능 Apache 저작권 표시 필요함 코드 공개 의무 없음 상용으로 사용 가능 GPL (General Public License) 저작권 표시 필요함 GPL 전문 포함해야 함 소스 코드 공개 필요함 LGPL (Lesser General Public License) 저작권 표시 필요함 LGPL Library를 수정한 경우 코드 공개해야 함 LGPL Library를 사용만 한 경우 코드 공개 의무 없음 MIT 소스 공개 의무 없음 Licensing은 Licensor가 Licensee에게 대가를 받고 그 재산권을 사용할 수 있도록 상업적 권리를 부여하는 것이다. 결론 : 상용에서는 GPL을 피하고 MIT, BSD, Apache를 사용하자. 출처아래의 글들을 교재삼아 작성하였습니다. 오픈소스 소프트웨어 라이선스 가이드 2.0 - 문화체육관광부, 한국저작권위원회 Principles of Software Contracts - The LINUX Foundation 한국저작권협회 APACHE Software Foundation GPL 2.0 GPL 3.0","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"etc","slug":"Dev/etc","permalink":"http://lazyrodi.github.io/categories/Dev/etc/"}],"tags":[{"name":"opensource","slug":"opensource","permalink":"http://lazyrodi.github.io/tags/opensource/"},{"name":"license","slug":"license","permalink":"http://lazyrodi.github.io/tags/license/"}]},{"title":"String-StringBuffer-StringBuilder","slug":"2016-06-27-java-string-stringbuffer-stringbuilder","date":"2016-06-27T11:42:50.000Z","updated":"2016-08-08T14:20:19.457Z","comments":true,"path":"2016/06/27/2016-06-27-java-string-stringbuffer-stringbuilder/","link":"","permalink":"http://lazyrodi.github.io/2016/06/27/2016-06-27-java-string-stringbuffer-stringbuilder/","excerpt":"","text":"살펴보고 돌아서서 5분 후면 까먹는 String, StringBuffer, StringBuilder에 대한 정리. StringString은 Immutable Class (불변 클래스)이다. 불변 클래스라 함은 아래 코드와 같이 str1에 str2를 더했을 때 원래의 객체가 변하는 것이 아니라 새로운 객체가 생성되는 것을 의미한다. 코드 1.12345678String str1 = \"Lazy\";String str2 = \"Rodi\"; System.out.println(\" str1 => \" + str1 + \": \" + str1.hashCode());System.out.println(\" str2 => \" + str2 + \": \" + str2.hashCode());str1 += str2;System.out.println(\"str1 += str2; => \" + str1 + \": \" + str1.hashCode()); 결과값 1. 주소가 바뀐 것을 확인할 수 있다.123 str1 => Lazy: 2361236 str2 => Rodriguez: 2552738str1 += str2; => LazyRodriguez: -1189801674 위의 예제에서 처음 str1이 차지하고 있던 공간이 낭비된다는 것을 알 수 있다. 이 낭비를 막기 위해 StringBuffer를 살펴보자. StringBufferStringBuffer는 Immutable Class가 아니며, append() 메소드를 제공하여 문자열 연산을 할 수 있게 도와준다. 코드 2.123456789StringBuffer strBuf = new StringBuffer();String str1 = \"Lazy\";String str2 = \"Rodriguez\";System.out.println(\"strBuf => \" + strBuf + \" : \" + strBuf.hashCode());strBuf.append(str1);System.out.println(\"strBuf => \" + strBuf + \": \" + strBuf.hashCode());strBuf.append(str2);System.out.println(\"strBuf => \" + strBuf + \": \" + strBuf.hashCode()); 결과값 2. 새로 생성되는 객체가 없다.123strBuf => : 366712642strBuf => Lazy: 366712642strBuf => LazyRodriguez: 366712642 별 차이 없는 것으로 볼 수도 있고, StringBuffer를 생성할 때의 자원이 걱정될 수도 있겠지만, 문자의 + 연산이 빈번하게 이루어진다면 메모리의 낭비가 커질 수 밖에 없고 이 때 StringBuffer를 String보다 효율적으로 사용할 수 있게 된다. String과 StringBuffer 모두 char[] 이다. String은 + 연산 시 새로운 배열을 만들어내고 StringBuffer은 미리 배열을 여유있게 잡아둔다. 이런 이유때문에 StringBuffer의 배열이 꽉 차서 메모리를 더 잡아주는 지점에서 성능 저하가 살짝 나타난다. 보통 StringBuffer가 String 보다 성능이 뛰어나다. String의 경우에도 Compiler가 + 연산을 StringBuffer로 자동 변환해주긴 하지만 모든 + 연산을 커버하지는 않는다고 한다. StringBuilder위에서 살펴본 StringBuffer는 멀티 스레딩 환경에서 Thread-safe를 위해 동기화(Synchronized)가 이루어진다. StringBuilder는 StringBuffer에서 동기화 기능을 제거한 것이다. 고로, 멀티 스레딩 환경이 아니라면 StrigBuilder를 쓰는 것이 낫다. 성능시험간단한 코드로 속도를 비교해 보도록 하자. loop 단위가 작을 때에는 currentTimeMillis() 대신 nanoTime()을 사용하면 된다. (사실 아래 코드가 측정에 적당한 코드인지 불안하긴 하다…)123456789101112131415161718192021222324252627282930313233343536String str1 = \"Lazy\";String str2 = \"Rodriguez\";StringBuffer strBuffer = new StringBuffer();StringBuilder strBuilder = new StringBuilder();int i = 0;long max = 50000;long s, e;s = System.currentTimeMillis();System.out.println(\"'StringBuffer' start = \" + s);for (i = 0; i < max; i++) { strBuffer.append(str2);}e = System.currentTimeMillis();System.out.println(\"'StringBuffer' start = \" + e);System.out.println(\"'StringBuffer' spend time = \" + (e - s));s = System.currentTimeMillis();System.out.println(\"'StringBuilder' start = \" + s);for (i = 0; i < max; i++) { strBuilder.append(str2);}e = System.currentTimeMillis();System.out.println(\"'StringBuilder' start = \" + e);System.out.println(\"'StringBuilder' spend time = \" + (e - s));s = System.currentTimeMillis();System.out.println(\"'+' start = \" + s);for (i = 0; i < max; i++) { str1 += str2;}e = System.currentTimeMillis();System.out.println(\"'+' start = \" + e);System.out.println(\"'+' spend time = \" + (e - s)); 아래 결과는 3회 측정 후 평균 낸 값이다. 기초적인 환경에서는 StringBuilder 성능이 제일 좋다는 것을 알 수 있다. 종류 \\ 수행100회 (ns)1000회 (ns)10000회 (ms)20000회 (ms)30000회 (ms)50000회 (ms)String274526907782953919104208.66710992.67StringBuffer305514.7583808.323.66666766.333333StringBuilder117393.7202339.72.3333332.3333333.3333337 내 맘대로 정리 String 단순한 코드, 문자열의 불변성이 유지되는 코드에서 사용 StringBuffer Thread-safe 환경에서 사용 StringBuilder Single thread 환경에서 문자열 결합에서는 가장 좋은 성능을 보임 출처아래의 글들을 교재삼아 작성하였습니다. http://cafe.naver.com/javachobostudy/2423 http://cafe.naver.com/javachobostudy/36860 http://cafe.naver.com/hitommy/543 java-api String StringBuilder StringBuffer","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"java","slug":"Dev/java","permalink":"http://lazyrodi.github.io/categories/Dev/java/"}],"tags":[{"name":"java","slug":"java","permalink":"http://lazyrodi.github.io/tags/java/"},{"name":"string","slug":"string","permalink":"http://lazyrodi.github.io/tags/string/"},{"name":"stringbuffer","slug":"stringbuffer","permalink":"http://lazyrodi.github.io/tags/stringbuffer/"},{"name":"stringbuilder","slug":"stringbuilder","permalink":"http://lazyrodi.github.io/tags/stringbuilder/"}]},{"title":"작심일일 프로젝트","slug":"2016-06-27-life-jaksimilill","date":"2016-06-27T10:47:50.000Z","updated":"2016-07-10T14:59:00.000Z","comments":true,"path":"2016/06/27/2016-06-27-life-jaksimilill/","link":"","permalink":"http://lazyrodi.github.io/2016/06/27/2016-06-27-life-jaksimilill/","excerpt":"","text":"作心一日뭐든 꾸준히 못 하는 성격인데 인생의 위기가 왔으니 꾸준히 공부를 해보려 한다. 목표는 하루에 글타래 하나 정리하기. (2016-06-27) String-StringBuffer-StringBuilder (2016-06-28) Opensource License (2016-06-29) FileIO (2016-06-30) Fundamental of Database (2016-07-01) Memory Management (2016-07-02) Regular Expression (2016-07-03) Queue (2016-07-04) OSI 7 layer and TCP/IP model (2016-07-05~2016-07-10) Android Permission","categories":[{"name":"life","slug":"life","permalink":"http://lazyrodi.github.io/categories/life/"}],"tags":[]},{"title":"동영상 모음","slug":"2016-06-22-docthread-video-collecting","date":"2016-06-22T11:01:00.000Z","updated":"2017-02-06T11:40:06.204Z","comments":true,"path":"2016/06/22/2016-06-22-docthread-video-collecting/","link":"","permalink":"http://lazyrodi.github.io/2016/06/22/2016-06-22-docthread-video-collecting/","excerpt":"","text":"개발자가 보면 암걸리는 동영상 산으로 가는 광고 오늘 미래를 만나다 - 허태균 교수 : 대한민국에서 행복 찾기 자기 적성을 몰라 헤매는 당신이 반드시 들어야 할 대답 : 송길영 다음소프트 부사장 How a CPU Works","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/tags/docs/"},{"name":"video","slug":"video","permalink":"http://lazyrodi.github.io/tags/video/"}]},{"title":"[조직문화] 회의","slug":"2016-06-22-hr-conference","date":"2016-06-22T11:00:00.000Z","updated":"2016-08-08T14:20:10.016Z","comments":true,"path":"2016/06/22/2016-06-22-hr-conference/","link":"","permalink":"http://lazyrodi.github.io/2016/06/22/2016-06-22-hr-conference/","excerpt":"","text":"실리콘밸리 임원들이 회의 하는 법 - www.andrewahn.co신속한 합의를 위한 5일의 법칙 - www.andrewahn.co","categories":[{"name":"hr","slug":"hr","permalink":"http://lazyrodi.github.io/categories/hr/"}],"tags":[{"name":"hr","slug":"hr","permalink":"http://lazyrodi.github.io/tags/hr/"},{"name":"conference","slug":"conference","permalink":"http://lazyrodi.github.io/tags/conference/"}]},{"title":"JS Library","slug":"2016-06-22-docthread-jslibrary","date":"2016-06-22T10:47:50.000Z","updated":"2016-08-08T14:19:55.560Z","comments":true,"path":"2016/06/22/2016-06-22-docthread-jslibrary/","link":"","permalink":"http://lazyrodi.github.io/2016/06/22/2016-06-22-docthread-jslibrary/","excerpt":"","text":"Visualization d3.js - 데이터 시각화 typed.js - 타이핑 효과… 뭐라고 설명을 써야할지… Wysiwyg Editor 10 best html wysisyg plugin - Froala, ContentTools, Raptor Editor, Aloha, TinyMCE, bootstrap3-wysiwyg, summernote, CKEditor, Trumbowyg, Redactor 브라우저 기반의 위지윅 에디터 총집합 CSS bootstrap bootstrap-material-design ETC 추천 JavaScript Library 정리 - Brunch의 @klaus님 글","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/tags/docs/"},{"name":"js","slug":"js","permalink":"http://lazyrodi.github.io/tags/js/"},{"name":"visualization","slug":"visualization","permalink":"http://lazyrodi.github.io/tags/visualization/"},{"name":"wysiwyg editor","slug":"wysiwyg-editor","permalink":"http://lazyrodi.github.io/tags/wysiwyg-editor/"},{"name":"css","slug":"css","permalink":"http://lazyrodi.github.io/tags/css/"}]},{"title":"UX","slug":"2016-06-22-docthread-ux","date":"2016-06-22T10:47:50.000Z","updated":"2016-08-08T14:20:01.449Z","comments":true,"path":"2016/06/22/2016-06-22-docthread-ux/","link":"","permalink":"http://lazyrodi.github.io/2016/06/22/2016-06-22-docthread-ux/","excerpt":"","text":"같은 듯 다른 사용자경험(UX)과 고객경험(CX)","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/tags/docs/"},{"name":"ux","slug":"ux","permalink":"http://lazyrodi.github.io/tags/ux/"}]},{"title":"[JavaScript] Document Word Parser","slug":"2016-06-07-works-document-word-parser","date":"2016-06-06T15:20:00.000Z","updated":"2016-08-08T14:19:39.920Z","comments":true,"path":"2016/06/07/2016-06-07-works-document-word-parser/","link":"","permalink":"http://lazyrodi.github.io/2016/06/07/2016-06-07-works-document-word-parser/","excerpt":"","text":"Document Word Parser Use : http://lazyrodi.github.io/misc/DocumentWordParser.html Repository : https://github.com/lazyrodi/misc/tree/master/DocumentWordParser UsageParse the document word by word. Input document in textarea left side. Run You can see word-frequency on the right side. Ignore uppercase/lowercase and some characters-, . ‘ “ ( ) { } [ ] ? !-.","categories":[{"name":"works","slug":"works","permalink":"http://lazyrodi.github.io/categories/works/"}],"tags":[{"name":"parser","slug":"parser","permalink":"http://lazyrodi.github.io/tags/parser/"},{"name":"javascript","slug":"javascript","permalink":"http://lazyrodi.github.io/tags/javascript/"}]},{"title":"[조직문화] 명언, 격언","slug":"2016-05-01-hr-famous-quotes","date":"2016-05-16T11:00:00.000Z","updated":"2016-08-10T15:16:13.633Z","comments":true,"path":"2016/05/16/2016-05-01-hr-famous-quotes/","link":"","permalink":"http://lazyrodi.github.io/2016/05/16/2016-05-01-hr-famous-quotes/","excerpt":"","text":"직원들은 다른 회사에서 더 나은 직책이나 연봉을 제안받았을 때가 아니라 지금 회사에서 존중받지 못했을 때 이직한다. Tom MendozaNetApp Vice president 단순함, 그것은 천재에게 주어진 재능이다.어떤 지적인 바보도 사물을 더 크고, 더 복잡하고, 더 격렬하게 만들 수 있다.하지만 그 반대편으로 나아가려면 약간의 천재성과 많은 용기가 필요하다.만약 당신이 어떤 것을 단순하게 설명할 수 없다면, 당신은 그것을 충분히 이해하지 못한 것이다. Albert Einstein (알버트 아인슈타인) 더 더할게 없을 때가 아니라, 더 뺄게 없을 때 완벽한 디자인에 도달할 수 있다. Antoine Marie Roger De Saint Expery (생텍쥐페리) 은하영웅전설 명언 모음","categories":[{"name":"hr","slug":"hr","permalink":"http://lazyrodi.github.io/categories/hr/"}],"tags":[{"name":"hr","slug":"hr","permalink":"http://lazyrodi.github.io/tags/hr/"},{"name":"famous quotes","slug":"famous-quotes","permalink":"http://lazyrodi.github.io/tags/famous-quotes/"}]},{"title":"SQL 기초","slug":"2016-04-13-db-sql-getting-started","date":"2016-05-13T04:00:00.000Z","updated":"2016-08-08T14:18:42.788Z","comments":true,"path":"2016/05/13/2016-04-13-db-sql-getting-started/","link":"","permalink":"http://lazyrodi.github.io/2016/05/13/2016-04-13-db-sql-getting-started/","excerpt":"","text":"SQL 명령어 SELECT : database로부터 data를 추출한다. UPDATE : database내의 data를 갱신한다. DELETE : database로부터 data를 삭제한다. INSERT INTO : database로 새로운 data를 삽입한다. CREATE DATABASE : 새로운 database를 생성한다. ALTER DATABASE : database를 수정한다. CREATE TABLE : 새 table을 생성한다. ALTER TABLE : table을 수정한다. DROP TABLE : table을 삭제한다. CREATE INDEX : index (search key)를 생성한다. DROP INDEX : index를 삭제한다. SELECT table로부터 선택한 column들을 전부 읽어서 보여준다. * 은 모든 column. 12SELECT column_name, column_name FROM table_name;SELECT * FROM table_name; 중복되는 data는 제외하고 보고싶을 때 DISTINCT 구문을 사용한다. (예를 들어 city 정보에 seoul을 갖는 record가 두 개 있을 때 하나만 보여준다.) 1SELECT DISTINCT column_name, column_name FROM table_name; WHERE 절은 특별한 기준을 만족시키는 record만 추출한다. Text value는 작은 따옴표, Numeric value에는 따옴표 없이 사용한다. OperatorDescription=Equal<>Not Equal>Greater than<Less than>=Greater than or equal<=Less than or equalBETWEENBetween an inclusive rangeLIKESearch for a patternINTo specify multiple possible values for a column 12SELECT column_name, column_name FROM table_name WHERE column_name operator value;SELECT * FROM Customers WHERE Country='Mexico'; AND와 OR의 경우 다음과 같이 사용 가능하다. 12SELECT * FROM Customers WHERE Country='Germany' AND City='Berlin';SELECT * FROM Customers WHERE Country='Germany' AND (City='Berlin' or City='Munchen'); ORDER BY keyword를 이용하여 오름차순으로 정렬해서 볼 수 있다. 내림차순 정렬을 위해서는 DESC keyword를 사용하면 된다. 1234SELECT column_name, column_name FROM table_name ORDER BY column_name ASC|DESC, column_name ASC|DESC;SELECT * FROM Customers ORDER BY Country;SELECT * FROM Customers ORDER BY Country DESC;SELECT * FROM Customers ORDER BY Country ASC, CustomerName DESC; INSERT INTO 구문은 table에 새로운 record를 삽입하는 것으로 두 가지 방식으로 사용할 수 있다. 123INSERT INTO table_name VALUES (value1,value2,value3,...);INSERT INTO table_name (column1,column2,column3,...) VALUES (value1,value2,value3,...);INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country) VALUES ('Cardinal','Tom B. Erichsen','Skagen 21','Stavanger','4006','Norway'); UPDATE 구문은 table 내에 존재하는 record를 갱신하는 기능이다. WHERE구문을 사용하지 않는다면 모든 record들이 갱신되므로 반드시 올바르게 사용하여야 한다. 12UPDATE table_name SET column1=value1, column2=value2, ... WHERE some_column=some_value;UPDATE Customers SET ContactName='Alfred Schmidt', City='Hamburg' WHERE CustomerName='Alfreds Futterkiste'; DELETE 구문은 table 내의 row를 삭제한다. WHERE를 사용하지 않으면 모두 삭제되므로 반드시 주의하자. 12DELETE FROM table_name WHERE some_column=some_value;DELETE FROM Customers WHERE CustomerName='Alfreds Futterkiste' AND ContactName='Maria Anders'; 모든 Data를 삭제하기 위해서는 다음 둘 중 하나를 사용하면 된다. 복구할 수 없으므로 모쪼록 주의하자. 12DELETE FROM table_name;DELETE * FROM table_name;","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"database","slug":"Dev/database","permalink":"http://lazyrodi.github.io/categories/Dev/database/"}],"tags":[{"name":"database","slug":"database","permalink":"http://lazyrodi.github.io/tags/database/"},{"name":"sql","slug":"sql","permalink":"http://lazyrodi.github.io/tags/sql/"}]},{"title":"Web","slug":"2016-04-02-docthread-web","date":"2016-04-02T10:47:50.000Z","updated":"2016-08-08T14:18:27.597Z","comments":true,"path":"2016/04/02/2016-04-02-docthread-web/","link":"","permalink":"http://lazyrodi.github.io/2016/04/02/2016-04-02-docthread-web/","excerpt":"","text":"HTTP REST API 디자인 - 조대협님의 강의자료","categories":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/categories/docs/"}],"tags":[{"name":"docs","slug":"docs","permalink":"http://lazyrodi.github.io/tags/docs/"},{"name":"web","slug":"web","permalink":"http://lazyrodi.github.io/tags/web/"}]},{"title":"[RoR] Rails-flavored Ruby","slug":"2016-04-02-ruby-on-rails-rails-flavored-ruby","date":"2016-04-01T15:04:00.000Z","updated":"2016-08-08T14:18:34.046Z","comments":true,"path":"2016/04/02/2016-04-02-ruby-on-rails-rails-flavored-ruby/","link":"","permalink":"http://lazyrodi.github.io/2016/04/02/2016-04-02-ruby-on-rails-rails-flavored-ruby/","excerpt":"","text":"Rails 에서는 Ruby 중 일부를 사용할 수 있다. 아래는 application.css 파일을 link하는 것이다. 일단 이건 뒤에서 다시 보자.1<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> String and Method 주석은 # 으로 처리한다. String은 "" 으로 처리하며 다음과 같은 연산들이 가능하다. '' 도 가능하지만 single quotation은 interpolation 기능을 지원하지 않는다. interpolation은 #{ } 를 통해 변수를 따옴표 안에서 사용할 수 있는 기능이다.12345678>> \"foo\" + \"bar\">> first_name = \"Michael\">> \"#{first_name} Hartl\" #interpolation>> first_name + \"Hartl\">> \"foobar\".length #get length>> \"foobar\".empty? #true/false>> \"foobar\".nil? #true/false print는 puts "foo" 의 방식으로 한다. if문은 다음과 같은 방식으로 사용한다. &&, ||, ! 으로 논리식을 표현한다.12345678if s.nil? \"abc\"elseif s.empty? \"def\"else \"gkl\"end 아래 방식으로 출력문에 조건문을 더할 수도 있다.123x = \"foo\"puts x if !x.empty? String으로 변환은 abc.to_s 으로 가능하다. if문 대신 unless문을 사용할 수 있는데, if는 조건이 true일 때 실행되고 unless는 조건이 false일 때 실행된다.123x = \"foo\"puts x unless x.empty? Method definitions method 선언의 간단한 예이다. method 호출 시 parameter가 하나 있으면 str에 들어가고 없으면 기본 값으로 ''를 사용한다. 1234567def string_message(str = '') if str.empty? \"empty string\" else \"not empty string\"end Ruby 함수는 implicit return을 지원한다. 한 statement의 마지막에 있는 것을 return한다. return을 기입하는 explicit return 역시 지원한다. 12345def string_message(str = '') return \"abc\" if str.empty? return \"nonempty\"end Array는 다음과 같이 편하게 생성할 수도 있다. 12345678910111213141516171819>> \"foo bar baz\".split # [\"foo\", \"bar\", \"baz\"]>> \"fooxbarxbazx\".split('x') # [\"foo\", \"bar\", \"baz\"]>> a = [42, 8, 17]>> a[0] # 42>> a[-1] # 17>> a.first>> a.second>> a.last>> a.length>> a.empty?>> a.include?(3) # true>> a.sort # [8, 17, 42]>> a.reverse # [17, 8, 42]>> a.shuffle # [17, 42, 8] 이건 랜덤으루다가.>> a # [42, 8, 17] 변하지 않음>> a.sort! # [8, 17, 42]>> a # [8, 17, 42] 변했음 위에서 ! 를 Bang method라 한다. << 연산자를 통해 배열에 새로운 아이템을 넣을 수 있다. 1234>> a.push(5) # [42, 8, 17, 5]>> a << 7 # [42, 8, 17, 5, 7]>> a << \"foo\" << \"bar\" # [42, 8, 17, 5, 7, \"foo\", \"bar\"] .. 을 통해 range를 나타낼 수 있다. to_a 는 array를 생성해 준다. 12345678910>> 0..9>> 0..9.to_a # error. 이건 9.to_a 와 같다.>> (0..9).to_a # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>> a = %w[foo bar baz quux] # %w 는 string array를 만들어 준다.>> a[0..2] # [\"foo\", \"bar\", \"baz\"]>> a[2..(a.length-1)] # [2, 3, 4, 5, 6, 7, 8, 9]>> a[2..-1] # [2, 3, 4, 5, 6, 7, 8, 9]>> ('a'..'e').to_a # [\"a\", \"b\", \"c\", \"d\", \"e\"] Ruby의 강력한 기능 중 하나인 Block은 다음과 같다. 12>> (1..5).each { |i| puts 2 * i } #2 4 6 8 10 이 출력된다. |i| 는 Block variable 이라고 부른다. { } 를 사용하지 않고도 같은 효과를 낼 수 있다. 1234(1..5).each do |i| puts 2 * iend 보통 Rails에서는 one-line일 때에는 block을 사용하고 multi-line일 때는 do..end 구문을 사용한다. map 메소드를 통해 다음과 같은 변환도 가능하다. 12345>> 3.times { puts \"abc\" } # abc를 세 번 출력>> (1..5).map { |i| i**2 } # [1, 4, 9, 16, 25]>> %w[a b c].map { |char| char.upcase } # [A, B, C] 로 바뀜>> join 메소드는 배열을 하나의 String으로 변환시켜준다. 아래 코드는 a ~ z 중 임의의 8가지 문자로 배열을 생성하고 이를 합쳐서 하나의 String으로 만들어준다. 12>> ('a'..'z').to_a.shuffle[0..7].join 다음과 같이 hash를 사용할 수 있다. 123456789>> user = {} # empty hash>> user[\"first\"] = \"Michael\" # Key : first , Value : Michael>> user[\"last\"] = \"Hartl\" # Key : last , Value : Hartl>> user[\"first\"] # return \"Michael\">> user # return {\"last\"=>\"Hartl\", \"first\"=>\"Michael\"}# 처음부터 이렇게 선언도 가능함. => 를 \"hashrocket\" 이라 부른다.>> useruser = {\"first\"=>\"Michael\", \"last\"=>\"Hartl\"} Ruby에서의 symbol은 :name 처럼 콜론으로 시작한다. 숫자로 시작할 수 없고 dash ‘-‘ 를 사용할 수 없다. hash에서 다음과 같이 사용할 수 있다. Ruby에서는 hash끼리의 비교도 가능하다. hash A, B 가 있을 때 ‘A == B’ 식으로… 1234>> user = { :name=>\"Michael Hartl\", :email=>\"[email protected]\" }>> user[:name] # return \"Michael Hartl\">> user[:passwd] # return nil 다음과 같은 방식으로도 hash를 선언할 수 있으나 :name 과 다르게 name은 독자적으로 사용할 수 없다. 말이 좀 어려운데 아래처럼 선언해도 사용할 때는 user[:name] 형식이 된다는 말이다. 12>> user = { name: \"abc\", email: \"asdf\" } Nested hash 형식도 가능하다. 12345>> params = {}>> params[:user] = {name: \"asdf\", age: \"55\"}>> params # return {:user=>{:name=>\"asdf\", :age=>\"55\"}}>> params[:user][:age] # \"55\" inspect 메소드는 변수를 문자 그대로(literally) 출력한다. puts + inspect 를 줄여서 p 로 쓸 수도 있다. 12345678910>> puts :name, :name.inspect # name # :name>> puts \"it worked\", \"it worked\".inspect # it worked # \"it worked\">> puts :name.inspect # :name>> p :name # :name 위에서 나왔던 아래 소스를 살펴보자.ruby는 괄호(parentheses)( ) 및 중괄호(braces){ }를 생략할 수 있다.즉, 아래 소스는 stylesheet_link_tag 라는 이름의 함수이다. stylesheet_link_tag 함수는 두 개의 인자를 갖는다. string (stylesheet의 path) hash media type Rails 4.0에 추가된 turbolinks 기능의 사용 여부123456<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %># 아래와 같이 표현될 수 있다.<%= stylesheet_link_tag ('application', { media: 'all', 'data-turbolinks-track' => true } ) %># 'data-turbolinks-track' 이 => (hashrocket) 을 사용한 이유는 변수명에 dash '-' 가 들어갈 수 없기 때문이다.# Ruby는 new line이나 space에 제한을 받지 않으므로 맘대로 끊어쓰면 된다. 보통 80 글자 기준으로 엔터 치는 듯 하다. <%= %> 로 둘러싸여있는 구문은 ERb에 의해 HTML에 들어가게 된다. 브라우저의 소스 보기를 통해 보면 다음과 같이 보이게 된다. :true12<link data-turbolinks-track="true" href="/assets/application.css" media="all" rel="stylesheet"> Ruby classes Ruby는 객체 지향 언어이기 때문에 method의 집합인 class로 이루어져 있고, 초기화를 통해 object를 생성한다. 이로 인한 몇 가지 특성을 보자. Constructor 123456>> s = \"foobar\">> s = String.new(\"foobar\")>> a = Array.new([1,3,2])>> h = Hash.new>> h = Hash.new(0) # 인자들을 0으로 초기화 Class inheritance 12345678>> s = String.new(\"foobar\")>> s.class # String>> s.class.superclass # Object>> s.class.superclass.superclass # BasicObject>> s.class.superclass.superclass.superclass # nil>> class ABC < String # 상속 build-in class 수정하기 JavaScript의 navigator와 비슷한 개념인가 싶은데… override는 아니고 직접 그 class를 다시 선언해서 native class의 method를 수정하여 사용할 수 있다.1234567>> class String>> # Returns true if the string is its own reverse.>> def palindrome?>> self == self.reverse>> end>> end blank와 empty의 차이. (irb에서는 지원하지 않는다. ERb에서 되는지는 안해봤지만 되니까 써놓지 않았을까…) 12345>> \"\".blank? # true>> \" \".empty? # false>> \" \".blank? # true>> nil.blank? # true Controller class Controller class는 다음과 같은 상속 구조를 갖는다. StaticPagesController < ApplicationController < ActionController::Base < ActionController::Metal < AbstractController::Base < Object User model에 대해 살펴보자. 12345678910111213class User attr_accessor :name, :email def initialize(attributes = {}) @name = attributes[:name] @email = attributes[:email] end def formatted_email \"#{@name} <#{@email}>\" endend attribute accessors는 getter와 setter 메소드를 만들겠다는 의미이다. @name, @email instance 변수에 대해 retrieve(get), assign(set)을 도와준다.instance 변수는 언제나 @로 시작하며, 정의하지 않을 시 nil 이 된다 위의 initialize 메소드는 User.new 를 수행하면 호출된다. initialize는 attributs: 라는 하나의 인자만 갖는다. 위 코드를 통해 인자는 Hash값이라는 것을 알 수 있다. 책의 예제에서는 console에서 위 파일을 가져와서 수행했는데 파일을 가져오기 위해서는 require 메소드를 사용한다. 파일명도 생략한다. require './example_user'","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Ruby on rails","slug":"Dev/Ruby-on-rails","permalink":"http://lazyrodi.github.io/categories/Dev/Ruby-on-rails/"}],"tags":[{"name":"ruby on rails","slug":"ruby-on-rails","permalink":"http://lazyrodi.github.io/tags/ruby-on-rails/"},{"name":"ruby","slug":"ruby","permalink":"http://lazyrodi.github.io/tags/ruby/"}]},{"title":"Android Default Skills","slug":"2016-04-01-android-default-skills","date":"2016-04-01T11:00:00.000Z","updated":"2016-08-19T15:32:25.728Z","comments":true,"path":"2016/04/01/2016-04-01-android-default-skills/","link":"","permalink":"http://lazyrodi.github.io/2016/04/01/2016-04-01-android-default-skills/","excerpt":"","text":"SkillCall other activity1startActivity(new Intent(MainActivity.this, InputActivity.class)); OnClickListener123456\"\"BUTTON_ID\"\".setOnClickListener(new Button.OnClickListener() { @Override public voic onClick(View view) { }}); 1234567891011Button btn = (Button)findViewById(R.id.\"\"btn_id\"\");Button.OnClickListner onClickListener = new Button.OnClickListner() { @Override public void onClick(View view) { switch (view.getId()) { case R.id.\"\"btnid\"\": break; } }}btn.setOnClickListener(onClickListener); Toast12Toast t = new Toast(getApplication());t.makeText(getApplication(), \"Text\", Toast.LENGTH_LONG).show(); Icons https://design.google.com/icons/index.html Trouble Shooting actual argument android.widget.Toolbar cannot be converted to android.support.v7.widget.Toolbar by method invocation conversion import android.widget.Toolbar; 대신 import android.support.v7.widget.Toolbar; 로 한다. setSupportActionBar(); 의 사용때문에 나온 에러이며, 이건 뒤의 라이브러리에 들어있다. 참조 onclicklistener - 개발자를 위한 레시피","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"android","slug":"Dev/android","permalink":"http://lazyrodi.github.io/categories/Dev/android/"}],"tags":[{"name":"android","slug":"android","permalink":"http://lazyrodi.github.io/tags/android/"}]},{"title":"Linux Command","slug":"2016-03-26-linux-command","date":"2016-03-26T10:47:50.000Z","updated":"2016-08-14T06:14:29.112Z","comments":true,"path":"2016/03/26/2016-03-26-linux-command/","link":"","permalink":"http://lazyrodi.github.io/2016/03/26/2016-03-26-linux-command/","excerpt":"","text":"개인적으로 많이 사용하고 헤깔리는 것들에 대해 정리한 것이며, 자세한 것은 --help 를 통해 확인하자. adduser 사용자를 추가합니다. adduser외에도 useradd가 있지만 그냥 adduser를 쓰는게 좋다고 인터넷에서 배웠다. (…) 사용자 계정 정보는 /etc/passwd에 위치하고 usermod 명령어로 계정에 대한 정보를 수정할 수 있다. /etc/group 파일의 admin 구역의 뒤쪽에 ,(콤마)를 사용하여 사용자를 추가하면 sudo 권한을 부여할 수 있다.adduser gildong.hong apt-get ubuntu 계열에서 package(windows에서는 프로그램, 유틸리티, ETC…)를 설치/제거/업데이트 등을 위해 사용하는 유틸리티. apt는 advanced package tool을 의미.apt-get install 패키지명apt-get remove 패키지명 chown change ownerchown 계정:그룹 경로 df display filesystem. filesystem 사용 용량 등의 정보를 알려준다. 보통 df -h 를 통해서 확인한다.df -h Group managementaddgroup Group을 추가한다.addgroup [group명] usermod 그룹에 사용자를 추가한다.usermod -a -G [그룹명] [사용자명] groups 사용자가 어떤 group에 속해있는지 보여준다. /etc/group 파일에서도 볼 수 있다.groups [사용자명] groupmod 그룹명을 변경한다.groupmod -n [변경할 그룹명] [원래 그룹명] ifconfig windows의 ipconfig. ip 정보를 보여준다. ifconfig link 디렉토리 설정 시 경로는 절대경로로 넣자.ln -s 원본디렉토리 [link디렉토리] screen .screenrc 에 환경설정 가능","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"linux","slug":"Dev/linux","permalink":"http://lazyrodi.github.io/categories/Dev/linux/"}],"tags":[{"name":"linux","slug":"linux","permalink":"http://lazyrodi.github.io/tags/linux/"},{"name":"command","slug":"command","permalink":"http://lazyrodi.github.io/tags/command/"},{"name":"apt-get","slug":"apt-get","permalink":"http://lazyrodi.github.io/tags/apt-get/"},{"name":"chown","slug":"chown","permalink":"http://lazyrodi.github.io/tags/chown/"},{"name":"df","slug":"df","permalink":"http://lazyrodi.github.io/tags/df/"},{"name":"ifconfig","slug":"ifconfig","permalink":"http://lazyrodi.github.io/tags/ifconfig/"},{"name":"link","slug":"link","permalink":"http://lazyrodi.github.io/tags/link/"},{"name":"screen","slug":"screen","permalink":"http://lazyrodi.github.io/tags/screen/"}]},{"title":"[RoR] CRUD","slug":"2016-03-25-ruby-on-rails-crud","date":"2016-03-24T15:01:00.000Z","updated":"2016-08-08T14:17:55.997Z","comments":true,"path":"2016/03/25/2016-03-25-ruby-on-rails-crud/","link":"","permalink":"http://lazyrodi.github.io/2016/03/25/2016-03-25-ruby-on-rails-crud/","excerpt":"","text":"https://www.railstutorial.org/book/static_pages 를 보고 공부한 내용. CRUD CRUD 는 아래 네 가지를 뜻한다. 위키백과를 참조하자 - Create - Read - Update - Delete MVC Ruby on rails는 MVC(Model View Controller)로 설계되어 있다. 위키백과를 참조하자 scaffold rails는 scaffold 를 통해 간단하게 MVC 를 생성할 수 있게 해준다.다음 명령어를 통해 User 를 생성해본다. 1rails generate scaffold User name:string email:string Rake Rake는 Ruby + Make를 합친 명령어이다. 위에서 생성한 User model(DB)을 사용하기 위해 database를 migration 해야 한다. 명령어에 대한 자세한 내용은 Stackoverflow의 질문내용에서 잘 설명해주고 있다. database task list를 보기 위해서는 bundle exec rake -T db 명령을 입력하면 된다.실행하면 users에 대해 create_table 해주는 것을 볼 수 있다. 또한, 이 내역은 application의 /db/migrate 디렉토리에 저장된다. 1bundle exec rake db:migrate 생성한 User 확인하기 이제 다시 서버를 실행rails server -b .... -p 3000한 후 브라우저에서 주소 뒤에 /users/new를 입력해 보자. User 생성 page를 볼 수 있다. 엄청 간단하게 CRUD 기능을 지원하는 Web site를 생성한 것이다. 생성된 URL은 다음과 같고 아래와 같은 의미를 갖는다. URL Action Purpose /users index page to list all users /users/1 show page to show user with id 1 /users/new new page to make a new user /users/1/edit edit page to edit user with id 1 rails에서의 MVC 패턴 동작을 그림으로 나타내면 아래와 같다.(출처 : https://www.railstutorial.org/book/toy_app) 123456781. The browser issues a request for the /users URL.2. Rails routes /users to the index action in the Users controller.3. The index action asks the User model to retrieve all users (User.all).4. The User model pulls all the users from the database.5. The User model returns the list of users to the controller.6. The controller captures the users in the @users variable, which is passed to the index view.7. The view uses embedded Ruby to render the page as HTML.8. The controller passes the HTML back to the browser. gem - gem install bootstrap-material-design - 아래 gem들이 같이 설치된다. - autoprefixer-rails-6.3.6 - bootstrap-sass-3.3.6 - bootstrap-material-design-0.2.2","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Ruby on rails","slug":"Dev/Ruby-on-rails","permalink":"http://lazyrodi.github.io/categories/Dev/Ruby-on-rails/"}],"tags":[{"name":"ruby on rails","slug":"ruby-on-rails","permalink":"http://lazyrodi.github.io/tags/ruby-on-rails/"},{"name":"crud","slug":"crud","permalink":"http://lazyrodi.github.io/tags/crud/"}]},{"title":"[RoR] Settings","slug":"2016-03-20-ruby-on-rails-setting","date":"2016-03-19T15:01:00.000Z","updated":"2016-08-08T14:17:32.044Z","comments":true,"path":"2016/03/20/2016-03-20-ruby-on-rails-setting/","link":"","permalink":"http://lazyrodi.github.io/2016/03/20/2016-03-20-ruby-on-rails-setting/","excerpt":"","text":"회사에서 존경하는 선배님과 함께 Ruby on rails를 이용하여 스터디를 진행하기로 하였다. Virtualbox를 설치하고 Ubuntu 환경에서 진행하였다. https://www.railstutorial.org/book/static_pages 를 보고 공부한 내용. Ruby on rails 란? Rails는 Ruby 언어에서 동작하는 Web Application Framework이다. 단순한 코드로 CRUD를 쉽게 구현할 수 있다고 한다. 제대로 사용할 줄 알면 빠른 시간 내에 웹 서비스를 구축할 수 있다. Ruby on rails 세팅 rbenv 설치rbenv는 이름에서도 유추 가능하듯 Ruby Environment의 관리 편의를 제공한다. Ruby on rails application 별로 다른 ruby version을 유지할 수 있게 해준다. rbenv Command Reference에서 자세한 정보를 확인할 수 있다. 설명서대로 설치를 진행해본다. ~/.rbenv 에 땡겨온다.1$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv $PATH에 ~/.rbenv/bin 을 추가한다. 그리고 적용을 위해 source .bashrc를 수행한다.1$ echo 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' >> ~/.bashrc 다음 명령을 통해 shell에 관계 없이 rbenv를 자동으로 초기화할 수 있다.1$ ~/.rbenv/bin/rbenv init ruby-build를 추가로 설치하자. 이를 통해 rbenv install 명령어를 사용할 수 있게 된다. 설치 후 rbenv install -l 명령을 수행하면 설치 가능한 ruby의 version들이 쫙나온다.1$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build 나는 2.3.0 을 설치해 보기로 했다. rbenv install 2.3.0 명령으로 간단하게 ruby를 설치할 수 있다. 한 번 실패했는데 sudo apt-get install -y libssl-dev libreadline-dev zlib1g-dev를 설치했어야 했다. rbenv versions 명령을 통해 현재 설치/설정된 ruby version들을 볼 수 있다. rbenv local 2.3.0 으로 local 계정에 적용되는 ruby version을 2.3.0으로 변경하였다. 추가로, 새 version의 ruby나 gem을 설치하게 되면 변경된 실행 정보를 알리기 위해 rbenv rehash 명령을 수행해야 한다. 다음은 gem을 이용하여 bundler를 설치한다. gem이란 ruby의 plugin 정도로 이해하면 편하겠다. ruby에 종속된다. bundler란 모듈을 한데 모아서 관리하는 것이다. ruby의 경우 gem들을 모아서 관리하는 주체라고 보면 되겠다. gem install bundler 로 bundler gem을 설치할 수 있으며, sudo 권한은 필요치 않다. rbenv를 통해 ruby version을 변경할 경우 그 버전에 맞는 gem을 추가로 설치해야 한다. 그런데 에러가 났다. 에러 내용은 이렇게.12ERROR: While executing gem ... (Errno::EACCES) Permission denied @ dir_s_mkdir - /var/lib/gems rbenv를 통해 설치한 ruby는 ~/.rbenv/versions/2.3.0/bin에 있는데 system에 기본 설치된 ruby가 실행되어 생기는 문제이다. ~/.bashrc 에 경로를 추가해두자. rbenv에서 이것까지 관리가 될 것 같은데 뭔가 좀 이상하다. 그래도 일단은 해결하고 넘어가자.1$ echo 'export PATH=\"$HOME/.rbenv/versions/2.3.0/bin:$PATH\"' >> ~/.bashrc rails 설치이제 ruby의 설치가 끝났다. rails 세팅에 돌입하자. rails는 ruby gem 중 하나이다. THE RUBY ON RAILS TUTORIAL 를 베이스로 진행한다.1$ gem install rails rails project 생성 아래의 명령으로 ‘blog’라는 이름을 갖는 rails project(application)을 생성할 수 있다.1$ rails new blog rails application은 아래의 명령을 통해 실행한다.1$ rails server 일단 에러가 날 것이다. 이유는 해당 application이 필요로 하는 gem들이 설치되지 않아서이다.필요로 하는 gem들의 정보는 “Gemfile” 에 선언되어 있고, 구현하고자 하는 application에서 사용할 gem들을 직접 정의해 줄 수도 있다. 아래 명령으로 gem들을 설치한다.1$ bundle install 내 pc(virtual box로 돌리는 ubuntu) 기준으로 아래와 같은 에러가 발생한다.An error occured while installing sqlite3 (1.3.11), and Bundler cannot continue.Make sure that ‘gem install sqlite3 -v ‘1.3.11’’ succeeds before building. 시키는대로 설치를 해보자.1$ gem install sqlite3 -v '1.3.11' 에러를 뿜는다. 이번에는 apt-get install libsqlite3-dev 를 설치하라고 한다. 하고 sqlite3 gem을 install하자. 자, 이제 대망의 rails server 를 실행한다. 또 에러가 난다… Could not find gem 'sass-rails (~>5.0)' in any of the gem sources listed in your Gemfile or available on this machine. … 힘들다. gem install sass-rails 를 통해 최신 버전을 설치한 후 bundle install 그리고 rails server 를 다시 실행한다. 이번에는 다음과 같은 에러가 발생했다. 1/home/user/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gem/bundler-1.11.2/lib/bundler/runtime.rb:80:in 'rescue in block (2 levels) in requir': There was an error while trying to load the gem 'uglifier'. 그냥 구글에 검색해본다. 검색 뚝딱뚝딱 그렇다. stackoverflow에 답이 있다. apt-get install nodejs 후 rails server를 하니 드디어 된다. uglifier가 뭔지 찾아보니 JavaScript parser, minifier, compressor, beautifier 등의 역할을 하는 툴인 것 같다. 나중에 기회가 되면 알아보자. 이제 브라우저에서 localhost:3000 을 입력하면 접속이 되어야 한다. 나의 경우 virtualbox에서 실행하였으므로, 네트워크 브리지를 통해 Windows의 Browser를 통해 접속하여 확인하였다. VirtualBox 리눅스 SSH 연결 (브리지)를 참조하였으며, ifconfig를 통해 Virtual box에 할당된 IP를 알아낼 수 있는데 rails 실행 시 옵션을 추가해 주어야 제대로 된 실행 결과를 볼 수 있다. 00.00.00.00은 ifconfig를 통해 알아낸 IP를 적고, 3000 자리에는 마음에 드는 port number를 적어준다. 그리고 Windows의 Browser에서 00.00.00.00:3000 으로 접속하면 드디어 Welcome aboard 화면을 볼 수 있게 된다. 1rails server -b 00.00.00.00 -p 3000 Virtual box 공유폴더 설정 1234561. 장치 -> 공유 폴더 -> 공유 폴더 설정2. 장치 -> 게스트확장 이미지 삽입3. /media/sf_폴더이름 으로 생긴 것 확인4. 폴더의 Group permission이 vboxsf로 되어있으므로 아래 명령으로 계정을 그룹에 추가 - sudo gpasswd -a 계정명 vboxsf5. reboot 명령으로 시스템 재부팅 경로를 돌아가는 것이 불편하여 아래와 같이 symbolic ilnk를 걸어 사용하기로 한다.1sudo ln -s /media/sf_shared ~/shared","categories":[{"name":"Dev","slug":"Dev","permalink":"http://lazyrodi.github.io/categories/Dev/"},{"name":"Ruby on rails","slug":"Dev/Ruby-on-rails","permalink":"http://lazyrodi.github.io/categories/Dev/Ruby-on-rails/"}],"tags":[{"name":"ruby on rails","slug":"ruby-on-rails","permalink":"http://lazyrodi.github.io/tags/ruby-on-rails/"},{"name":"settings","slug":"settings","permalink":"http://lazyrodi.github.io/tags/settings/"}]},{"title":"First posting - Jekyll 작업 완료","slug":"2016-03-19-life-first-posting","date":"2016-03-19T11:00:00.000Z","updated":"2016-08-05T12:41:12.405Z","comments":true,"path":"2016/03/19/2016-03-19-life-first-posting/","link":"","permalink":"http://lazyrodi.github.io/2016/03/19/2016-03-19-life-first-posting/","excerpt":"","text":"Jekyll 작업 완료. 신문물이 아무리 많아도 알지 못하고 사용하지 못한다면 무용지물이다. 이미 늦었을지도 모르지만 더 늦기 전에 하나씩 따라잡아보기로 하자. 일단은 github.io 계정 생성, Jekyll 작업 완료. 작심삼일이 되지 않기를 바라며.","categories":[{"name":"life","slug":"life","permalink":"http://lazyrodi.github.io/categories/life/"}],"tags":[]}]}