=======================
=======================
=======================
출처: http://writefoot.tistory.com/142
안드로이드 50메가가 넘어가는 파일 다운로드
생각 없이 쭉 따라 하면 됩니다.
확장 파일은 안드로이드 콘솔에다 업로드 하는데 .. 누구는 압축을 안한 압축파일을 하라고해서 그렇게 하니 됐습니다.
알집에 보니 압축률 없이 압축하는 방법이 있습니다.
안드로이드에서 샘플이 제공되고 있습니다.
D:\adt-bundle-windows-x86_64\sdk\extras\google\play_apk_expansion\downloader_sample
파일이 저장되면 아래의 경로에 저장 됩니다.
Environment.getExternalStorageDirectory() + "/Android/obb/" + thePackageName + "/main."
+ Def.EXTENSION_FILE_VERSION + "." + thePackageName + ".obb";
시작합니다.
아래의 두개의 프로젝트를 import한다
D:\adt-bundle-windows-x86_64\sdk\extras\google\play_apk_expansion\downloader_library
D:\adt-bundle-windows-x86_64\sdk\extras\google\play_apk_expansion\zip_file
downloader_library여기서 에러 나는데 library라이센스를 다시 정해 주면된다
라이센스는 D:\adt-bundle-windows-x86_64\sdk\extras\google\play_licensing 여기 있습니다.
downloader_library,zip_file 둘을 작업 중인 프로젝트에서 프로젝트>안드로이드>library에 add한다
AndroidManifest.xml
<service
android:name="com.home.test.s1.ExpansionFile.ExpansionFileDownloaderService"/>
<receiver
android:name="com.home.test.s1.ExpansionFile.ExpansionFileAlarmReceiver" />
이건 그냥 내가 쓴건데 이중에 뭐가 필요한건지 모르겠네 ...
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
두개의 파일
ExpansionFileAlarmReceiver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
public class ExpansionFileAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
try {
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent,
ExpansionFileDownloaderService.class);
} catch (NameNotFoundException e) {
}
}
}
ExpansionFileDownloaderService.java
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
public class ExpansionFileDownloaderService extends DownloaderService {
//렌덤 숫자 20개 (byte) 아무렇게나 바이트 숫자 넣으세요
private static final byte[] SALT = new byte[]{
0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9};
@Override
public String getAlarmReceiverClassName() {
// TODO Auto-generated method stub
return ExpansionFileDownloaderService.class.getName();
}
@Override
public String getPublicKey() {
// TODO Auto-generated method stub
//이건 MIIBIjANBgkq...... 요래 시작하는 긴거 알지요 ? 안드로이드 콘솔에서 해당 앱눌러서 publickey
return getResources().getString(R.string.base64EncodedPublicKey);
}
@Override
public byte[] getSALT() {
// TODO Auto-generated method stub
return SALT;
}
}
요건 activity에서 기록하는것
Def.EXTENSION_FILE_VERSION 이건 확장 파일을 콘솔에 올렸을때의 앱버젼이다
public class IntroLogoActivity extends SActivity implements IDownloaderClient {
.
.
.
.
String mainFileName = Helpers.getExpansionAPKFileName(this, true, Def.EXTENSION_FILE_VERSION);
Log.e(Constants.TAG, "CRC does not match for entry: ");
if (!Helpers.doesFileExist(this, mainFileName,
Integer.parseInt(getResources().getString(R.string.extendfilesize)), false)) {
mExtensionFrame.setVisibility(View.VISIBLE);
Intent launcher = getIntent();
Intent fromNotification = new Intent(this, getClass());
fromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
fromNotification.setAction(launcher.getAction());
if (launcher.getCategories() != null) {
for (String cat : launcher.getCategories()) {
fromNotification.addCategory(cat);
}
}
PendingIntent pendingIntent = PendingIntent.getActivity(this, 279912, fromNotification,
PendingIntent.FLAG_UPDATE_CURRENT);
try {
// Start the download
int result = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent,
ExpansionFileDownloaderService.class);
Log.i("AA", "DownloaderClientMarshaller result => " + result);
if (DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED != result) {
// implement Downloading UI.
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
ExpansionFileDownloaderService.class);
ToastS.makeText(mToast, this, "파일을 다운로드 중입니다.");
}
} catch (NameNotFoundException e) {
Log.e("apk-expansion-files", "NameNotFoundException occurred. " + e.getMessage(), e);
}
} else {
CallNext();
}
.
.
.
.
@Override
protected void onResume() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onResume();
}
@Override
protected void onStop() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}
@Override
public void onDownloadProgress(DownloadProgressInfo progress) {
// TODO Auto-generated method stub
//다운로드 진행 상황 보여줄려고 하는 ui입니다 입맞에 맞게 작성하시면됩니다 .
//일단 값들은 찍어 보세요
progress.mOverallTotal = progress.mOverallTotal;
mExtenstionProgress.setMax((int) (progress.mOverallTotal >> 8));
mExtenstionProgress.setProgress((int) (progress.mOverallProgress >> 8));
mExtensionCur.setText(Long.toString(progress.mOverallProgress * 100 / progress.mOverallTotal) + "%");
mExtensionMax.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
}
@Override
public void onDownloadStateChanged(int newState) {
// TODO Auto-generated method stub
Log.i("AA", "in onDownloadStateChanged, newState=" + newState);
boolean showDashboard = true;
boolean fAppFinish = false;
//상황에 맞게 작업들을 넣어줘야합니다.
switch (newState) {
case STATE_COMPLETED:// The download was finished
showDashboard = false;
CallNext();
break;
case STATE_IDLE:
Log.i("AA", "in onDownloadStateChanged, STATE_IDLE");
break;
case STATE_FETCHING_URL:
showDashboard = true;
Log.i("AA", "in onDownloadStateChanged, STATE_FETCHING_URL");
break;
case STATE_CONNECTING:
showDashboard = true;
Log.i("AA", "in onDownloadStateChanged, STATE_CONNECTING");
break;
case STATE_DOWNLOADING:
showDashboard = true;
Log.i("AA", "in onDownloadStateChanged, STATE_DOWNLOADING");
break;
case STATE_PAUSED_NETWORK_UNAVAILABLE:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_NETWORK_UNAVAILABLE");
break;
case STATE_PAUSED_BY_REQUEST:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_BY_REQUEST");
break;
case STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION");
break;
case STATE_PAUSED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_NEED_CELLULAR_PERMISSION");
break;
case STATE_PAUSED_WIFI_DISABLED:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_WIFI_DISABLED");
break;
case STATE_PAUSED_NEED_WIFI:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_NEED_WIFI");
break;
case STATE_PAUSED_ROAMING:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_ROAMING");
break;
case STATE_PAUSED_NETWORK_SETUP_FAILURE:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_NETWORK_SETUP_FAILURE");
break;
case STATE_PAUSED_SDCARD_UNAVAILABLE:
Log.i("AA", "in onDownloadStateChanged, STATE_PAUSED_SDCARD_UNAVAILABLE");
break;
case STATE_FAILED_UNLICENSED:
ToastS.makeText(mToast, this, "라이센스 검사에 실패 했습니다.\n앱을 종료합니다.");
fAppFinish = true;
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_FAILED_UNLICENSED");
break;
case STATE_FAILED_FETCHING_URL:
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_FAILED_FETCHING_URL");
break;
case STATE_FAILED_SDCARD_FULL:
ToastS.makeText(mToast, this, "저장 공간이 부족합니다.");
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_FAILED_SDCARD_FULL");
break;
case STATE_FAILED_CANCELED:
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_FAILED_CANCELED");
break;
case STATE_FAILED:
showDashboard = false;
Log.i("AA", "in onDownloadStateChanged, STATE_FAILED");
break;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mExtensionFrame.getVisibility() != newDashboardVisibility) {
mExtensionFrame.setVisibility(newDashboardVisibility);
}
if (fAppFinish) {
finish();
}
}
@Override
public void onServiceConnected(Messenger m) {
// TODO Auto-generated method stub
Log.i("AA", "Download started...");
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
.
.
.
요건 파일 가져오는거 2가지 방법
public AssetFileDescriptor getAssetFileDescriptor(String filePath) {
PackageManager manager = this.getPackageManager();
PackageInfo info;
try {
info = manager.getPackageInfo(this.getPackageName(), 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
return null;
}
String thePackageName = this.getPackageName();
// int thePackageVer = info.versionCode;
String zipFilePath = Environment.getExternalStorageDirectory() + "/Android/obb/" + thePackageName + "/main."
+ Def.EXTENSION_FILE_VERSION + "." + thePackageName + ".obb";
ZipResourceFile expansionFile = null;
AssetFileDescriptor afd = null;
try {
expansionFile = new ZipResourceFile(zipFilePath);
afd = expansionFile.getAssetFileDescriptor(filePath);
} catch (IOException e) {
e.printStackTrace();
}
return afd;
}
public InputStream getAssetFileInputStream(String filePath) {
PackageManager manager = this.getPackageManager();
PackageInfo info;
try {
info = manager.getPackageInfo(this.getPackageName(), 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
return null;
}
String thePackageName = this.getPackageName();
// int thePackageVer = info.versionCode;
String zipFilePath = Environment.getExternalStorageDirectory() + "/Android/obb/" + thePackageName + "/main."
+ Def.EXTENSION_FILE_VERSION + "." + thePackageName + ".obb";
ZipResourceFile expansionFile = null;
InputStream is = null;
try {
expansionFile = new ZipResourceFile(zipFilePath);
is = expansionFile.getInputStream(filePath);
} catch (IOException e) {
e.printStackTrace();
}
return is;
}
라이센스 에러 나고 페치 url 실패 나고 하는데요
확장 파일 버젼 잘 맞추고 사이닝된 apk파일과 콘솔에 올린 버젼이 같으면 됩니다.
일단은 저는 알파테스트로 apk를 올리고 거기다 확장 파일 올리고
다음 부터는 싸이닝된 apk파일 만들어서 폰에 넣고 설치하고를 반복 하면서 테스트 했습니다.
그런데 확장 파일콘솔에 올리고 난뒤에 디버깅으로 앱 설치해도 확장 파일 다운로드가 되네요 디버깅이 더 쉬워 졌어요
처음에 콘솔에 올리고 언제 업데이트 되서 테스트 하나 막막했음 ....
=======================
=======================
=======================
안드로이드 구글 플레이어 올리기..
안드로이드는 50메가 제한이 있음..
.. 어떻게 유니티에서 파일을 나눠서 apk 파일을 만들 것인가 ? ==> 애셋 번들을 만들라. 방법 잘 정리된 블로그 글.
.. 어떻게 나눠진 파일을 올리는가 ?
zip 으로 압축해서 올림. android\obb\패키지명\main.버전코드.패키지명.obb 이런식으로 된다고?.. 파일명은 신경 꺼라. 바로가기
APK Expansion Files (확장파일)
구글 플레이는 현재 50메가 이하의 파일을 요구한다. 대부분의 어플에게 이 용량은 코드와 애셋을 합쳐 충분할 것이다. 하지만 고퀄리티 그래픽이나 미디어 파일, 큰 애셋을 갖는 어플은 더 많은 공간을 요구한다. 이전에는 50메가 이상의 경우 사용자가 앱을 오픈할 때 개발자가 추가적인 리소스를 호스팅하고 다운로드를 제공해야 했다. 추가적인 파일을 호스팅하는 것은 비용이 들고 UX 도 이상적이지 않다. 이 과정을 쉽게하기 위해 구글 플레이는 2개의 대용량 파일을 붙일 수 있게 했다.
구글 플레이는 확장파일을 비용 없이 제공한다. 확장파일은 기기의 공유 스토리지 위치 (SD 카드 또는 USB-마운터블 파티션 – 즉 “외장” 공간) 에 저장된다. 대부분의 기기에서 구글 플레이는 확장 파일을 APK 파일 다운로드와 동시에 받아 사용자가 처음 열었을 때 모든 것이 다 준비되도록 한다. 하지만, 어떤 경우에는 어플이 구글 플레이로부터 어플 시작 시 파일을 다운로드 해야 한다.
Overview
구글 플레이 안드로이드 개발 콘솔을 통해서 APK를 올릴 때 마다 하나 또는 두개의 확장파일을 추가하는 옵션이 주어진다. 각 파일은 2기가 까지이며 어떤 포맷이든 허용되나 다운로드 시 bandwidth 를 유지할 수 있는 압축된 파일을 추천한다. 개념적으로 각 확장파일은 다른 역할을 한다.
# ‘main’ 확장파일은 추가적인 리소스에 대한 주된 확장파일이다.
# ‘patch’ 확장파일은 메인 파일에 대한 부가적인 파일로 작은 업데이트 용도의 파일이다.
두 확장 파일을 어떤 방법으로든 쓸 수 있지만, 메인 파일이 중요한 애셋을 포함하고 업데이트는 가끔 하도록 권장한다. 패치 파일은 더 작고 패치를 위한 파일..
하지만, 어플이 새 패치 파일만을 업데이트 했더라도, 개발자는 매니패스트 의 versionCode 를 업데이트 해서 새로운 APK 를 올려야 한다.
\/\/ Note : 패치 확장파일은 실질적으론 메인 확장 파일과 동일함. 어떻게 사용해도 무방. 시스템은 앱의 패칭을 위해 패치 확장파일을 사용하지 않음. 패치는 스스로 해야 함.
File name format
각 확장 파일은 zip, pdf, mp4 등 어떤 포맷도 가능하다. 파일 타입에 관계 없이 구글 플레이는 opaque binary blobs 로 간주하고 다음과 같은 기준으로 리네임한다.
[main|patch].<expansion-version>.<package-name>.obb
세가지 콤포넌트.
main or patch
파일이 메인인지 패치인지 구별. 각 APK 에 하나의 메인과 하나의 패치만 가능.
<expansion-version>
확장이 ‘첫번째’ 참조되는 정수의 버전 코드 . (어플의 android:versionCode 값과 일치)
‘첫번째’ 는 개발자 콘솔이 업로드된 확장 파일을 재사용하는 것(새로운 APK 파일과 함께)을 허용하기 때문에 강조되었음. 이것은 처음 파일을 올렸을 때의 버전을 유지한다.
<package-name>
어플의 자바-스타일 패키지 이름.
예> main.314159.com.example.app.obb 이렇게 됨.
Storage location
구글 플레이가 확장파일을 다운로드 할 때, 시스템의 공유 공간에 저장한다. 제대로 작동되기 위해서 파일을 지우거나, 옮기거나, 이름을 바꾸지 말것. 어플이 구글 플레이에서 다운로드 해야 할 때, 사용자는 바로 같은 곳에 저장해야 한다.
정해진 위치는 다음과 같다.
<shared-storage>/Android/obb/<package-name>/
# <shared-storage> 는 공유 공간이며 getExternalStorageDirectory() 로 받는다.
# <package-name> 은 getPackageName() 으로 얻음.
각 어플에는 메인/패치 파일이 가능. 이전 버전은 새로운 확장 파일로 업데이트하면 overwrite 됨.
파일을 언-팩 해야하면 .obb 파일을 지우지 말고 같은 디렉토리에 풀린 데이터를 저장하지 말라. 압축 해제된 파일은 getExternalFilesDir() 에 지정된 곳에 저장해야 한다. 하지만, 가능하면 해제 과정 없이 확장 파일을 바로 사용하는 것이 제일 좋다. 예를 들면 집 파일을 바로 읽는 라이브러리 같은 경우..
/\/\ Note : APK 파일과는 다르게 어떤 파일이라도 저장 가능하다.
/\/\ Tip : 미디어 파일을 집 압축 한다면 미디어 플레이백 콜을 오프셋, 길이 조절로 부를 수 있다. (MediaPlayer.setDataSource() & SoundPool.load() ) 압축 해재 없이. 이 과정을 통해 부가적인 압축을 하지 않아도 된다. 예를 들어 집 툴을 쓸 때 -n 옵션을 써서 압축을 안 할 수 있다.
Download process
대부분의 경우 확장 파일은 APK 파일과 함께 다운로드, 저장 된다. 하지만, 어떤 경우에은 구글 플레이는 확장 파일을 다운로드 하지 못하거나 사용자가 이전에 내려받은 파일을 지울 수 있다. 이러한 경우에 대비하여 어플은 파일 자체를 다운로드할 수 있어야 한다. 구글 플레이가 제공하는 URL 을 통해서.
다운로드 프로세스
1. 사용자가 구글 플레이에서 앱 선택.
2. 구글 플레이가 확장파일을 다운로드 할 수 있으면 (대부분의 경우) APK 와 함께 파일 다운로드. 확장 파일을 다운로드 할 수 없을 때는 APK 만 한다.
3. 사용자가 앱을 런칭할 때, 앱은 확장 파일이 있는 지 확인해야 함. 있으면 레디 투 고. 없으면 구글 플레이의 HTTP 에서 다운로드 해야 함. 앱은 구글 플레이 클라이언트에 Application Licensing 서비스를 이용하여 요청해야 한다. 이것은 이름, 파일 크기, URL 에 대응함. 이 정보로 파일을 다운로드 하고 정당한 위치에 저장한다.
주의 : 파일 다운로드 코드를 포함하는 것이 중요하다. 라이브러리 제공했으니 최소의 작업량으로 가능할 것.
Development checklist
내용 요약
1. 50메가 이상이 필요한 지 판단. 가능한 한 작게.. 여러 기기를 위해서라면 복수의 APK 를 고려하라.
2. 메인 확장 파일로 뭘 뺄 지 결정.
3. 기기의 ‘공유 저장 위치’ 로부터 읽어 들이도록 개발. 파일 포맷이 무관하면 ‘집’ 해서 APK Expansion Zip Library 를 이용하도록.
4. 초기에 확장 파일 유무 검사. 없으면 다운로드 받도록. Downloader Library 를 사용하면 간단하다.
테스팅.
=======================
=======================
=======================
출처: http://it77.tistory.com/38
안녕하세요. 시원한물냉입니다. 오늘은 구글 50Mb 용량제한 해결방법에대해 포스팅할려고합니다.
사실 구글에 검색하면 많은 자료들이 나오는데요. 네 맞습니다.
영어입니다. 한글도있다구요? 제대로된 설명이 없더라구요.
저도 이번에 50Mb용량제한 생각안하고 어플을 만들다 다 만들고보니 120메가가 훌쩍 넘어버리더라구요.
그래서 찾던중.. 반나절을 고생했네요.
저처럼 고생하는분 없었으면 하는마음에.. 그리고 다음에 또 해야될일 생기면 좀더 편하게 기억을 더듬기 위해서
이렇게 간단히 포스팅하려합니다.
우선 레퍼런스 정보가 담겨있는 주소입니다.
안드로이드 홈페이지이구요. 영어입니다. http://developer.android.com/google/play/expansion-files.html
친절한 어떤분이 한글로 해석해놓은 사이트도 있습니다. 돌아다니다보니 주소를 잊어버렸네요.. 무튼..있습니다(허허.)
-- 추가 --
해석해놓은 페이지 찾아서 다시 적습니다 ㅎ http://gameforfun.tistory.com/103
1. 확장파일?
우선 확장파일 만드는 방법을 간단히 설명드리겠습니다.
일반적으로 어플리케이션은 이미지파일이나 음악, 동영상 등등 파일들을 res나 혹은 assets에 많이 관리들 하시는데요.
assets가 대부분이시겠죠. 소규모프로그램은 상관없지만, 용량이 커지다보면 50Mb넘어버리기는 일수입니다.
그래서 기본적인 파일들은 apk로 만들고, 리소스가 들어있는 assets폴더 혹은 일부를 (일부를 떼실때에는 이미지든, 동영상이든, 사운드든 아무것이나 상관없습니다) apk에서 제외해서 따로 압축합니다.(zip) 그래서 어플 등록할때 apk와 함께 zip을 업로드하는 것이지요. 이해되시나요..?
확장파일의 저장경로는
Android\obb\ PackageName\main.PackageVersion.PackageName.obb 입니다. |
PackageVersion에는 버전코드 1.0.0 혹은 4.0.3 이런거 있죠. 제일 앞자리만 들어갑니다 ( 앞에서 예로든 버전으로는 1 혹은 4 )
PackageVersion에는 버전네임이 아닌 버전 코드가 들어갑니다. (마켓이 올릴때 올려야하는 그 1.0.0말고 1,2 ,100 등의 코드
그래서 예를들어 com.example.test 라는 패키지의 ver 1.0.0 어플의 저장경로는
Android\obb\com.example.test\main.1.com.example.test.obb 입니다.
굳이 외우지 않으셔도 됩니다. 소스에서 다 처리해줄거니까요.
보통 일반적으로 Assets폴더에서 리소스를 참조할경우 그냥 AssetsGet 하면 되지만, 이제부턴 불러오는 위치를 저기로 바꿔야합니다. (이미지가 다른곳에 있는거니까요)
확장파일의 이름은 아무거나.zip로 해서 올리면 구글에서 알아서 위의 이름이 맞게 바꿔서 서버에 저장됩니다.
파일명도 신경쓰지 않으셔도 됩니다.
(13.10.14 추가)
- 화장파일 압출하실때에는 압축옵션중에 '압축 안함' 이라고 있습니다. 반드시 이걸로 압축해야합니다~! 오랜만에 새로 할려고하니 이부분을 잊고 지나가서 한참 삽질했네요 ㅜ
50Mb가 넘어버렸을때에는 구글이 아닌 T스토어나 기타 다른마켓으로 가시면 손쉽게 해결됩니다. ( 구글보다 용량제한이 덜하거든요.. ) 굳이 구글에 올리시겠다! 그럼 밑으로 쭉 읽으시면 됩니다.
계속 하겠습니다.
2. 확장파일 만들 환경 세팅하기
1) 우선 SDK매니저를 실행하셔서 라이브러리 2개를 설치합니다
Extras - Google Play Licensing Library , Google Play APK Expansion Library 2개를 설치해줍니다.
설치하게되면
sdk/extras/google 경로에
play_apk_expansion 폴더와 play_licensing 폴더가 생성됩니다.
그러면 이제 이클립스로가서
play_apk_expansion 폴더안에있는 downloader_library 하고 zip_file 2개를 Import 해줍니다. 그리고
play_licensing 폴더안에있는 library를 Import해줍니다.
(자바프로젝트로 임포트하지말고 안드로이드 프로젝트로 임포트해주세요)
2) downloader_library 프로젝트에서 오른쪽마우스 - Properties - Android - Library보시면 market_licensing 를 참조하고 있을겁니다. 그러시다면 깔끔하게 Remove눌러서 삭제해줍니다. ( 이게 왜그런지 모르겠는데 최신버전오면서 바뀐건지.. 정확한 이유는 모르겠네요 )
그리고 Add를 눌러서 아까 Import한 Library를 참조합니다.
3) 그런다음, 적용할려는 프로젝트에 오른쪽마우스 - Properties - Java Build Path로 가셔서 Add JARs..를 눌러줍니다.
downloader_library , zip_file 2개를 추가하는데
경로 - bin에 가보면 jar파일 있습니다. 그거2개 추가해줍니다.
그리고 오른쪽에 Order and Export 가셔서 방금 추가한 2개를 체크 해줍니다.
이제 기본적인 세팅은 끝났습니다. 이제 코딩을 시작해볼까요.
4) 확장파일에서 이미지 가져오기
우선 앞에서 설명했었던, 확장파일의 경로에서 이미지를 불러오는 방법입니다.
우선 파일을 가져올 함수부터 만들겠습니다. getAssetFileDescriptor() 함수입니다.
public AssetFileDescriptor getAssetFileDescriptor(String filePath)
{
PackageManager manager = this.getPackageManager();
PackageInfo info;
try
{
info = manager.getPackageInfo(this.getPackageName(), 0);
} catch (NameNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return (null);
}
String thePackageName = this.getPackageName();
int thePackageVer = info.versionCode;
String zipFilePath = "/mnt/sdcard/Android/obb/" + thePackageName + "/main." + thePackageVer + "." + thePackageName + ".obb";
ZipResourceFile expansionFile = null;
AssetFileDescriptor afd = null;
try
{
expansionFile = new ZipResourceFile(zipFilePath);
afd = expansionFile.getAssetFileDescriptor(filePath);
} catch (IOException e)
{
e.printStackTrace();
}
return afd;
}
그리고 이미지를 불러와야 하는 부분에서 이런식으로 getAssetFileDescriptor함수를 호출하면 됩니다.
AssetFileDescriptor readFile = getAssetFileDescriptor("이미지 파일 경로/이름");
Bitmap image;
if (readFile != null)
{
try
{
Drawable d = Drawable.createFromStream(readFile.createInputStream(), null);
image = ((BitmapDrawable) d).getBitmap();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
5) 확장파일 다운실패했을경우의 처리
우선 Manifest에 추가해야할 내용이 있습니다.
<uses-permission android:name="com.android.vending.CHECK_LICENSE">
<uses-permission android:name="android.permission.INTERNET">
<uses-permission android:name="android.permission.WAKE_LOCK">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
<service android:name=".ExpansionFileDownloaderService" android:label="@string/app_name"><span style="font-size: 11pt;">
<receiver android:name=".ExpansionFileAlarmReceiver" android:label="@string/app_name"><span style="font-size: 11pt;">
</span></receiver></span></service></uses-permission></uses-permission></uses-permission></uses-permission></uses-permission></uses-permission>
다음으로 제일처음 시작하는 Activty Oncreate에서 다운받았는지에 대한 확인을 하고 다운로드를 처리합니다.
// Check whether the file have been downloaded.
String mainFileName = Helpers.getExpansionAPKFileName(this, true, 1);
boolean fileExists = Helpers.doesFileExist(this, mainFileName, 파일용량L, false);
if (!fileExists) {
Intent launcher = getIntent();
Intent fromNotification = new Intent(this, getClass());
fromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
fromNotification.setAction(launcher.getAction());
if (launcher.getCategories() != null) {
for (String cat: launcher.getCategories()) {
fromNotification.addCategory(cat);
}
}
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, fromNotification, PendingIntent.FLAG_UPDATE_CURRENT);
try {
// Start the download
int result = DownloaderClientMarshaller.startDownloadServiceIfRequired(
this, pendingIntent, ExpansionFileDownloaderService.class);
if (DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED != result) {
// implement Downloading UI.
return;
}
} catch (NameNotFoundException e) {
Log.e("apk-expansion-files", "NameNotFoundException occurred. " + e.getMessage(), e);
}
}
// expansion file is available. start your application.
주의할점은 위에 파일용량L이 있는데, 저것은 확장파일 zip파일의 등록정보 봤을때 뜨는 바이트 용량크기입니다.
이 크기가 다르면 다운로드 안되니 반드시 정확하게 입력해주세요.
6) 끝났냐구요? 마지막입니다.
클래스를 만들어주세요.
// Extends com.google.android.vending.expansion.downloader.impl.DownloaderService class and override three methods.
public class ExpansionFileDownloaderService extends DownloaderService {
// the Base64-encoded RSA public key for your publisher account
private static final String PUBLIC_KEY = "{YOU_PUBLIC_KEY}";
// Generate 20 random bytes, and put them here.
private static final byte[] SALT = new byte[] {};
@Override public String getPublicKey() {
return PUBLIC_KEY;
}
@Override public byte[] getSALT() {
return SALT;
}
@Override public String getAlarmReceiverClassName() {
return ExpansionFileAlarmReceiver.class.getName();
}
}
public class ExpansionFileAlarmReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
try {
DownloaderClientMarshaller.startDownloadServiceIfRequired(
context, intent, ExpansionFileDownloaderService.class);
} catch (NameNotFoundException e) {
Log.e("apk-expansion-files", "NameNotFoundException occurred. " + e.getMessage(), e);
}
}
}
2개입니다.
이상입니다. 이미지가 안들어가있어서 보기 힘드실텐데. 한줄한줄 읽으면서 천천히 따라하면 충분히 모두 다 해내실겁니다^^
질문은 댓글로 주세요.
마켓이 업로드할때는 apk올리고 확장파일 따로 올리는거 아시죠^^?
모두들 즐프 하세요~
=======================
=======================
=======================
apk파일의 용량이 너무커서 확장파일을 사용하려고 합니다.
지금 우여곡절 끝에 거의다 하긴 했는데요.
이미지 파일의경우 사용을 할수 있을것같습니다.
그런데 mp3파일이나 동영상파일의 경우 사용할 줄을 모르겠습니다.
1.확장 파일을 사용할 경우 이미지파일들만 사용을 해야합니까?
2.만약 동영상등의 파일도 가능하다면. InputStream의 형태로 받는게 아니라 다른걸로 받아서 사용해야 하는건가요?
압축된 확장파일에서 읽어오는 방법을 InputStream을 이용해서 하는방법밖에 모르겠습니다만 MediaPlayer에서는 InputStream으로는 못쓰는거 같아서요. 마찬가지로 동영상 파일도 못쓰는거 같습니다.
사용가능하다면 어떻게 해야하는지좀...
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.확장 파일을 사용할 경우 이미지파일들만 사용을 해야합니까?
- 아니요. 어떤것이 들어가도 상관없습니다. 아직 확장파일의 감을 못잡으신거 같은데.
이미지라던지 동영상이라던지 상관없어요. 텍스트파일도 상관없고 자신이 쓰고 싶은 파일을 압축시키는겁니다.
디비를 넣어도 상관없고, apk자체에 넣기는 부담스러운 파일들을 따로 빼서 압축시키는겁니다.
2.만약 동영상등의 파일도 가능하다면. InputStream의 형태로 받는게 아니라 다른걸로 받아서 사용해야 하는건가요?
- 압축 파일을 받아서 압축을 풀고 사용하는거에요. 예를 들어서 인터넷에서 zip으로 압축된 게임을 다운받았다면, 압축을 어딘가에 해제하고 실행파일을 실행하겠죠? 압축한 상태에서 실행을 시키진 않죠.
마찬가지로 다운 받은 확장파일을 어딘가에 압축을 풀어주는겁니다. sd카드에 풀든 어디에 풀든지 아무튼 풀어 놓고 불러다 쓴느거죠.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
아...압축된 상태로만 쓰려고 했엇네요... 압축 파일을 푸는 방법을 찾아봐야겠군요...감사합니다.. 라르크님, OKKiller님
저번에 링크걸어주신 부분에 압축 푸는것도 설명이 나와있나요?
------
>android sdk 폴더를 열어보시면
android-sdk-windows\extras\google\play_apk_expansion\zip_file\src\com\android\vending\expansion\zipfile
이런 경로에 압축파일 관련된 소스가 있어요
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
확장파일 사용해서 실제 압축파일에서 mp3를 뽑아와서 재생하는 것 까지 해봤습니다.
확장파일을 올릴때 zip파일로 만들잖아요.
그때 그 'zip파일을 압축하지 않고 압축하기'옵션을 설정하고 압축하셔야합니다.
그렇지 않으면 inputstream같은 값으로 받을때 null값이 떨어집니다.
왜 떨어지는 지는 ziplibrary소스코드를 보면 알 수 있습니다.(압축이 되어있으면 null주게 되어있음)
그리고 inpuststream으로만 받아올 수 있는 건 아닙니다.
물론 확장파일 관련 구글 문서에서는 그렇게 나와있지만...
AssetFileDescriptor형식으로 받을 수 있습니다.
대충 소스코드 드릴께요
public AssetFileDescriptor getAssetFileDescriptor(String filePath){
String zipFilePath = "/mnt/sdcard/Android/obb/패키지명/main.1.패키지명.obb";
ZipResourceFile expansionFile = null;
AssetFileDescriptor afd = null;
try {
expansionFile = new ZipResourceFile(zipFilePath);
afd = expansionFile.getAssetFileDescriptor(filePath);
} catch (IOException e) {
e.printStackTrace();
}
return afd;
}
그리고 AssetFileDescriptor형식으로 받은 거를 쉽게 미디어로 재생가능합니다.
----
>아 그리고 라르크v님이 말씀하신 거에 이의를 제기합니다.
구글에서 제공하는 ZipLibrary가 압축해제하지 않고 직접 접근가능하게 해주는 라이브러리고
구글 문서에도 그걸 권장하고 있는데 굳이 압축을 풀 필요는 없죠.
예를 들어서 500mb짜리 확장파일을 받고, 그 500메가의 압축을 푼다??
비효율적입니다.
zip파일에서 inputstream으로 빼든 AssetFileDescriptor로 빼시면 되요.
압축파일에서 빼오는데 시간이 더 걸릴 수 있다고 생각하시는 분 있는데
전 차이를 전혀 못 느꼈습니다.
------------------------------------------------
=======================
=======================
=======================