Androidの無料アプリ・有料アプリを切り替えてビルドする方法

おそらくもっとスマートな方法があると思いますが、ShakeDroid/ShakeDroid Advanceで使用した無料・有料アプリのプロジェクト設定とビルド方法です。

Androidの問題点として、同じプロジェクトから有料・無料アプリの切り替えが非常に面倒(packageの制限やManifestの書き換えなど)であるため、別プロジェクトとして用意するのが普通だと思います。
違うプロジェクトにした場合はgenフォルダ内のpackageが切り替わってしまうため、ソースに変更が必要です。
いくつかの制限はありますが、プロジェクトに細工を行えば同じソース・リソースで有料・無料プロジェクトのビルドが可能です。
※もっと便利な方法があると思いますが、自分ではこれ以上自動化できませんでした。

開発環境はWindows Vista(Home)+Eclipse 3.5を利用していますが、他のOSでも問題ないと思います。
以下、見づらくてすいません。




1:基本となる無料版プロジェクト(ここでは基底プロジェクトと呼びます)を作成する

俺の場合だと基底プロジェクト名はShakeDroid、生成されるapkはShakeDroid.apkです。
このプロジェクトは基本的に普通のAndroidアプリとして作成して構いません。
ライブラリjarやプロジェクトも普通に設定して構いません。
ただし、2で説明する派生プロジェクトのリソースもresフォルダに含めなければなりません。




2:派生先の有料版プロジェクト(派生プロジェクト)を作成する

俺の場合だとShakeDroidAdvance、生成されるapkはShakeDroidAdvance.apkです。
このプロジェクトに仕掛けを行うことで同じソース・リソースを利用してビルドが可能になります。
ただし、AndroidManifest.xmlは使いまわせません(ShakeDroidでは特に不都合はありませんでした)ので、各々のプロジェクトで用意します。
起動用のActivityは基底プロジェクトのActivityを単純継承して、何もオーバーライドしません。

                                                • -

package eagle.android.app.shakeadvance;

import eagle.android.app.shake.ShakeDroid;
import android.app.Activity;
import android.os.Bundle;

//! 継承してクラスパッケージのみを分離する。
public class ShakeDroidAdvance extends ShakeDroid {
/** Called when the activity is first created. */
}

                                                • -


3:派生プロジェクトのフォルダを削除する。
 基底プロジェクトから引き継ぎたいフォルダ(res/assets)を派生プロジェクトから削除します。
 次に、派生プロジェクトの新規->フォルダを選択します。

 フォルダ名を指定せず、拡張->ファイル・システム内のフォルダーにリンク->参照から、基底プロジェクトのresフォルダを指定します。

 完了を選択すると、基底プロジェクトとの間にシンボリックリンクが作成され、リソースが共有されるようになります。
 無料版にも有料版のリソースを含める必要がありますが、特に不都合はないと思います。
 assetsも必要であれば同じ手順でシンボリックを作成してください。
 ソースコードも同じようにフォルダ/ファイル単位のシンボリックを作成して、ファイルを共有します。そうすることで、編集漏れやバージョン違いを防ぐことができます。




4:genフォルダの中身をコピーする
 レイアウト等のリソースから画面を生成するのに生成されたRクラスを使用してると思いますが、packageが違うため通常はソースコードの編集が必要です。
 生成されたRクラスはfinal属性であるため、継承によってダミーのクラスを作ることもできません。
 そのため、基底プロジェクトから派生プロジェクトにRクラスをコピーするバッチを作成します。
 バッチ自体はこんな感じです。

                                                              • -

@echo off
SET PARENT_PROJECT=ShakeDroid
SET CURRENT_PROJECT=ShakeDroidAdvance
REM 定義ファイルをコピーする
cd ..\%PARENT_PROJECT%\
REM genフォルダをコピーする
xcopy /s /e /y .\gen ..\%CURRENT_PROJECT%\gen
cd ..\CURRENT_PROJECT\

                                                              • -

 これを実行すればRクラスがコピーされ、派生プロジェクトでも同一ソースを利用出来るようになります。
 ただ、いちいち手動実行では非効率的ですので、プロジェクトの設定を変更します。
 派生プロジェクトのプロパティ->ビルダー->新規から、先程作成したバッチを呼び出す起動構成を作成します。

 これでビルドの度に自動的に基底プロジェクトからgenフォルダが同期されます。




5:無料・有料のアプリ内での切り分け
 これは個々のアプリごとに設計が異なると思いますが、ShakeDroidの場合はeagle.android.app.appinfoパッケージを意図的にシンボリックリンクせず、別々の中身にすることで同一ソースで有料・無料を切り分けています。
 切り分けが必要な箇所ではAppInfomationをnewし、フラグやクラスを取得することで切り分けを実現しています。
 具体的にはこんな感じです。
AppInfomation.java
無料版----------------------------------
/**
*
* @author eagle.sakura
* @version 2010/06/05 : 新規作成
*/
package eagle.android.app.appinfo;

import com.admob.android.ads.AdView;

import android.app.Activity;
import android.view.View;
import eagle.android.appcore.IAppInfomation;

/**
* @author eagle.sakura
* @version 2010/06/05 : 新規作成
*/
public class AppInfomation implements IAppInfomation
{

/**
* @author eagle.sakura
* @param activity
* @return
* @version 2010/06/05 : 新規作成
*/
@Override
public View createAdView(Activity activity)
{
// TODO 自動生成されたメソッド・スタブ
AdView ad = new AdView( activity );
ad.setVisibility( View.VISIBLE );
ad.setKeywords("Android application");
ad.bringToFront();
ad.requestFocus();
ad.invalidate();
return ad;
}

/**
*
* @author eagle.sakura
* @return
* @version 2010/06/05 : 新規作成
*/
@Override
public boolean isSharewareMode()
{
//! 無料モードであるため、falseを返す。
// TODO 自動生成されたメソッド・スタブ
return false;
}

}
有料版----------------------------------
/**
*
* @author eagle.sakura
* @version 2010/06/05 : 新規作成
*/
package eagle.android.app.appinfo;

import android.app.Activity;
import android.view.View;
import eagle.android.appcore.IAppInfomation;
import eagle.util.EagleUtil;

/**
* @author eagle.sakura
* @version 2010/06/05 : 新規作成
*/
public class AppInfomation implements IAppInfomation
{

/**
* @author eagle.sakura
* @param activity
* @return
* @version 2010/06/05 : 新規作成
*/
@Override
public View createAdView(Activity activity)
{
EagleUtil.log( "Not Admob" );
// TODO 自動生成されたメソッド・スタブ
return null;
}

/**
*
* @author eagle.sakura
* @return
* @version 2010/06/05 : 新規作成
*/
@Override
public boolean isSharewareMode()
{
EagleUtil.log( "this is shareware" );
//! 有料モードであるため、trueを返す。
// TODO 自動生成されたメソッド・スタブ
return true;
}

}

                                                                                                                            • -


設定項目が多く面倒ですが、ある程度の回数アップデートするなら設定しておいて損はないかと思います。