웹서버를 통한 파일 자동 업데이트
아래 내용은 안드로이드 6.x 까지만 적용되는 예제입니다.
안드로이드 7.x 부터는 다른 예제를 찾아보세요! ㅜ.ㅜ
참고 : http://duongame.tistory.com/263 (Android 7.0 APK 파일 설치)
웹서버 설정
1. 파일명 : application.properties
#messages for udpate versionCode=9 versionName=1.1 fileName=TraMainActivity.apk message=\ufffd\ufffd\u022d\ufffd\ufffd\ufffd\ufffd\u05f9\u6e6e\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd \ufffd\u05b5\ufffd\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd\u03ff\ufffd\ufffd\ufffd\ufffd\u03f4\ufffd. \ufffd\ufffd\u022d\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd\u05b5\ufffd\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0534\u03f4\ufffd. title=\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u01ae\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd\u0574\u03f4\ufffd. |
message 에서 줄바꿈 하려면 한줄로 적되, 개행할 곳에 \r\n 를 사용하면 된다.
2. 원문파일명 : application.properties.source.txt
#messages for udpate versionCode=2 versionName=1.12 fileName=TraMainActivity.apk message=통화견적및방문견적을 볼수 있도록 수정하였습니다. 정화조 도면을 볼수있도록 수정중입니다. title=업데이트를 시작합니다. |
기존에 설치된 app 의 AndroidManifest.xml 과 웹서버에 있는 application.properties 의 versionCode 를 비교해서
웹서버 versionCode 값이 더 클 경우 앱에서 팝업으로 알려주게 된다. (최대값이
2147483647
이다.)
versionName 은 필요시 사용자에게 보여주기 위한 번호일 뿐이다. (비교 안함)
AndroidManifest.xml 파일의 옵션값이 먹히지 않을 경우 (AndroidStudio를 사용할 경우) build.gradle (Module: app) 내의 옵션이 우선되기 때문이다.
3. 파일 형식 변환
원문 파일을 jdk 폴더 내의 bin폴더에 있는 native2ascii.exe 명령으로 바꾼것이다.
native2ascii.exe application.properties.source.txt application.properties
[사용 방법]
-- versionCode 와 앱의 manifest 파일의 versionCode와 일치시키면 된다. versionName도 마찬가지로 일치시키면 된다.
-- fileName은 앱의 파일 이름을 적어주면된다.
-- message에는 업데이트 된 내용을 적어주면 된다.
-- title은 다운로드 받는 progress dialog의 제목이다.
설정 파일 작성을 완료 했다면 설정 파일과 앱파일을 서버상에 같은 폴더에 업로드 한다.
아래 소스에서는 웹서버의 /download/ 디렉토리에 업로드 하도록 하였다.
필수 권한
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
AutoupdateActivity.java
package com.neulwon.study; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; public class AutoupdateActivity extends Activity { //새버전의 프로그램이 존재하는지 여부 private int newver=0; private int oldver=0; private String strVer; private String fileName; private CharSequence updateMessage; private String updateTitle; // Progress Dialog private TextView textVersion; private TextView progressTitle; private TextView progressText; private ProgressBar progressBar; private RelativeLayout downloadUpdateLayout; //확인하고 싶은 패키지명 String private static final String CHECK_PACKAGE_NAME="com.neulwon.study"; public static final String MSG_TAG = "AutoupdateActivity"; private static final String IP_ADDRESS = "sysdocu.tistory.com"; // Update Url private static final String APPLICATION_PROPERTIES_URL = "http://" + IP_ADDRESS + "/download/application.properties"; private static final String APPLICATION_DOWNLOAD_URL = "http://" + IP_ADDRESS + "/download/"; public static final int MESSAGE_DOWNLOAD_STARTING = 3; public static final int MESSAGE_DOWNLOAD_PROGRESS = 4; public static final int MESSAGE_DOWNLOAD_COMPLETE = 5; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_autoupdate); progressBar = (ProgressBar) findViewById(R.id.progressBar); progressText = (TextView) findViewById(R.id.progressText); progressTitle = (TextView) findViewById(R.id.progressTitle); downloadUpdateLayout = (RelativeLayout) findViewById(R.id.layoutDownloadUpdate); textVersion = (TextView) findViewById(R.id.textVersion ); // 앱실행시 기존 다운로드 파일 삭제 String del_localpath = getDownloadDirectory(); File del_file = new File(del_localpath + "/update-debug.apk"); del_file.delete(); // 업데이트 체크 checkForUpdate(); PackageInfo oldversionInfo; try { oldversionInfo = getPackageManager().getPackageInfo(CHECK_PACKAGE_NAME,PackageManager.GET_META_DATA); strVer = oldversionInfo.versionName; } catch (NameNotFoundException e1) { e1.printStackTrace(); } //textVersion.setText( "버전 " + strVer ); textVersion.setText( "IPTV 업데이트" ); } //앱 업데이트 검사 public void checkForUpdate() { new Thread(new Runnable(){ public void run(){ Looper.prepare(); // 서버상의 Properties 얻기 Properties updateProperties = queryForProperty(APPLICATION_PROPERTIES_URL); String verName; if ( updateProperties != null && updateProperties.containsKey("versionCode") ) { int newversion = Integer.parseInt(updateProperties.getProperty("versionCode")); //int installedVersion = TetherApplication.this.getVersionNumber(); fileName = updateProperties.getProperty("fileName", ""); updateMessage = updateProperties.getProperty("message", ""); updateTitle = updateProperties.getProperty("title", "업데이트가 가능합니다."); verName = updateProperties.getProperty("versionName", ""); newver = newversion; try { //설치된 앱 정보 얻기 PackageInfo oldversionInfo = getPackageManager().getPackageInfo(CHECK_PACKAGE_NAME,PackageManager.GET_META_DATA); oldver = Integer.valueOf(oldversionInfo.versionCode); //다운로드 폴더 얻어오기 String localpath = getDownloadDirectory(); Log.d("앱버전","앱버전 : " + oldver ); Log.d("서버상","서버에 있는 파일 버전 : " + newver ); Log.d("받아온경로","받아온 경로: " + localpath); if ( oldver < newver ) { //파일 버전비교 openUpdateDialog( APPLICATION_DOWNLOAD_URL + fileName , fileName , updateMessage , updateTitle,localpath); } else { Log.d(MSG_TAG, " 최신버전입니다. 버전 : " + verName ); } //textVersion.setText(" Ver : " + verName); } catch ( Exception e ) { e.printStackTrace(); } } Looper.loop(); } }).start(); } //다운로드 폴더 얻기 private String getDownloadDirectory(){ String sdcardPath=""; String downloadpath = ""; if ( isUsableSDCard(true)){ //외장메모리 사용가능할 경우 sdcardPath = Environment.getExternalStorageDirectory().getPath(); //downloadpath = sdcardPath + "/download/"; downloadpath = sdcardPath + "/data/local/tmp/"; } else { //내장메모리 위치 File file = Environment.getRootDirectory(); sdcardPath = file.getAbsolutePath(); //downloadpath = sdcardPath + "/download"; downloadpath = sdcardPath + "/data/local/tmp"; } return downloadpath; } //외장메모리 사용 가능여부 확인 private boolean isUsableSDCard(boolean requireWriteAccess) { String state = Environment.getExternalStorageState(); if ( Environment.MEDIA_MOUNTED.equals(state)) { return true; } else if( !requireWriteAccess && Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) { return true; } return false; } //서버의 application.properties 파일 읽어오기 public Properties queryForProperty(String url) { Properties properties = null; HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(String.format(url)); try { HttpResponse response = client.execute(request); StatusLine status = response.getStatusLine(); if ( status.getStatusCode() == 200 ) { HttpEntity entity = response.getEntity(); properties = new Properties(); properties.load(entity.getContent()); } } catch ( IOException e ) { Log.d("오류","Can't get property '" + url + "'."); } return properties; } //업데이트 할 것인지 확인 public void openUpdateDialog(final String downloadFileUrl, final String fileName, final CharSequence message, final String updateTitle,final String localpath4down) { LayoutInflater li = LayoutInflater.from(this); Builder dialog; View view; view = li.inflate(R.layout.updateview, null); TextView messageView = (TextView) view.findViewById(R.id.updateMessage); TextView updateNowText = (TextView) view.findViewById(R.id.updateNowText); if (fileName.length() == 0) // No filename, hide 'download now?' string updateNowText.setVisibility(View.GONE); messageView.setText(message); dialog = new AlertDialog.Builder(AutoupdateActivity.this) .setTitle(updateTitle) .setView(view); if (fileName.length() > 0) { //dialog.setNeutralButton("취소", new DialogInterface.OnClickListener() { // public void onClick(DialogInterface dialog, int whichButton) { // Log.d(MSG_TAG, "No pressed"); // } //}); dialog.setNegativeButton("확인", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Log.d(MSG_TAG, "Yes pressed"); Log.d("경로명","경로명 : " + downloadFileUrl + " 파일명 : " + fileName ); downloadUpdate(downloadFileUrl, fileName, localpath4down); } }); } else { dialog.setNeutralButton("확인",new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Log.d(MSG_TAG, "Ok pressed"); } }); } dialog.show(); //dialog.setCancelable(false); // back 키 무효. 동작 안함 //dialog.setCanceledOnTouchOutside(false); // dialog 바깥 클릭 무효. 동작 안함 } //다운로드 받은 앱을 설치, 이전 실행 앱 종료 public void downloadUpdate(final String downloadFileUrl, final String fileName,final String localpath) { new Thread(new Runnable(){ public void run(){ Message msg = Message.obtain(); msg.what = MESSAGE_DOWNLOAD_STARTING; msg.obj = localpath + fileName ; File apkFile = new File ( localpath + fileName ); Log.d("downloadUpdate","경로1:"+ localpath + fileName ); viewUpdateHandler.sendMessage(msg); downloadUpdateFile(downloadFileUrl, fileName, localpath); //다운로드 받은 패키지를 인스톨한다. Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(apkFile),"application/vnd.android.package-archive"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); /* * 안드로이드 프로세스는 단지 finish() 만 호출 하면 죽지 않는다. * 만약 프로세스를 강제로 Kill 하기위해서는 화면에 떠있는 Activity를 BackGround로 보내고 * 강제로 Kill하면 프로세스가 완전히 종료가 된다. * 종료 방법에 대한 Source는 아래 부분을 참조 하면 될것 같다. */ moveTaskToBack(true); finish(); android.os.Process.sendSignal(android.os.Process.myPid(), android.os.Process.SIGNAL_KILL); } }).start(); } public Handler viewUpdateHandler = new Handler(){ public void handleMessage(Message msg) { switch(msg.what) { case MESSAGE_DOWNLOAD_STARTING : Log.d(MSG_TAG, "프로그레스바 시작"); progressBar.setIndeterminate(true); progressTitle.setText((String)msg.obj + " 다운로드"); progressText.setText("시작중..."); downloadUpdateLayout.setVisibility(View.VISIBLE); break; case MESSAGE_DOWNLOAD_PROGRESS : progressBar.setIndeterminate(false); progressText.setText(msg.arg1 + "k /" + msg.arg2 + "k"); progressTitle.setText("최신 버전을 다운로드 하고 있습니다."); progressBar.setProgress(msg.arg1*100/msg.arg2); break; case MESSAGE_DOWNLOAD_COMPLETE : Log.d(MSG_TAG, "다운로드 완료."); progressText.setText(""); progressTitle.setText(""); downloadUpdateLayout.setVisibility(View.GONE); break; } super.handleMessage(msg); } }; public boolean downloadUpdateFile(String downloadFileUrl, String destinationFilename, String localPath) { if (Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED) == false) { return false; } File downloadDir = new File( localPath ); Log.d("DOWNLOAD","다운로드중"); if (downloadDir.exists() == false) { downloadDir.mkdirs(); } else { File downloadFile = new File( localPath + destinationFilename ); if (downloadFile.exists()) { downloadFile.delete(); } } return this.downloadFile(downloadFileUrl, localPath, destinationFilename ); } //파일 다운로드 과정 표시 public boolean downloadFile(String url, String destinationDirectory, String destinationFilename ) { boolean filedownloaded = true; HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(String.format(url)); Message msg = Message.obtain(); try { HttpResponse response = client.execute(request); StatusLine status = response.getStatusLine(); Log.d(MSG_TAG, "Request returned status " + status); if (status.getStatusCode() == 200) { HttpEntity entity = response.getEntity(); InputStream instream = entity.getContent(); int fileSize = (int)entity.getContentLength(); FileOutputStream out = new FileOutputStream(new File(destinationDirectory + destinationFilename)); byte buf[] = new byte[8192]; int len; int totalRead = 0; while((len = instream.read(buf)) > 0) { msg = Message.obtain(); msg.what = MESSAGE_DOWNLOAD_PROGRESS; totalRead += len; msg.arg1 = totalRead / 1024; msg.arg2 = fileSize / 1024; viewUpdateHandler.sendMessage(msg); out.write(buf,0,len); } out.close(); } else { throw new IOException(); } } catch (IOException e) { filedownloaded = false; } msg = Message.obtain(); msg.what = MESSAGE_DOWNLOAD_COMPLETE; viewUpdateHandler.sendMessage(msg); return filedownloaded; } } |
activity_autoupdate.xml
<FrameLayout 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:layout_gravity="center" > <TextView android:id="@+id/textVersion" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Version" android:textSize="14sp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical"> <RelativeLayout android:id="@+id/layoutDownloadUpdate" android:layout_width="match_parent" android:layout_height="60dp" android:visibility="gone" android:layout_gravity="center_horizontal" android:gravity="center" android:background="#A0909090"> <TextView android:id="@+id/progressTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:paddingLeft="5dp" android:paddingRight="5dp" android:text="" android:textStyle="bold" android:textColor="#000000" /> <ProgressBar android:id="@+id/progressBar" android:layout_width="match_parent" android:layout_height="wrap_content" style="?android:attr/progressBarStyleHorizontal" android:paddingTop="22dp" android:paddingLeft="5dp" android:paddingRight="5dp" android:layout_alignParentTop="true" android:max="100" /> <TextView android:id="@+id/progressText" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="40dp" android:paddingLeft="5dp" android:paddingRight="5dp" android:layout_alignParentTop="true" android:text="" android:textStyle="bold" android:textColor="#000000" /> </RelativeLayout> </LinearLayout> </FrameLayout> |
updateview.xml (dialog 창)
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/updateMessage" android:layout_width="match_parent" android:layout_height="match_parent" android:autoLink="web" android:paddingLeft="15dp" android:paddingRight="15dp" android:paddingTop="15dp" android:text="" android:textColor="#00ffff" android:textStyle="bold" /> <TextView android:text="Update" android:id="@+id/updateNowText" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="15dp" android:paddingLeft="15dp" android:paddingRight="15dp" android:textStyle="bold" android:textColor="#000000" /> </LinearLayout> </ScrollView> |
[출처] http://blog.daum.net/wonky12/1401710
'프로그래밍 > Android (Java)' 카테고리의 다른 글
Android 개발환경 구성 (ver 2018-03-21) (0) | 2018.03.21 |
---|---|
안드로이드 음성 인식 기능 (STT) (0) | 2017.10.10 |
네비게이션바 (navigation bar) 상태 확인 (0) | 2017.09.14 |
웹페이지 텍스트 내용 가져와서 출력하기 (0) | 2017.05.22 |
지연 실행 (delay 그리고 재호출시 연기하기) (0) | 2016.12.14 |