안드로이드 2.3에 추가된 DownloadManager를 사용하는 방법에 대해 적어보았습니다.
블로그에 적은 글을 옮겨봅니다 :)

-----

많은 용량의 데이터를 필요로 하는 애플리케이션 혹은 주기적으로 데이터를 업데이트 할 필요가 있는 애플리케이션들은 인터넷에서 데이터를 직접 다운로드하는 기능을 포함해야 합니다. 하지만, 데이터를 다운로드 하는 기능을 구현하는 것이 그렇게 만만한 작업은 아니였습니다.

UI 쓰레드의 정지를 방지하기 위해 다운로드 작업은 별도 쓰레드에서 구현해야 했으며, 사용자에게 다운로드 현황을 알려주기 위해 ProgressBar 혹은 ProgressDialog를 사용해야 했습니다. 게다가 일부 코드는 애플리케이션 생애주기를 고려하지 않고 작성하여 사용자의 예기치 않은 동작 때문에 오류가 발생하기도 쉬웠습니다.

안드로이드 2.3에서는 이러한 불편을 해소하기 위해 파일 다운로드를 편하게 해주는 DownloadManager 클래스를 제공합니다.

DownloadManager 클래스의 구성

DownloadManager는 크게 다음과 같은 세가지 클래스로 구성되어 있습니다.

  • DownloadManager : 다운로드 시작/취소, 다운로드 파일 열기, 다운로드한 항목 조회 메서드 제공
  • DownloadManager.Request : 다운로드 시작에 필요한 정보 포함
  • DownloadManager.Query : 다운로드 항목 조회에 필요한 정보 포함


DownloadManager

파일 다운로드와 관련된 모든 동작을 담당합니다. DownloadManager 클래스에서 사용 가능한 메서드는 다음과 같습니다.

API
public long enqueue (DownloadManager.Request request)
새로운 다운로드 작업을 요청합니다. 요청한 작업은 다운로드 대기열에 추가됩니다. 다운로드 대기열에 추가된 작업의 ID를 반환합니다.

public int remove (long... ids)
요청한 다운로드 작업을 취소합니다.

public Cursor query (DownloadManager.Query query)
DownloadManager를 통해 다운로드한 항목들 중 조건에 일치하는 항목을 검색합니다. 결과는 Cursor 형태로 반환됩니다. 결과 레코드의 값을 참조하기 위한 필드 값은 DownloadManager.COLUMN_으로 시작하는 상수들(예:DownloadManager.COLUMN_TITLE)을 사용하여 얻을 수 있습니다.

public ParcelFileDescriptor openDownloadedFile (long id)
파일 처리를 위해 다운로드한 파일을 엽니다. 인자로 다운로드 항목의 고유ID를 넘겨줍니다.



DownloadManager.Request

DownloadManager에 다운로드 요청을 하기 위해 필요한 정보를 포함합니다. 다운로드할 파일의 주소(URI), 파일 저장 경로, 다운로드 항목의 제목 및 설명을 설정할 수 있습니다. DownloadManager.Request의 주요 메서드는 다음과 같습니다.

API
public DownloadManager.Request (Uri uri)
DownloadManager.Request의 생성자입니다. 인자로 다운로드할 파일의 URI를 지정합니다. HTTP 형식만 지원합니다.

public DownloadManager.Request setAllowedNetworkTypes (int flags)
다운로드 진행시 사용 가능한 네트워크 종류를 설정합니다. 모바일 네트워크(3G)와 Wi-Fi 중 하나를 선택할 수 있습니다. 대용량 파일 다운로드시 실수로 3G 네트워크를 사용하여 데이터를 다운로드하여 많은 요금이 부과되는 것을 방지하기 위한 목적으로 사용할 수 있습니다. 사용 가능한 네트워크를 명시적으로 지정하지 않으면 모든 네트워크에서 다운로드를 수행할 수 있습니다.

public DownloadManager.Request setTitle (CharSequence title)
다운로드 항목에 표시될 제목을 지정합니다.

public DownloadManager.Request setDescription (CharSequence description)
다운로드 항목에 표시될 세부 설명을 지정합니다.

public DownloadManager.Request setDestinationUri (Uri uri)
다운로드할 파일이 저장될 경로를 지정합니다. 외부 미디어(예:SD카드)의 경로여야 하며, 외부 미디어에 데이터를 쓰기 위해 WRITE_EXTERNAL_STORAGE 권한을 지정해야 합니다.



DownloadManager.Query

다운로드 항목에서 원하는 정보만을 조회하기 위한 필터를 설정합니다.

API
public DownloadManager.Query ()
모든 다운로드 항목을 조회하는 객체를 생성합니다.

public DownloadManager.Query setFilterById (long... ids)
지정된 id에 해당하는 다운로드 항목만을 조회하기 위한 필터를 설정합니다.

public DownloadManager.Query setFilterByStatus (int flags)
특정 다운로드 상태의 다운로드 항목을 조회하기 위한 필터를 설정합니다. 


DownloadManager를 사용하는 예제를 통해 DownloadManager 클래스를 사용하는 방법에 대해 자세히 알아보겠습니다.

[어플리케이션 정보]

액티비티
  • Main (Main.java)

레이아웃
  • main.xml

권한 (uses-permission)
  • android.permission.INTERNET
  • android.permission.WRITE_EXTERNAL_STORAGE

API Level
  • 9 (Android 2.3)

예제 코드 :

예제 애플리케이션에서는 다운로드할 파일의 주소를 입력받아 해당 파일을 다운로드하는 기능과 마지막으로 다운로드를 요청한 파일의 다운로드를 취소하는 기능, 다운로드한 항목을 조회하는 기능을 제공합니다. 예제 애플리케이션의 완성된 모습은 다음과 같습니다. EditText에 다운로드할 파일의 URL을 입력하도록 되어있으며, 각 버튼을 눌러 필요한 작업을 수행할 수 있습니다.


예제 애플리케이션의 레이아웃을 다음과 같이 작성합니다.

[main.xml]
01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout
04.android:orientation="vertical"
05.android:layout_width="fill_parent"
06.android:layout_height="fill_parent">
07. 
08.<EditText
09.android:layout_height="wrap_content"
10.android:layout_width="fill_parent"
11.android:hint="Enter URL"
12.android:id="@+id/downloadUrl"
13.android:inputType="textUri" />
14.<Button
15.android:layout_width="wrap_content"
16.android:layout_height="wrap_content"
17.android:id="@+id/addQueueButton"
18.android:text="Add to download queue" />
19.<Button
20.android:layout_width="wrap_content"
21.android:layout_height="wrap_content"
22.android:text="Cancel latest download"
23.android:id="@+id/cancelDownloadButton" />
24.<Button
25.android:layout_width="wrap_content"
26.android:layout_height="wrap_content"
27.android:text="View downloads"
28.android:id="@+id/viewDownloadsButton" />
29. 
30.</LinearLayout>

다음, 액티비티 코드를 작성합니다. 위젯 인스턴스 및 DownloadManager, DownloadManager.Request, Uri의 인스턴스를 선언합니다.

[Main.java]
01.public class Main extends Activity implements OnClickListener{
02.private EditText downloadUrl;
03.private Button addToQueueButton;
04.private Button cancelLatestButton;
05.private Button viewDownloadsButton;
06. 
07.private long latestId = -1;
08. 
09.private DownloadManager downloadManager;
10.private DownloadManager.Request request;
11.private Uri urlToDownload;

다음, 다운로드가 완료를 알려줄 브로드캐스트 리시버를 생성합니다. DownloadManager는 다운로드가 완료되었을 때 DownloadManager.ACTION_DOWNLOAD_COMPLETE 를 액션으로 갖는 브로드캐스트 메시지를 보내며, 이를 처리하는 브로드캐스트 리시버를 선언하면 특정 항목이 모두 다운로드 되었을 때 수행할 동작을 구현할 수 있습니다.

1.private BroadcastReceiver completeReceiver = new BroadcastReceiver(){
2. 
3.@Override
4.public void onReceive(Context context, Intent intent) {
5.Toast.makeText(context, "다운로드가 완료되었습니다.",Toast.LENGTH_SHORT).show();
6.}
7. 
8.};

이제 액티비티 코드를 구현하겠습니다. onCreate() 에서 다음과 같이 각 인스턴스의 객체를 받고, 버튼의 리스너를 등록하는 작업을 수행합니다.

01.@Override
02.public void onCreate(Bundle savedInstanceState) {
03.super.onCreate(savedInstanceState);
04.setContentView(R.layout.main);
05. 
06.downloadUrl = (EditText)findViewById(R.id.downloadUrl);
07.addToQueueButton = (Button)findViewById(R.id.addQueueButton);
08.cancelLatestButton = (Button)findViewById(R.id.cancelDownloadButton);
09.viewDownloadsButton = (Button)findViewById(R.id.viewDownloadsButton);
10. 
11.downloadManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
12. 
13.addToQueueButton.setOnClickListener(this);
14.cancelLatestButton.setOnClickListener(this);
15.viewDownloadsButton.setOnClickListener(this);
16.}

다음, onPause()에서 다운로드 완료시 발송되는 브로드캐스트 메시지를 처리하는 브로드캐스트 리시버를 등록합니다. 

1.@Override
2.public void onResume(){
3.super.onResume();
4.IntentFilter completeFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
5.registerReceiver(completeReceiver, completeFilter);
6.}

이제 각 버튼을 눌렀을때 처리할 작업을 구현할 차례입니다. onClick() 메서드에서 각 버튼에서 수행할 작업을 구현합니다. 다운로드 버튼을 누르면 EditText에 입력한 URL을 가져와 DownloadManager.Request를 생성한 후, 제목과 설명, 그리고 다운로드한 파일을 저장할 경로를 지정한 후에 DownloadManager에 다운로드를 요청합니다. 예제에서는 외장 메모리(SD카드)에 저장하도록 구현하였습니다. 

예제와 같이 외장 메모리의 공용 다운로드 영역에 데이터를 저장하려면 데이터 저장 전에 해당 디렉터리가 생성되어 있는지 필히 확인해야 하며, 이를 위해 mkdirs() 메서드를 사용하여 디렉토리가 존재하지 않을 경우 디렉토리를 생성하도록 구현합니다.

다운로드 취소 버튼을 누르면 마지막으로 요청한 다운로드를 취소하며, View Downloads 버튼을 누르면 다운로드 현황을 보여주는 액티비티를 표시하도록 구현하였습니다.

01.@Override
02.public void onClick(View v) {
03.switch(v.getId()){
04.case R.id.addQueueButton:
05.urlToDownload = Uri.parse(downloadUrl.getText().toString());
06.List<String> pathSegments = urlToDownload.getPathSegments();
07.request = new DownloadManager.Request(urlToDownload);
08.request.setTitle("다운로드 예제");
09.request.setDescription("항목 설명");
10.request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, pathSegments.get(pathSegments.size()-1));
11.Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdirs();
12.latestId = downloadManager.enqueue(request);
13.downloadUrl.setText("");
14.break;
15. 
16.case R.id.cancelDownloadButton:
17.downloadManager.remove(latestId);
18.break;
19. 
20.case R.id.viewDownloadsButton:
21.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
22.break;
23.}
24.}