차근차근/Android
[ study ] 다이어리 어플 만들어보기 2 - 부가설명6
안드로이드 폰에서 갤러리의 이미지나 카메라의 이미지를 가져오는 것은 했다.
그런데 DB에 넣으려고 찾다보니. 이런 글 발견
http://www.androidside.com/bbs/board.php?bo_table=B49&wr_id=41868
음... 솔직히 말씀드리자면 가능은 합니다.
어짜피 sqlite 밖에 앱용 db를 쓸수 밖에 없을테고 거기서 BLOB형을 이용하면 되긴 할테니까요..
문제는 효율입니다.
실제 앱을 제작하는 작업 등을 하는 사람 중 진짜 왠만해선 그런 큰데이터를 db에 넣는짓은 안합니다.
안드로이드 시라면 이미지를 asset이나 res에 넣어서 사용하는걸 추천합니다
sql DB 에 파일 저장 경로만 DB 에 지정하고 그 경로를 읽어 오는걸로 하시는게 날듯 싶습니다
uri만 저장하시면 될듯합니다. 혹은 경로나요... 안드로이드에서 uri는 이럴떄 쓰라고 있는듯합니다....
일단 자료를 모아본다.
우선 안드로이드 어플에서 서버로 이미지 파일을 전송하게 하려면 서버측에서 이미지 파일을 받아서 저장해주는
프로그램이 필요하다.
multipart/form-data 를 이용하면 파일을 업로드하는 프로그램을 작성할 수 있다.
com.oreilly.servlet 패키지의 MultipartRequest 클래스를 이용하여 쉽게 파일 업로드를 구현할 수 있다.
http://www.servlets.com/cos/cos-05Nov2002.zip 사이트에서 cos-05Nov2002.zip 파일을 다운로드 받아서
압축을 풀어 cos.jar 파일을 <Tomcat>/common/lib 에 복사합니다.
***************************************************************************
<%@ page import="java.io.*,com.oreilly.servlet.*,com.oreilly.servlet.multipart.*"%> <% request.setCharacterEncoding("euc-kr"); %> <html> <% //최대크기 max바이트, dir 디렉토리에 파일을 업로드하는 MultipartRequest new DefaultFileRenamePolicy()); <jsp:forward page="vew.jsp"/> </body>
|
view.jsp 파일을 다음과 같이 작성해서 업로드 된 이미지 파일을 확인해 본다.
<body>
<img src="upload/image01.jpg"/>
</body>
안드로이드에서 MultipartRequest를 이용하여 이미지 파일을 전송하려면 프로젝트에
httpmime-4.0.3.jar
apache-mime4j-0.6.1.jar
commons-logging-1.1.1.jar
httpcore-4.0.1.jar
라이브러리를 추가해 주어야 한다.(첨부파일 다운로드 받으심 됨)
카메라를 이용해 사진을 찍고, 찍어진 사진은 일단 뷰로 화면에 표시되고, 화면에 표시되는 동안
이미지파일을 서버에 전송한뒤, 전송이 완료되면 다시 카메라를 이용해 사진을 찍을 수 있도록 한다.
먼저 CameraTestActivity 를 실행하기 위한 Layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"> <SurfaceView android:id="@+id/SurfaceView01" android:layout_marginTop="20dip" android:layout_width="300dp" android:layout_height="fill_parent" android:layout_gravity="center_horizontal"/>
<ImageView android:id="@+id/ImageView01" android:layout_width="300dp" android:layout_height="fill_parent" /> </LinearLayout> |
카메라와 인터넷, 셔트를 사용하기 위한 AndroidManifest.xml 권한설정 및 Activity 등록
<activity android:name=".CameraTestActivity" android:screenOrientation="landscape"/> <uses-permission android:name="android.permission.CAMERA" />
|
카메라 촬영및 서버로 이미지 파일 업로드를 처리하는 Activity .
package kims.android.device.test; import java.io.DataOutputStream; import java.net.HttpURLConnection; import java.net.URL; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Camera; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; import android.widget.ImageView; import android.widget.Toast;
public class CameraTestActivity extends Activity { private static final String TAG = "CameraTestActivity"; // 카메라 제어 private Camera camera; // 촬영된 사진보기 private ImageView imageView; // 처리중 private boolean inProgress; //카메라에 찍힌 이미지 데이터 byte[] data; DataOutputStream dos; ImageView view; SurfaceView surfaceView;
// 카메라 SurfaceView의 리스너 private SurfaceHolder.Callback surfaceListener = new SurfaceHolder.Callback() { // SurfaceView가 생성되었을때 화면에 보여주기위한 초기화 작업을 수행 // 카메라를 오픈하고, 프리뷰의 위치를 설정한다. public void surfaceCreated(SurfaceHolder holder) { camera = Camera.open(); try { camera.setPreviewDisplay(holder); //카메라의 preview 설정 } catch (Exception e) { e.printStackTrace(); } } // SurfaceView가 화면에 표시되지않을때(액티비티가 비활성화 될때)호출한다. public void surfaceDestroyed(SurfaceHolder holder) { camera.release(); camera = null; }
// surfaceCreate()가 호출된 다음 호출된다. // 프리뷰의 크기를 설정하고 프리뷰 영상을 표시한다. public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // 카메라의 파라메터 값을 가져와서 미리보기 크기를 설정 하고 // 프리뷰를 보여준다. Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(width, height); camera.setParameters(parameters); camera.startPreview(); } };
// 카메라 셔트가 눌러질때 private Camera.ShutterCallback shutterListener = new Camera.ShutterCallback() {
public void onShutter() {
if (camera != null && inProgress == false) {
camera.takePicture( shutterListener, // 셔터 후 null, // Raw 이미지 생성 후 picutureListener); // JPE 이미지 생성 후 inProgress = true; } } };
Bitmap bitmap; // JPEG 이미지를 생성 후 호출 private Camera.PictureCallback picutureListener = new Camera.PictureCallback() { public void onPictureTaken(byte[] data, Camera camera) { if (data != null) { // 적용할 옵션이 있는 경우 BitmapFactory클래스의 Options() // 메서드로 옵션객체를 만들어 값을 설정하며 // 이렇게 만들어진 옵션을 Bitmap 객체를 만들때 네번째 // 아규먼트로 사용한다. // // 처리하는 이미지의 크기를 축소 // BitmapFactory.Options options = // new BitmapFactory.Options(); // options.inSampleSize = IN_SAMPLE_SIZE; MainActivity.this.data=data; bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, null); //이미지 뷰 이미지 설정 imageView.setImageBitmap(bitmap); doSaveFile(); // sdcard에 파일 저장 doFileUpload(); //서버에 이미지를 전송하는 메서드 호출 Toast.makeText(MainActivity.this, "서버에 파일을 성공적으로 전송하였습니다", Toast.LENGTH_LONG).show();
camera.startPreview(); // 정지된 프리뷰를 재개 inProgress = false; // 처리중 플래그를 끔
} } };
File saveFile=new File("/mnt/sdcard/image01.jpg"); // 파일이 저장되는 경로 지정 public void doSaveFile() {
OutputStream out = null; try { saveFile.createNewFile(); out = new FileOutputStream(saveFile); bitmap.compress(CompressFormat.JPEG, 70, out); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) {e.printStackTrace();} } }
public void doFileUpload() { try { HttpClient httpClient = new DefaultHttpClient(); String url = "http://172.20.11.235:8080/web1/file.jsp"; HttpPost post = new HttpPost(url); FileBody bin = new FileBody(saveFile); MultipartEntity multipart = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); multipart.addPart("images", bin);
post.setEntity(multipart); HttpResponse response = httpClient.execute(post); HttpEntity resEntity = response.getEntity(); }catch(Exception e){e.printStackTrace();} }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 액티비티의 타이틀이 안보이도록 설정한다. requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.camera); imageView = (ImageView) findViewById(R.id.ImageView01); surfaceView = (SurfaceView) findViewById(R.id.SurfaceView01);
// surface를 제어하는 SurfaceHolder SurfaceHolder holder = surfaceView.getHolder();
// SurfaceView 리스너를 등록 holder.addCallback(surfaceListener);
// 버퍼 사용하지 않음 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
// 키가 눌러졌을때 카메라 셔트가 눌러졌다고 이벤트 처리설정 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub
if(event.getAction() == KeyEvent.ACTION_DOWN) { switch(keyCode) { case KeyEvent.KEYCODE_CAMERA: shutterListener.onShutter(); return true; } } return super.onKeyDown(keyCode,event); } }
|
[안드로이드 프로그래밍] 서버에서 이미지 불러와서 이미지뷰에 띄우기
http://cholol.tistory.com/154안드로이드에서 이미지를 보여주는 방법은 여러 가지가 있습니다. 가장 기본적인 방법은 어플내에 있는 그림파일을 보여주는 것인데, 어플에서 여러 그림을 사용하는 경우 어플의 용량이 증가하는 단점이 있고, 동적으로 그림을 바꿔주지 못한다는 단점이 있습니다. 동적으로 그림을 바꿔주지 못한다는 것은 새로운 그림으로 교체하고 싶을 때는 어플을 업데이트해야지만 그림을 교체할 수 있다는 것입니다.
이런 문제점들을 해결하는 방법은 서버에서 이미지를 받아오는 방법입니다. 서버에서 그림을 받아오기 때문에 어플용량에 대한 부담을 덜어주고, 이미지를 바꾸고 싶은 경우에는 서버에서 이미지만 교체하면 됩니다.
단순히 서버에서 이미지를 받아오는 것은 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public void loadBitmap(String strUrl) { Bitmap bitmap = null; try { URL url = new URL(strUrl); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); bitmap = BitmapFactory.decodeStream(is); imView.setImageBitmap(bmp); } catch (IOException e) { e.printStackTrace(); } } |
그림파일에 대한 URL을 받아와서 HttpURLConnection 을 이용해 연결한후, 그림을 스트림으로 받아와서 비트맵파일로 만들어 마지막에 imView.setImageBitmap 을 사용해 뷰에 그림을 띄웁니다.
하지만 이 코드를 사용해서 프로그램을 실행시키면 다음과 같은 오류가 발생합니다.
이는 허니콤버전 이후 안드로이드에서 네트워크 작업을 할 때 메인스레드에서 실행 시 발생하는 오류입니다. 네트워크 작업은 별도의 스레드에서 하는 것이 안정적입니다. 지난 포스팅에서 AsyncTask 를 배웠으니 AsynTask 를 사용해서 프로그램을 만들어 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | package com.example.downloadimg; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.widget.ImageView; public class MainActivity extends Activity { ImageView imView; String imgUrl = "http://dnllab.incheon.ac.kr/appimg/"; Bitmap bmImg; back task; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); task = new back(); imView = (ImageView) findViewById(R.id.imageView1); task.execute(imgUrl+"img1"); } private class back extends AsyncTask<String, Integer,Bitmap>{ @Override protected Bitmap doInBackground(String... urls) { // TODO Auto-generated method stub try{ URL myFileUrl = new URL(urls[0]); HttpURLConnection conn = (HttpURLConnection)myFileUrl.openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); bmImg = BitmapFactory.decodeStream(is); }catch(IOException e){ e.printStackTrace(); } return bmImg; } protected void onPostExecute(Bitmap img){ imView.setImageBitmap(bmImg); } } } |
저는 back이라는 클래스를 만들어서 AsyncTask 를 extend했습니다. AsyncTask 의 첫 번째 generic 은 백그라운드 작업을 담당하는 메소드인 doInBackground 에 넘길 generic 으로 그림의 주소 값을 가지고 있는 String을 넣었습니다. 가운데 generic은 onProgressUpdate 메소드를 위한 generic인데 사용하지 않아서 그냥 Integer를 넣었습니다. Void를 넣어도 상관없습니다. 마지막 generic은 doInBackground 메소드가 끝나고 반환하는 generic으로 비트맵 타입을 넣었습니다.
이렇게 되면 doInBackground 메소드는 그림파일의 주소가 있는 String타입의 인자값을 받아와서 그림파일을 bitmap형식으로 반환하게 됩니다. bitmap 형식으로 반환하도록 설정한 이유는, 메인스레드가 아닌 다른 스레드에서 UI 객체에 접근할 수 없기 때문입니다. 만약 bitmap을 반환하지 않고 doInBackground 에서 바로 UI 객체에 접근해 뷰.setImage 등을 사용하여 변화시키려 한다면 에러가 발생합니다. 따라서 메인쓰레드에서 실행되는 onPostExecute 메소드를 통해 그림파일을 전달해서 UI객체에 접근하는 방법을 사용합니다.
onPostExecute 메소드를 보면 인자값으로 Bitmap img 가 되어있는 것을 확인할 수 있습니다. onPostExecute 는 메인 스레드에서 실행되기 때문에 imView.setImageBitmap 을 사용하여 UI 객체에 접근이 가능합니다. 실질적으로 이미지를 변경하는 부분은 이 부분이라고 할 수 있습니다.
이렇게 back 클래스를 만들고 나서 메인클래스에서 back 클래스의 객체를 만들고 execute 메소드를 사용하면 asynctask 를 사용할 수 있습니다.
안드로이드에서 JSP서버로 파일 업로드 질문입니다.(httppost, multipartentitybuilder사용)
http://m.ppomppu.co.kr/new/bbs_view.php?id=developer&no=14724
아.. 인터넷 찾다가.. 다른 jsp 소스 받아서 하니 잘 되네요 ㅠㅠ <%@ page language="java" contentType="text/html;charset=utf-8" pageEncoding="UTF-8"%> <%@ page import="java.util.List" %> <%@ page import="java.util.Iterator" %> <%@ page import="java.io.File" %> <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%> <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%> <%@ page import="org.apache.commons.fileupload.*"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSP Page</title> </head> <body> <% boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (!isMultipart) { out.println("not "); } else { out.println("multipart"); FileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List items = null; try { items = upload.parseRequest(request); } catch (FileUploadException e) { e.printStackTrace(); } Iterator itr = items.iterator(); while (itr.hasNext()) { FileItem item = (FileItem) itr.next(); if (item.isFormField()) { out.println("FormField"); String name = item.getFieldName(); String value = item.getString(); System.out.println("Parameters " + name + " value " + value); } else { out.println("else FormField"); try { String itemName = item.getName(); File root=File.listRoots()[0]; File f = new File("저장경로"+itemName); f.setWritable(true); f.setReadable(true); item.write(f); } catch (Exception e) { e.printStackTrace(); } } } } %> </html> |
[Android] SQLite활용 : 서버에 있는 데이터베이스(DB) 내려받아 설치하기
내용이 이상하거나, 잘못됐거나, 이해가 안되는 부분은 꼭 댓글로 남겨주시라~! 본인이 답변도 드리고, 추후에 방문한 분들께 더 나은 포스트를 제공해드리는데 큰 도움이 된다.
서버와 DB를 활용한 원격 판올림은 서버에 SQLite 데이터베이스(이하 DB)를 올려 놓은 후 앱에서 다운로드 받아 새로운 DB로 갱신하여 앱을 판올림하는 것이다.
이는 안드로이드보다는 아이폰에 더 필요한 사항으로, 검수가 약 일주일이 걸리는 현실을 고려해 최근 여러 앱에 도입되는 추세다. (물론 이 전에도 지속적으로 도입되고 있었다. 예로 버스, 지하철 앱)
다행인 것은 안드로이드도, iOS도 SQLite를 지원한다는 것이다.
그러므로 우리는 이미지가 들어있지 않는 한 하나의 DB를 가지고 두 플랫폼을 모두 지원할 수 있다.
(하지만 본인 회사에서 서비스하는 앱은 이미지가 첨부되기 때문에 DB를 따로 운영한다 -_-; 무결성은 전적으로 사람에게로...;;;)
(현재 두 플랫폼 레이아웃에 맞춰봤을 때 더 큰 이미지를 탑재해 하나의 DB로 관리하고 있다. 아이폰 또는 안드로이드(xhdpi) 이미지를 탑재해서 둘 중 하나의 플랫폼에서 크기를 조절하여 쓰는 것도 한 방법이다.)
웹에서 찾아보면 "SQLite 활용하기", "DB 서버에서 다운받아 설치하기", "SQLite 테이블 생성" 등을 볼 수 있다. 본 포스트는 이러한 내용들을 통합한 것이라고 보면 된다. 참고로 본 포스트에서는 테이블 또는 레코드의 생성(테이블), 삭제, 수정, 삽입(레코드) 등은 다루지 않는다.
본 포스트에서 설명할 "서버에 업로드된 DB를 단말에서 받아 설치"하는 내용은 형규님의 포스트를 참고하였다.
형규님 블로그 : Android apk 내 대용량 파일 처리 방법(SQLite DB 등)
본 포스트를 참고하기 위해서는 DB에 대한 기본지식, SQL문의 작성법, Java 네트워크 프로그래밍 등이 필요하다.
DB를 이용한 판올림의 과정은 다음과 같다.
(최초)DB생성 > DB 입력 > 서버에 DB 업로드 > 단말에 다운로드 > 단말에 설치 > 단말에서 활용
DB 생성은 최초 한 번만 해주면 되며, 이후에는 입력부터 시작해 업로드로 진행된다.
/* DB 생성, 입력 */
DB 생성은 SQLite를 위한 툴을 이용하면 한결 간편하다.
특히 Firefox의 부가기능인 SQLite Manager를 이용하면 쉽게 DB를 관리할 수 있다.
SQLite Manager로 검색하면 사용법등이 잘 나와있는 블로그들이 많다.
물병자리 시대의 여명 블로그 : SQLite Manager 설치하기
SQLite Manager에서 DB를 생성하면 DB 파일을 저장할 곳을 묻는데, 해당 위치에 가 보면 DB명으로 sqlite파일이 생성되어 있을 것이다.
/* 서버에 DB 업로드 */
DB가 생성되었다면 서버에 자리 한 켠을 마련하여 FTP등으로 올려놓자.
/* 단말에 다운로드 */
단말에서 DB를 내려받을지를 확인하는 경우는 2가지다.
1. 받아놓은 DB파일이 없을 때 (새로 설치 과정을 따르게 된다.)
2. 서버 파일이 갱신됐을 때 (판올림 과정을 따르게 된다.)
먼저 받아놓은 파일이 있는지 검색한다.
// 자주색으로 된 부분은 자신의 기호에 맞게 변경하여 사용하도록 한다.
File dbFile = new File(
Environment.getDataDirectory().getAbsolutePath() + "/data/" + getPackageName() + "/mydb.sqlite");if (dbFile.exists()) {
// 파일이 있을 경우 처리
} else {
// 파일이 없다면 최초 DB 설치 과정으로 진행
showDialog(DIALOG_DB_FIRST);
}
Environment.getDataDirectory().getAbsolutePath() 는 앱에 관련된 파일을 저장할 수 있는 공간의 경로를 받아온다.
위 경로 + data폴더에 파일을 저장할 수 있다.
그 중 자신이 쓸 것은 자신의 패키지 경로이므로 getPackageName()을 붙여준다.
마지막으로 DB 파일명을 붙인다.
(잘 이해가 안되시는 분은 mydb.sqlite 부분만 자신이 원하는 파일명으로 고쳐서 사용하시면 된다.)
DataDirectory를 사용하는 이유는 앱이 지워질 때 같이 지워질 수 있도록 하기 위함이다.
SD카드에 저장할 경우 사용자가 앱을 지워도 DB가 남아 기기 저장공간이 지저분해 질 수 있다.
- SD카드에 설치한 경우, 앱을 삭제했을 때 DB파일이 남는다. 외부에서 접근이 쉽다. 용량 확보가 용이하다.
- Data영역에 설치한 경우, 응용프로그램 관리에서 해당 앱 정보를 봤을 때 데이터에 있는 용량이 바로 이 DB 파일 용량이며, 데이터 지우기를 눌렀을 경우 DB파일을 지울 수 있고 앱을 삭제했을 경우 함께 삭제된다.
되도록 모든 문자열은 상수로 정하여 사용하는 것이 좋다. DB 경로는 DB를 열 때마다 사용된다.
(이 말인즉슨, Environment.getDataDirectory().getAbsolutePath() + "/data/" + getPackageName() + "/mydb.sqlite 과 같은 DB 경로는 여러 곳에서 쓰일 수 있기 때문에 매번 저 긴 코드를 붙여넣기 보다는 상수로 만들어 사용하는 것이 훨씬 용이하고 관리도 편하다는 것이다.)
다시 코드로 돌아가서 showDialog(DIALOG_DB_FIRST); 가 실행되면 현재 설치된 DB가 없어 설치를 진행해야 한다는 AlertDialog가 노출되고, 확인을 누르면 새로 다운로드받아 설치하게 된다.
(DIALOG_DB_FIRST는 상수다.)
*showDialog 참고
랩하는 프로그래머님 : [안드로이드] 다이얼로그 생성하기
그렇다면 DB 갱신은 어떻게 하는지 알아보자.
// 아래 코드는 위의 받아놓은 파일이 있는지 검색하는 코드를 확장한 것으로,
// 파일이 있을 경우를 처리하는 코드와 파일이 없을 경우를 처리하는 코드가 추가되었다.
/* checkDB() */
{
File dbFile = new File(K.getDbAbsolutePath(this));
if (dbFile.exists()) {
// 파일이 있을 경우, DB의 상태를 확인한다. SELECT문을 날려서 데이터가 날라오지 않는다면 DB파일에 이상이 생긴 것이므로 다시 다운로드 받는다.
SQLiteDatabase db = SQLiteDatabase.openDatabase(K.getDbAbsolutePath(SplashActivity.this), null, SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
Cursor cursor = db.rawQuery("SELECT * FROM tablename LIMIT 1, 10", null);
// SELECT문이 레코드를 못찾을 경우 count는 -1이다.
if (!db.isOpen() || cursor.getCount() <= 0) {// DB가 비정상이라면 새로운 DB를 받기 전 사용자에게 대화상자로 알린다.
handler.sendEmptyMessage(WHAT_DB_RECOVERY);return;
} else {
// 네트워크 처리를 위해 Thread 생성
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 서버의 DB 경로로 URL 객체를 생성한다.
// K.DB_URL = "http://xxxxxxx.xxx/xxxxxx/db.sqlite"
URL url = new URL(K.DB_URL);
// 해당 URL로 연결한다.
URLConnection conn = url.openConnection();
// 해당 경로에 위치한 대상의 가장 최근에 수정된 날짜를 받아온다.(Millisecond)
// lastModified 변수는 long 형태로 선언되어야 한다. 클래스 멤버다.
lastModified = conn.getLastModified();
// 받아온 수가 0보다 크면 제대로 연결됐으므로 정상처리한다.
if (lastModified > 0) {
// 단말기 내에 저장된 DB의 최신 변경 날짜를 조회하기 위해 생성한다.
// SharedPreferences 에 대해서는 다음을 참고하자.
// [Android] 상태 저장하기 (savedInstanceState, SharedPreference)
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(SplashActivity.this);
// 저장된 DB의 수정 날짜를 조회해 서버의 수정 날짜와 비교한다.
if (lastModified > pref.getLong("savedLatest", 0)) {
handler.sendEmptyMessage(WHAT_DB_UPDATE);
// 서버 DB가 갱신되지 않았다면 메인화면으로 넘어간다. (앱 바로 사용)
} else {handler.sendEmptyMessage(WHAT_DB_SKIP);
}
} else {
// 아니면 (lastModified가 0이면) 오류를 알린다.
handler.sendEmptyMessageDelayed(WHAT_DB_CHECK_ERROR, 1000);
}
} catch (MalformedURLException e) {
e.printStackTrace();
handler.sendEmptyMessageDelayed(WHAT_DB_CHECK_ERROR, 1000);
} catch (IOException e) {
e.printStackTrace();
handler.sendEmptyMessageDelayed(WHAT_DB_CHECK_ERROR, 1000);
}
}
});
thread.start();
}
// 사용한 DB와 커서를 닫아준다.
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
// 파일이 없을 경우 DB 최초 설치 과정으로 진행한다.
} else {
showDialog(DIALOG_DB_FIRST);
}
}
handler가 호출하는 메서드에 들어가는 WHAT_DB_SKIP 이나 WHAT_DB_UPDATE같은 값들은 상수이며, 개발자가 임의의로 정하여 사용하면 된다.
본인의 경우, WHAT_DB_CHECK_ERROR일 때에는 그냥 정상 진행하는 것으로 처리하였다.
이는 이미 앞서 DB의 존재유무를 파악했기 때문에 서버에 접속할 수 없더라도 (만약 서버에 DB파일이 갱신되었다 하더라도) 이전 버전의 DB로 앱을 사용할 수 있도록 한 것이다.
DB가 갱신되었는지 비교 판단하는 방법으로 본인은 SharedPreferences(이하 SP)를 사용하였다.
일단 처음 설치시에는 DB에 대한 아무런 정보가 없기에 SP에 아무런 내용도 기록되어 있지 않겠지만,
일단 DB를 한 번이라도 설치했다면 DB의 수정된 날짜를 SP에 기록한 후 추후 서버의 수정된 날짜와 비교하여 업데이트를 판단하는 것이다.
그래서 위의 checkDB()가 끝나고 핸들러가 메세지를 받으면 각 메세지에 담겨진 what 값에 따라
대화상자를 띄우게 된다. (업데이트를 해야 하는지, 새로 설치해야 하는지)
이제 DB를 본격적으로 다운로드 받는 방법에 대해 알아보자.
위 소스를 보면 "처음 설치 과정"과 "업데이트", "복구" 과정이 있는데,
이 들의 차이는 간단하다.
다운로드 받기 전 나타나는 메세지에 차이가 있을 뿐이다.
1. 처음 설치한 경우
현재 저장된 DB가 없습니다.
앱을 사용하기 위해서 DB를 설치해야 합니다.
지금 설치하시겠습니까?
(본 과정은 네트워크를 사용하므로 와이파이 환경에서 진행하시길 권장합니다.)
[다운로드] [종료]
2. DB 갱신
DB가 업데이트 되었습니다.
이전 버전 : 2012년 2월 18일 18시 16분
최신 버전 : 2012년 2월 22일 11시 43분
[다운로드] [나중에]
3. DB 복구
DB가 손상되었습니다.
앱을 사용하기 위해서 DB를 복구해야 합니다.
지금 복구하시겠습니까?
(본 과정은 네트워크를 사용하므로 와이파이 환경에서 진행하시길 권장합니다.)
[다운로드] [종료]
그리고 다운로드를 누르면 같은 메서드로 진입한다. 종료를 누르면 앱이 종료된다.
(대화상자에서 [다운로드] 버튼을 누르면 아래 메서드로 진입하도록 작성하면 된다.)
그럼 이제 DB를 내려받아보자.
/* downloadDB() */
{
// 내려받기 진행상황을 보여주기 위한 ProgressDialog를 띄운다.
showDialog(DIALOG_DB_DOWNLOAD);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
InputStream is = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
URL url;
URLConnection conn;
try {
// 서버의 DB 경로로 URL 객체를 생성한다.url = new URL(K.DB_URL);
// 해당 URL로 연결한다.
conn = url.openConnection();
// 서버에 있는 DB 파일의 용량을 받아온다.
int lengthOnServer = conn.getContentLength();
// 용량이 0보다 크면 제대로 연결되었다고 판단하고 정상 진행한다.
if (lengthOnServer > 0) {
dbDialog.setMax(lengthOnServer);
} else {
// 용량이 0보다 크지 않을 경우 오류임을 표시한다.
handler.sendEmptyMessageDelayed(WHAT_DB_DOWNLOAD_ERROR, 1000);
return;
}
if (lastModified == 0)
lastModified = conn.getLastModified();
}
// 다운로드 받은 파일을 시스템에 저장하기 위해 InputStream을 얻는다.
is = conn.getInputStream();
// DB 파일을 저장할 경로로 File 객체를 생성한다. (폴더 경로만)
// 본인의 경우 K.getDbDirPath()를 호출하면 아래 경로를 반환하도록 작성하였다.
// Environment.getDataDirectory().getAbsolutePath() + "/data/" + getPackageName() + "/mydb.sqlite"
File dir = new File(K.getDbDirPath(Activity.this));
// 만약 폴더가 없다면 생성한다.
if (!dir.exists()) {
dir.mkdir();
}
// 폴더경로에 파일명까지 붙인 경로로 File 객체를 생성한다.
File target = new File(K.getDbAbsolutePath(Activity.this));
// 파일이 존재한다면 삭제한다.
if (target.exists()) {
target.delete();
}
// 그리고 새로운 파일을 생성한다.
target.createNewFile();
// 아래는 생성한 파일에 서버의 DB를 쓰는 과정이다.
fos = new FileOutputStream(target);
bos = new BufferedOutputStream(fos);
int bufferLength = 0;
<font color="#009e25">// 다운로드가 진행되는동안 다운로드된 크기를 축적하는 맴버 변수다.</font>totalLength = 0;
// 버퍼 크기가 클 수록 다운로드는 빨라진다. 하지만 메모리를 많이 잡고 있다는 것을 명심하자.
byte[] buffer = new byte[1024];
while((bufferLength = is.read(buffer)) > 0) {
// bos.write()로 파일을 쓸 수 있다.
bos.write(buffer, 0, bufferLength);
// 총 받은 양을 기록한다.
totalLength += bufferLength;
runOnUiThread(new Runnable() {
@Override
public void run() {
// 진행 상태 대화창에 진행량을 증가시킨다.
dbDialog. setProgress(totalLength);
}
});
// sleep()없이 진행해보시면 아시겠지만, 없을 때 속도는 빠를지라도 사용자에게 명시적으로 보여주기 힘든 상황이 발생한다. 진행 과정이 부드럽게 보여지지 않는다. 하지만 없으면 다운로드가 빨라진다.
Thread.sleep(1);
}
// 내려받은 용량과 서버에 있는 DB 파일의 용량이 같지 않으면 오류를 표시한다.
if (totalLength != lengthOnServer) {
handler.sendEmptyMessageDelayed(WHAT_DB_DOWNLOAD_ERROR, 1000);
return;
}
// 마지막으로 100% 달성을 보여주기 위해 진행상태를 최대치로 입력한다.
runOnUiThread(new Runnable() {
@Override
public void run() {
dbDialog.setProgress(dbDialog.getMax());
}
});
// 다운로드가 완료된 후 행동을 취하기 위해 Handler에 Message를 전달한다.
handler.sendEmptyMessageDelayed(WHAT_DB_DOWNLOADED, 1000);
} catch (MalformedURLException e) {
e.printStackTrace();
handler.sendEmptyMessageDelayed(WHAT_DB_DOWNLOAD_ERROR, 1000);
} catch (IOException e) {
e.printStackTrace();
handler.sendEmptyMessageDelayed(WHAT_DB_DOWNLOAD_ERROR, 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 최종적으로 사용했던 Stream들을 정리한다.
try {
if (bos != null) bos.close();
if (fos != null) fos.close();
if (is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
다음 소스를 같이 살펴보도록 하자.
runOnUiThread(new Runnable() {
@Override
public void run() {
// 진행 상태 대화창에 진행량을 증가시킨다.
dbDialog.setProgress(totalLength);
}
});
runOnUiThread는 UI작업을 할 때 사용하는 메서드다. 이 메서드를 UIThread가 아닌 Thread에서 호출하면 해당 작업을 UI Thread의 event queue에 집어넣는다.
* 자세한 Thread관련 내용은 다음 블로그를 참고하면 많은 도움이 될 듯 싶다.
i티거님 : 안드로이드 021: Thread 구현하기 2/2 (with AsyncTask & ProgressBar)
하지만 아직까지 밝혀지지 않은 미스테리가 있다. runOnUiThread()를 사용하든, Handler의 post()를 사용하든 ProgressDialog의 ProgressBar와 Progress값이 서로 일치하지 않는다는 것이다. Bar는 덜 찼는데 값은 100%라거나, 반대의 경우가 생기기도 한다.
해서 본인이 생각한 방법이 바로 Thread.sleep(1);인 것이다. 주기적으로 쉬어주면 진행상태도 자연스럽고, 위와 같은 미스테리도 어느정도 해결된다.
진행 중 인터넷이 끊어지거나 용량이 상이한 경우
handler.sendEmptyMessageDelayed(WHAT_DB_DOWNLOAD_ERROR, 1000);
가 호출되는데, 이 때 오류를 알리는 AlertDialog를 띄우고 재시도를 하거나 종료할 수 있도록 하였다.
DB 다운로드가 끝났으면, 다운로드 받은 DB의 수정된 날짜를 SP에 저장하도록한다.
(Handler에서 Message 처리)
// DB를 다 받고 시스템에 저장했으므로 SharedPreferences에 최신 날짜로 저장한다.
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(Activity.this);
SharedPreferences.Editor editor = pref.edit();
editor.putLong("savedDBTime", lastModified);
editor.commit();
그리고 앱의 다음 과정으로 진행하면 된다.
여기까지가 서버에 있는 DB를 단말에 내려받아서 설치하는 과정이다.
다음으로 내려받은 DB를 어떻게 활용할 수 있는지 간단하게나마 소개하겠다.
그리고 내려받는 도중 3G, WiFi간 전환시 어떻게 대처할 수 있는지 개인적은 의견을 포스팅하겠다.
내용이 이상하거나, 잘못됐거나, 이해가 안되는 부분은 꼭 댓글로 남겨주시라~! 본인이 답변도 드리고, 추후에 방문한 분들께 더 나은 포스트를 제공해드리는데 큰 도움이 된다.
Q&A : lifecluee@gmail.com
Multipart를 이용한 데이터 주고 받기
- 안드로이드에서 서버로 보내기
http://derveljunit.tistory.com/5
jsp에서 db연결 방법
http://derveljunit.tistory.com/entry/JSP%EC%97%90%EC%84%9C-DB-%EC%97%B0%EA%B2%B0-%EB%B0%A9%EB%B2%95
100% 안드로이드에서 이미지 파일 서버로 업로드하기 (소스 공개)
톰캣5.5 + 스트럿츠
지인의 조언.. 그 많은 구글링 예제 소스.. 다 필요 없음(과장해서..ㅎㅎ 지인분들 감사합니다..^^;;).. 한방에 해결..!!
스트럿츠는 FormFile 타입으로 쉽에 업로드 할수 있기에 안드로이드에서 Formfile 타입으로 보내보려고 했었지만.. 실패ㅠ
먼저 안드로이드쪽부터 살펴보면..
갤러리에서 이미지 선택한다음
-> onActivityResult(int requestCode, int resultCode, Intent intent) 에서 받아서 아래와 같이 처리!
Uri selPhotoUri = intent.getData();
//나중에 이미지뷰에 뿌려주기 위해 담아놓음.
Bitmap selPhoto = Images.Media.getBitmap( getContentResolver(), selPhotoUri );
Log.e("전송","시~~작 ~~~~~!");
String urlString = "서버에 전송할 API URL을 넣는다.";
//절대경로를 획득한다!!! 중요~
Cursor c = getContentResolver().query(Uri.parse(selPhotoUri.toString()), null,null,null,null);
c.moveToNext();
String absolutePath = c.getString(c.getColumnIndex(MediaStore.MediaColumns.DATA));
//파일 업로드 시작!
DoFileUpload(urlString , absolutePath);
그럼 이제 DoFileUpload 메소드를 보자.
public void DoFileUpload(String apiUrl, String absolutePath) { HttpFileUpload(apiUrl, "", absolutePath); } |
다음 HttpFileUpload 메소드를 봐야겟져..? 사실 안에 코드 내용은 중요 하지 않아요~ 카피 & 붙여넣기 하세요^-^v
String lineEnd = "\r\n"; String twoHyphens = "--"; String boundary = "*****"; public void HttpFileUpload(String urlString, String params, String fileName) { try { mFileInputStream = new FileInputStream(fileName); connectUrl = new URL(urlString); Log.d("Test", "mFileInputStream is " + mFileInputStream); // open connection HttpURLConnection conn = (HttpURLConnection)connectUrl.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); // write data DataOutputStream dos = new DataOutputStream(conn.getOutputStream()); dos.writeBytes(twoHyphens + boundary + lineEnd); dos.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\";filename=\"" + fileName+"\"" + lineEnd); dos.writeBytes(lineEnd); int bytesAvailable = mFileInputStream.available(); int maxBufferSize = 1024; int bufferSize = Math.min(bytesAvailable, maxBufferSize); byte[] buffer = new byte[bufferSize]; int bytesRead = mFileInputStream.read(buffer, 0, bufferSize); Log.d("Test", "image byte is " + bytesRead); // read image while (bytesRead > 0) { dos.write(buffer, 0, bufferSize); bytesAvailable = mFileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = mFileInputStream.read(buffer, 0, bufferSize); } dos.writeBytes(lineEnd); dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); // close streams Log.e("Test" , "File is written"); mFileInputStream.close(); dos.flush(); // finish upload... // get response int ch; InputStream is = conn.getInputStream(); StringBuffer b =new StringBuffer(); while( ( ch = is.read() ) != -1 ){ b.append( (char)ch ); } String s=b.toString(); Log.e("Test", "result = " + s); mEdityEntry.setText(s); dos.close(); } catch (Exception e) { Log.d("Test", "exception " + e.getMessage()); // TODO: handle exception } } |
이렇게 하면 안드로이드 쪽은 끝~~!! 쉽죠.. 자꾸 이상한 에러들이 나서 난감했음..ㅋ
예를 들면 뭐 스트럿츠에서 request를 두번 받는다느니..서블릿이 포함되서 간다거나, 스트림을 잃는 다거나.. 이런내용이 많았는데.. 에러 상황은 똑같지만 전 그냥 코드를 잘못 입력한듯..ㅋㅋ
자.. 이제 서버쪽..MultipartRequest 는 자주 이용해보셨을껍니다..간단해요.. lib폴더에 cos.jar 넣어 주시고..
import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
소스 안에서는
String folderTypePath = "받는 이미지를 넣을 경로"; int sizeLimit = 5 * 1024 * 1024 ; // 5메가까지 제한 넘어서면 예외발생 MultipartRequest multi = new MultipartRequest(request, folderTypePath, sizeLimit, new DefaultFileRenamePolicy()); Enumeration files = multi.getFileNames(); //파일 정보가 있다면 if(files.hasMoreElements()) { name = (String)files.nextElement(); fileName = multi.getFilesystemName(name); } System.out.println("★★★★★★★★★★★★★★★★★ 이미지 업로드 완료 파일명은? : " + fileName); |
감사합니다;)
by. xranma
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog().build());
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
참고 사이트
http://android-developers.blogspot.kr/2010/12/new-gingerbread-api-strictmode.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+blogspot/hsDu+(Android+Developers+Blog)
2012.10.14 iBluemind님 추가 내용입니다^^ 감사합니다(_ _)
StrictMode라는 API가 안드로이드 진저브레드에서부터 탑재되기 시작했습니다. 그리고, 안드로이드 허니콤부터는 이 StrictMode가 기본적으로 항상 Enable 되게 되었기 때문에, 네트워크 요청이나 디스크 IO와 관련된 처리들은 무조건 UI 스레드와 분리해서 처리해야 하게 되었습니다. 하지만, '꼼수'로 이 StrictMode를 해제하여 네트워크 처리를 UI 스레드에서도 처리할 수 있게끔 할 수 있는 것입니다. 그런데, Seriane님의 댓글에 소개된 코드는 오히려 StrictMode를 Enable하는 코드입니다. 링크해주신 페이지를 보더라도 진저브레드에서 이를 켜는 것이라고 소개되어 있지요. 그래서, 네트워크 처리와 디스크 IO 처리 부분에 대한 StrictMode를 Disable하는 방법은 다음과 같습니다. StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .permitDiskReads() .permitDiskWrites() .permitNetwork().build()); |
파일명으로 리소스 가져오기
http://www.androidpub.com/42875
안녕하세요.
열심히 삽질중인 개발자입니다.;;
이곳에서 항상 도움을 받아오다가
다 알고 계실만한 내용이지만 한 번 남겨보겠습니다.
리소스 접근시 여러가지 방법이 있겠지만
Bitmap testImg = BitmapFactory.decodeResource(res, R.drawable.testRes0);
이런식으로 파일 ID로 리소스를 읽어와 사용하고 있었습니다.
그런데 리소스가 많을 경우
testImg = new Bitmap[10];
for(int i = 0; i < 10; i++)
testImg[i] = BitmapFactory.decodeResource(res, R.drawable.testRes0+i);
이런식으로 ID로 연산을 하여 읽어오다보면 ID가 꼬이는 경우 문제가 발생할 여지가 많더군요.
파일명이 순차적으로 되어 있다고 하더라도 이런식의 접근은 안좋은 방법인것으로 알고 있습니다.
이럴경우
int tmpID;
testImg = new Bitmap[10];
for(int i = 0; i < 10; i++)
{
tmpID = res.getIdentifier( "testRes"+i, "drawable" , "com.androidpub.android.test");
testImg[i] = BitmapFactory.decodeResource(res, tmpID);
}
이런식으로 리소스 파일명으로 ID를 알아와 접근하는 방식을 사용할 수 있습니다.
"패키지명 : 타입 / 리소스명", null, null
getIdentifier("com.androidpub.android.test:drawable/testRes", null, null);
또는
"리소스명", "타입", "패키지명"
getIdentifier("testRes", "drawable", "com.androidpub.android.test");
'차근차근 > Android' 카테고리의 다른 글
app.initialize() (0) | 2016.02.01 |
---|---|
[ study ] 다이어리 어플 만들어보기 2 - 부가설명7 (0) | 2015.05.07 |
[ study ] 다이어리 어플 만들어보기 2 - 부가설명5 (0) | 2015.05.07 |
[ study ] 다이어리 어플 만들어보기 2 (0) | 2015.05.06 |
listview + checkbox (0) | 2015.05.06 |
'차근차근/Android'의 다른글
- 현재글[ study ] 다이어리 어플 만들어보기 2 - 부가설명6