AsyncTask 예제를 작성하기 위해 프로그램 작성하기 시작했는데 필요한 기능들을 하나씩 계속 넣다보니 여러 가지가 포함되어 버렸습니다. 최종적으로 완성된 앱은 URL 주소로부터 동영상 다운로드 후 재생시켜 주는 작업을 합니다. 포스팅에 포함된 코드에는 다음 내용들이 추가되어있습니다. 주석을 추가해놓긴 했지만 복잡해보여서 추후 다음 항목별로 따로 포스팅을 작성하여 코드 설명을 추가하도록 하겠습니다.
안드로이드 6.0 마시멜로( API 23 )이상에서 런타임 퍼미션(Runtime Permission) 처리
AsyncTask 사용
URLConnection, InputStream, OutputStream을 이용하여 URL 주소에 있는 동영상 파일 다운로드 후, 로컬에 저장하기
PowerManager.WakeLock를 사용하여 다운로드 중 전원버튼을 누르더라도 CPU가 잠들지 않도록 처리
AsyncTask를 사용하여 파일 다운로드 시 ProgressDialog를 이용하여 다운로드 상태 표시
Intent를 사용하여 로컬에 저장된 동영상을 설치되어 있는 플레이어 앱에서 플레이 시키기
AlertDialog를 사용하여 사용자에게 물어보기
본 포스팅의 코드를 설명하기 위해 추가로 작성한 포스팅입니다.
[Android/개념 및 예제] - 안드로이드 개념 및 예제 - AsyncTask
앱 실행과정
프로그램 코드
앱 실행과정
안드로이드 6.0 마시멜로( API 23 )에서만 앱 태스트가 이루어졌습니다.
처음 앱을 실행시키면 퍼미션 허용를 요청합니다. 인터넷에서 다운받은 파일을 SD카드에 저장 후, 다시 SD 카드에서 읽어오기 위해선 퍼미션이 필요하기 때문입니다.
기존에는 AndroidManifests.xml에만 퍼미션을 추가하면 되었지만 안드로이드 6.0 마시멜로( API 23 )부턴 런타임 퍼미션(Runtime Permission)이 추가되어씁니다. 그래서 앱을 처음 실행할 때 추가로 퍼미션 허가 화면을 보여주며 사용자에게 물어봅니다.
여기서 퍼미션 허용을 거부해보면
다시 한번 퍼미션이 있어야 앱이 실행가능하다고 물어봅니다. 여기서 아니오를 선택하면 앱을 종료하도록 되고 예를 선택하면 다시한번 퍼미션 허가를 요청하게 됩니다.
이번엔 퍼미션 요청을 허용해봅니다.
이제 앱 초기 화면이 보입니다. 다운로드 버튼을 누르게 되면
다운로드가 실행되고, 진행상태를 다이어로그로 보여줍니다.
다운로드 완료시 동영상을 플레이시킬 앱들의 목록이 보입니다. 하나를 선택하면
선택한 앱에 의해서 동영상이 플레이 됩니다. 동영상 재생 중 뒤로가기를 누르면
초기 상태로 돌아옵니다. 다시 다운로드를 선택하면
이미 SD카드에 다운로드 받은 파일이 있다고 다시 받을지 물어봅니다. 예를 선택하면 기존 파일을 삭제하고 다시 다운로드가 시작되며, 아니오를 선택하게 되면
기존에 다운로드 받았던 동영상 파일을 플레이하기 위한 앱들의 목록이 보입니다.
선택한 앱에 의해서 동영상이 플레이 되게 됩니다.
프로그램 코드
AndroidManifests.xml
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 | <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.tistory.webnautes.asynctask_example"> <!-- 인터넷 연결 허용하는 퍼미션 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- SD카드 기록 허용하는 퍼미션 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- SD카드 읽기 허용하는 퍼미션, 킷캣이후로는 필요없음 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- CPU 상태 유지 및 화면 꺼짐 제어를 위한 퍼미션 --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest> | cs |
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:text="다운로드" /> </LinearLayout> | cs |
MainActivity.java
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | package com.tistory.webnautes.asynctask_example; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.os.PowerManager; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; public class MainActivity extends AppCompatActivity { private ProgressDialog progressBar; static final int PERMISSION_REQUEST_CODE = 1; String[] PERMISSIONS = {"android.permission.READ_EXTERNAL_STORAGE","android.permission.WRITE_EXTERNAL_STORAGE"}; private File outputFile; //파일명까지 포함한 경로 private File path;//디렉토리경로 private boolean hasPermissions(String[] permissions) { int res = 0; //스트링 배열에 있는 퍼미션들의 허가 상태 여부 확인 for (String perms : permissions){ res = checkCallingOrSelfPermission(perms); if (!(res == PackageManager.PERMISSION_GRANTED)){ //퍼미션 허가 안된 경우 return false; } } //퍼미션이 허가된 경우 return true; } private void requestNecessaryPermissions(String[] permissions) { //마시멜로( API 23 )이상에서 런타임 퍼미션(Runtime Permission) 요청 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(permissions, PERMISSION_REQUEST_CODE); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (!hasPermissions(PERMISSIONS)) { //퍼미션 허가를 했었는지 여부를 확인 requestNecessaryPermissions(PERMISSIONS);//퍼미션 허가안되어 있다면 사용자에게 요청 } else { //이미 사용자에게 퍼미션 허가를 받음. } progressBar=new ProgressDialog(MainActivity.this); progressBar.setMessage("다운로드중"); progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressBar.setIndeterminate(true); progressBar.setCancelable(true); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { //1 //웹브라우저에 아래 링크를 입력하면 Alight.avi 파일이 다운로드됨. final String fileURL = "http://webnautes.tistory.com/attachment/cfile4.uf@267BB53E58451C582BD045.avi"; path= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); outputFile= new File(path, "Alight.avi"); //파일명까지 포함함 경로의 File 객체 생성 if (outputFile.exists()) { //이미 다운로드 되어 있는 경우 AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("파일 다운로드"); builder.setMessage("이미 SD 카드에 존재합니다. 다시 다운로드 받을까요?"); builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Toast.makeText(getApplicationContext(),"기존 파일을 플레이합니다.",Toast.LENGTH_LONG).show(); playVideo(outputFile.getPath()); } }); builder.setPositiveButton("예", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { outputFile.delete(); //파일 삭제 final DownloadFilesTask downloadTask = new DownloadFilesTask(MainActivity.this); downloadTask.execute(fileURL); progressBar.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { downloadTask.cancel(true); } }); } }); builder.show(); } else { //새로 다운로드 받는 경우 final DownloadFilesTask downloadTask = new DownloadFilesTask(MainActivity.this); downloadTask.execute(fileURL); progressBar.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { downloadTask.cancel(true); } }); } } }); } private class DownloadFilesTask extends AsyncTask<String, String, Long> { private Context context; private PowerManager.WakeLock mWakeLock; public DownloadFilesTask(Context context) { this.context = context; } //파일 다운로드를 시작하기 전에 프로그레스바를 화면에 보여줍니다. @Override protected void onPreExecute() { //2 super.onPreExecute(); //사용자가 다운로드 중 파워 버튼을 누르더라도 CPU가 잠들지 않도록 해서 //다시 파워버튼 누르면 그동안 다운로드가 진행되고 있게 됩니다. PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); mWakeLock.acquire(); progressBar.show(); } //파일 다운로드를 진행합니다. @Override protected Long doInBackground(String... string_url) { //3 int count; long FileSize = -1; InputStream input = null; OutputStream output = null; URLConnection connection = null; try { URL url = new URL(string_url[0]); connection = url.openConnection(); connection.connect(); //파일 크기를 가져옴 FileSize = connection.getContentLength(); //URL 주소로부터 파일다운로드하기 위한 input stream input = new BufferedInputStream(url.openStream(), 8192); path= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); outputFile= new File(path, "Alight.avi"); //파일명까지 포함함 경로의 File 객체 생성 // SD카드에 저장하기 위한 Output stream output = new FileOutputStream(outputFile); byte data[] = new byte[1024]; long downloadedSize = 0; while ((count = input.read(data)) != -1) { //사용자가 BACK 버튼 누르면 취소가능 if (isCancelled()) { input.close(); return Long.valueOf(-1); } downloadedSize += count; if (FileSize > 0) { float per = ((float)downloadedSize/FileSize) * 100; String str = "Downloaded " + downloadedSize + "KB / " + FileSize + "KB (" + (int)per + "%)"; publishProgress("" + (int) ((downloadedSize * 100) / FileSize), str); } //파일에 데이터를 기록합니다. output.write(data, 0, count); } // Flush output output.flush(); // Close streams output.close(); input.close(); } catch (Exception e) { Log.e("Error: ", e.getMessage()); }finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException ignored) { } mWakeLock.release(); } return FileSize; } //다운로드 중 프로그레스바 업데이트 @Override protected void onProgressUpdate(String... progress) { //4 super.onProgressUpdate(progress); // if we get here, length is known, now set indeterminate to false progressBar.setIndeterminate(false); progressBar.setMax(100); progressBar.setProgress(Integer.parseInt(progress[0])); progressBar.setMessage(progress[1]); } //파일 다운로드 완료 후 @Override protected void onPostExecute(Long size) { //5 super.onPostExecute(size); progressBar.dismiss(); if ( size > 0) { Toast.makeText(getApplicationContext(), "다운로드 완료되었습니다. 파일 크기=" + size.toString(), Toast.LENGTH_LONG).show(); Intent mediaScanIntent = new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); mediaScanIntent.setData(Uri.fromFile(outputFile)); sendBroadcast(mediaScanIntent); playVideo(outputFile.getPath()); } else Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show(); } } @Override public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){ switch(permsRequestCode){ case PERMISSION_REQUEST_CODE: if (grantResults.length > 0) { boolean readAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED; boolean writeAccepted = grantResults[1] == PackageManager.PERMISSION_GRANTED; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if ( !readAccepted || !writeAccepted ) { showDialogforPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다."); return; } } } break; } } private void showDialogforPermission(String msg) { final AlertDialog.Builder myDialog = new AlertDialog.Builder( MainActivity.this); myDialog.setTitle("알림"); myDialog.setMessage(msg); myDialog.setCancelable(false); myDialog.setPositiveButton("예", new DialogInterface.OnClickListener() { public void onClick(DialogInterface arg0, int arg1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(PERMISSIONS, PERMISSION_REQUEST_CODE); } } }); myDialog.setNegativeButton("아니오", new DialogInterface.OnClickListener() { public void onClick(DialogInterface arg0, int arg1) { finish(); } }); myDialog.show(); } private void playVideo(String path) { Uri videoUri = Uri.fromFile(new File(path)); Intent videoIntent = new Intent(Intent.ACTION_VIEW); videoIntent.setDataAndType(videoUri, "video/*"); if (videoIntent.resolveActivity(getPackageManager()) != null) { startActivity(Intent.createChooser(videoIntent, null)); } } } | cs |
'Android > 개념 및 예제' 카테고리의 다른 글
안드로이드 개념 및 예제 - Fragment (9) | 2016.12.19 |
---|---|
안드로이드 개념 및 예제 - AsyncTask (14) | 2016.12.07 |
안드로이드 - 버튼 클릭 시 ImageView의 이미지 변경하기 (0) | 2016.10.26 |
Android - Spinner 간단한 예제 (3) | 2016.08.09 |
Android - ListView 간단한 예제 (0) | 2016.08.09 |
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!