cocos2d-x Android In App Billing Dungeons Sample Optimization and Intergration in External Jar Lib
제목이 거창하네요. 제가 현재 회사에서 하고 있는 Framework는 살짝 어떤 내용인지 몇번 언급한 적이 있었죠. 대략적으로 현 상황을 정리하자면, iOS딴은 xcode에서 mm으로 해주면 c++이든 Objective-C든 간에 소스가 짬뽕이 되어도 컴파일이 되서 하나의 static lib로 결과물을 만들 수 있습니다.
반면, Android에서는 예외가 발생하더군요. C++로 되어있는 Framework는 NDK를 통해 이상없이 컴파일이 되지만 최소한의 Java 코딩이 더 들어갑니다. 특히나 cocos2d-x를 활용한 순수 게임 코딩이 아닌 제가 지금것 포스팅했던 여러 외부 모듈들을 붙이다보면 어쩔수 없이 JNI와 Java딴 코딩이 들어가죠. 그동안은 cocos2d-x Android template로 자동 생성된 게임의 java파일 즉 GameMainActivity에 그냥 바로 샘플식으로 작업해왔지만 이렇게 하면 다른 팀원들과의 협업에 있어서 C++ lib와 java code 공유라는 반쪽짜리 Android Framework가 되어버리게 됩니다. C++로 된 것을 NDK로 컴파일한 Framework와 Java딴을 컴파일한 JAR static lib도 만들 수 밖에 없는 상황이 된거죠.
서론이 길었습니다. 이번 포스팅에서는 지난번 안드로이드 인앱 빌링 연동을 했을 때 언급했던 Dungeons Sample 최적화부분과 게임 App이 아닌 따로 만든 안드로이드 외부 Lib JAR 프로젝트에 인앱 빌링 기능을 통합하는 것을 정리해봅니다. 대부분은 위 링크들을 기준으로 선 작업이 되어 있어야하고, 기타 수정부분만을 언급하도록 하겠습니다.
먼저 링크를 참고하면서 안드로이드 외부 Lib 프로젝트에 구글 인앱 빌링 Dungeons Sample를 Import해줍니다. 단, 이번에는 play_billing 하위 폴더중 res는 빼고 src만 가져옵니다.
<activity android:name="com.example.dungeons.Dungeons"
android:configChanges="orientation" >
</activity>
개발중인 App의 AndroidManifest.xml에서 Dungeons의 Activity는 삭제합니다. 더이상 Dungeons는 Activity가 아닌 일반 class 객체로 할 것이기 때문이죠.
역시나 기존에 연동했을 때 가져왔던 Dungeons Sample의 Layout 관련 xml 4개도 모두 삭제합니다.
아래는 최적화? 한 Dungeons class 입니다. 게임용에 맞게 Activity를 상속하지 않고 일반 class로 만들어봤습니다. Restore관련 db와 onRestoreTransactionsResponse 정도와 불필요해 보이는 dialog등을 삭제했고 구독 아이템 이외 타입의 아이템 정도만 구입할 수 있게 정리해봤습니다. JniMapper는 C++ Framework와 통신하기 위해 만든 Java측 Framework에 있는 class입니다. 별것은 없고 각 게임에 맞게 Thread Safe하게 JNI 통신 처리와 Dungeons class의 init, destroy 해주시면 됩니다.
이제 작업했던 cocos2d-x c++ 부분을 NDK로 빌드하고 Java측 JAR Lib를 export하고 Android 게임 프로젝트에 다 적용하시면 이상없이 작동할 것입니다.
모든 예외 상황을 처리한 것도 아니고 구현 안된 부분도 있으므로 아직 추가 개발에 여지는 남아있습니다.
반면, Android에서는 예외가 발생하더군요. C++로 되어있는 Framework는 NDK를 통해 이상없이 컴파일이 되지만 최소한의 Java 코딩이 더 들어갑니다. 특히나 cocos2d-x를 활용한 순수 게임 코딩이 아닌 제가 지금것 포스팅했던 여러 외부 모듈들을 붙이다보면 어쩔수 없이 JNI와 Java딴 코딩이 들어가죠. 그동안은 cocos2d-x Android template로 자동 생성된 게임의 java파일 즉 GameMainActivity에 그냥 바로 샘플식으로 작업해왔지만 이렇게 하면 다른 팀원들과의 협업에 있어서 C++ lib와 java code 공유라는 반쪽짜리 Android Framework가 되어버리게 됩니다. C++로 된 것을 NDK로 컴파일한 Framework와 Java딴을 컴파일한 JAR static lib도 만들 수 밖에 없는 상황이 된거죠.
서론이 길었습니다. 이번 포스팅에서는 지난번 안드로이드 인앱 빌링 연동을 했을 때 언급했던 Dungeons Sample 최적화부분과 게임 App이 아닌 따로 만든 안드로이드 외부 Lib JAR 프로젝트에 인앱 빌링 기능을 통합하는 것을 정리해봅니다. 대부분은 위 링크들을 기준으로 선 작업이 되어 있어야하고, 기타 수정부분만을 언급하도록 하겠습니다.
먼저 링크를 참고하면서 안드로이드 외부 Lib 프로젝트에 구글 인앱 빌링 Dungeons Sample를 Import해줍니다. 단, 이번에는 play_billing 하위 폴더중 res는 빼고 src만 가져옵니다.
<activity android:name="com.example.dungeons.Dungeons"
android:configChanges="orientation" >
</activity>
개발중인 App의 AndroidManifest.xml에서 Dungeons의 Activity는 삭제합니다. 더이상 Dungeons는 Activity가 아닌 일반 class 객체로 할 것이기 때문이죠.
역시나 기존에 연동했을 때 가져왔던 Dungeons Sample의 Layout 관련 xml 4개도 모두 삭제합니다.
아래는 최적화? 한 Dungeons class 입니다. 게임용에 맞게 Activity를 상속하지 않고 일반 class로 만들어봤습니다. Restore관련 db와 onRestoreTransactionsResponse 정도와 불필요해 보이는 dialog등을 삭제했고 구독 아이템 이외 타입의 아이템 정도만 구입할 수 있게 정리해봤습니다. JniMapper는 C++ Framework와 통신하기 위해 만든 Java측 Framework에 있는 class입니다. 별것은 없고 각 게임에 맞게 Thread Safe하게 JNI 통신 처리와 Dungeons class의 init, destroy 해주시면 됩니다.
package com.example.dungeons;
import com.example.dungeons.BillingService.RequestPurchase;
import com.example.dungeons.BillingService.RestoreTransactions;
import com.example.dungeons.Consts.PurchaseState;
import com.example.dungeons.Consts.ResponseCode;
import android.os.Handler;
import android.util.Log;
import xxxxx.framework.JniMapper;
/**
* A sample application that demonstrates in-app billing.
*/
public class Dungeons {
private static final String TAG = "Dungeons";
private DungeonsPurchaseObserver mDungeonsPurchaseObserver;
private Handler mHandler;
private BillingService mBillingService;
private boolean bBillingSupport = false;
/**
* A {@link PurchaseObserver} is used to get callbacks when Android Market sends
* messages to this application so that we can update the UI.
*/
private class DungeonsPurchaseObserver extends PurchaseObserver {
public DungeonsPurchaseObserver(Handler handler) {
super(JniMapper.getOwnerActivity(), handler);
}
@Override
public void onBillingSupported(boolean supported, String type) {
if (Consts.DEBUG) {
Log.i(TAG, "supported: " + supported);
}
if (supported) {
bBillingSupport = true;
}
}
@Override
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload) {
if (Consts.DEBUG) {
Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
}
if (purchaseState == PurchaseState.PURCHASED) {
}
}
@Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
if (Consts.DEBUG) {
Log.d(TAG, request.mProductId + ": " + responseCode);
}
final String cppResultCode;
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.i(TAG, "purchase was successfully sent to server");
}
cppResultCode = request.mProductId;
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
if (Consts.DEBUG) {
Log.i(TAG, "user canceled purchase");
}
cppResultCode = "cancelIAP";
} else {
if (Consts.DEBUG) {
Log.i(TAG, "purchase failed");
}
cppResultCode = "faileIAP";
}
///< JNI 호출로 C++에게 결과를 알려준다.
JniMapper.callCppPurchaseResponse( cppResultCode );
}
@Override
public void onRestoreTransactionsResponse(RestoreTransactions request,
ResponseCode responseCode) {
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.d(TAG, "completed RestoreTransactions request");
}
// Update the shared preferences so that we don't perform
// a RestoreTransactions again.
/*
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(DB_INITIALIZED, true);
edit.commit();
*/
} else {
if (Consts.DEBUG) {
Log.d(TAG, "RestoreTransactions error: " + responseCode);
}
}
}
}
public void init() {
mHandler = new Handler();
mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
mBillingService = new BillingService();
mBillingService.setContext(JniMapper.getOwnerActivity());
// Check if billing is supported.
ResponseHandler.register(mDungeonsPurchaseObserver);
if (!mBillingService.checkBillingSupported()) {
}
/*
if (!mBillingService.checkBillingSupported(Consts.ITEM_TYPE_SUBSCRIPTION)) {
showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
}
*/
}
public void destroy() {
ResponseHandler.unregister(mDungeonsPurchaseObserver);
mHandler = null;
mDungeonsPurchaseObserver = null;
mBillingService.unbind();
mBillingService = null;
}
/**
* 아이템 구매 요청
* @param strProductId
*/
public void requestPurchase( String strProductId ) {
if (Consts.DEBUG) {
Log.d(TAG, "requestPurchase: " + strProductId);
}
if( bBillingSupport == false )
{
///< JNI 호출로 C++에게 구매 못한다고 알려준다.
JniMapper.callCppPurchaseResponse( "faileIAP" );
if (Consts.DEBUG) {
Log.d(TAG, "requestPurchase: Bulling Not Support");
}
return;
}
//if( mManagedType != Managed.SUBSCRIPTION ) {
if( !mBillingService.requestPurchase(strProductId, Consts.ITEM_TYPE_INAPP, null) ) {
//showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
}
//}
///< 구독
/*
else
{
if( !mBillingService.requestPurchase(strProductId, Consts.ITEM_TYPE_SUBSCRIPTION, null) ) {
//showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
}
}
*/
}
}
이제 작업했던 cocos2d-x c++ 부분을 NDK로 빌드하고 Java측 JAR Lib를 export하고 Android 게임 프로젝트에 다 적용하시면 이상없이 작동할 것입니다.
모든 예외 상황을 처리한 것도 아니고 구현 안된 부분도 있으므로 아직 추가 개발에 여지는 남아있습니다.


댓글
댓글 쓰기