Recently in my outsourcing project I had to build multiple Android APK files
from a single source code. For example, build apps downloading different contents
from server, different icons or app names but basically all the other codes are same.
Let's say your package name is like 'com.yourcompany.yourappname'
and your default app name is 'YourApp' and your app is downloading information
about specific planet.
Now you want to build 'YourApp_download_data_only_for_mars',
'YourApp_download_data_only_for_sun', 'YourApp_download_data_only_for_pluto', etc
and there can be 'free_version' and 'comercial version'.
You can do that by changing 'app name', some strings for each cases, some icons,
and most importantly the 'package name'.
You can change the 'package name' from Eclipse by 'refactorying' feature. But you have to
do it multiple times for each different app variations. So you better make your app buildable
from command line, by using Ant.
If you change the 'package name' in just 'AndroidManifest.xml' it will compile and be
installed as a different app. But soon you will find that the APKs bult with that way
will share the same 'application directory' so those apps will all have same
configurations.
On of the OTHER possible walkarounds for this topic will be to use 'Library Project'. But
I found that it is not so applicable to my case because I already have working proejct
and many files need to be changed anyway for replacing strings or values for each cases.
So I tought "If I have to change contents of many files then I can even control most of the
things so why do I have to bother to make another 'library' project and change my current app?"
So let's find more about the first way.
You have to replace ALL the 'package name' in my source files, even directory path changes.
For example, if your package name is like 'com.yourcompany.yourappname' then the directory
where most of your main logic resides will be under './src/com/yourcompany/yourappname'.
And your package name will appear on most of your *.java source files.
For that purpose I decided to use 'ruby' script because it is easy.
Basically you can change the pattern of a string in a single file like this.
require 'find'
require 'FileUtils'
def replaceall_file( file, regex, replace )
text = File.read(file)
result = text.gsub(/#{regex}/, replace)
File.open(file, "w") {|f| f.puts result}
end
If you want to change the wole directory then you can do it like this.
def replaceall_directory( root, regex, replace )
Find.find(root) do |f|
#p f if f =~ /(\.svn)$/ && !File.directory?(f)
if !File.directory?(f)
p f
replaceall_file(f, regex, replace)
end
end
end
For example if you want to change the string in './AndroidManifest.xml'
package="com.yourdomain.yourappname"
to
package="com.yourdomain.yourappname1"
then you call
replaceall_file( "./AndroidManifest.xml", "package=\"com\\.yourdomain\\.yourappname\"", "package=\"com.yourdomain.yourappname1\"")
You have to do it for all the source files (ordinaryily reside under './src' directory),
layout resource ("./res/layout/"), and other xml resources ("./res/xml/").
And you maybe have to change your 'app name' to different one so you can call
replaceall_file( "./res/values/strings.xml", "app_name\">.*", "app_name\">New App Name")
If you want to replace program icons with different ones per each apps then you just prepare those image files
for each 'dpi's in a separate directory and you just overrwite default 'ic_launcher.png' file like this.
system("cp ./tmp/#for_app1_ldpi.png ./res/drawable-ldpi/ic_launcher.png")
And now you have to move source directory to a new directory to apply the new package name.
system("mv ./src/com/yourdomain/yourappname ./src/yourdomain/yourdomain/yourappname1")
Almost done. The resource are compiled and those are tightly bound to the package name.
So if you change the package name you have to clean './gen' and './bin' directory and
have to rebuild the resouce again. So in every build you have to call this in the beginning.
system("rm -rf ./gen")
system("rm -rf ./bin")
Finally, call 'ant' build.
system("ant release")
Done. now the following could be the full example of my ruby script.
require 'find'
require 'FileUtils'
def replaceall_directory( root, regex, replace )
Find.find(root) do |f|
#p f if f =~ /(\.svn)$/ && !File.directory?(f)
if !File.directory?(f)
p f
replaceall_file(f, regex, replace)
end
end
end
def replaceall_file( file, regex, replace )
text = File.read(file)
result = text.gsub(/#{regex}/, replace)
File.open(file, "w") {|f| f.puts result}
end
def build(package_name_postfix_from, package_name_postfix_to, isthis_freeversion, project_name, changeable_image_file_name_header, new_app_name)
system("rm -rf ./gen")
system("rm -rf ./bin")
apkfile_postfix = isthis_freeversion=="true" ? "_free" : ""
replaceall_file( "./AndroidManifest.xml", "package=\"com\\.yourdomain\\.#{package_name_postfix_from}\"", "package=\"com.yourdomain.#{package_name_postfix_to}\"")
replaceall_file( "./res/values/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_file( "./res/values-es/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_file( "./res/values-jp/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_file( "./res/values-ko/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_file( "./res/values-th/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_file( "./res/values-zh-rCN/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_file( "./res/values-zh-rTW/strings.xml", "app_name\">.*", "app_name\">#{new_app_name}")
replaceall_directory( "./src/", "com\\.yourdomain\\.#{package_name_postfix_from}", "com.yourdomain.#{package_name_postfix_to}")
replaceall_directory( "./res/layout/", "com\\.yourdomain\\.#{package_name_postfix_from}", "com.yourdomain.#{package_name_postfix_to}")
replaceall_directory( "./res/xml/", "com\\.yourdomain\\.#{package_name_postfix_from}", "com.yourdomain.#{package_name_postfix_to}")
replaceall_file( "./build.xml", "project name=\".*\"", "project name=\"#{project_name}\"")
system("cp ./changeables/#{changeable_image_file_name_header}_splash.png ./res/drawable/splash.png")
system("cp ./changeables/#{changeable_image_file_name_header}#{apkfile_postfix}_ldpi.png ./res/drawable-ldpi/ic_launcher.png")
system("cp ./changeables/#{changeable_image_file_name_header}#{apkfile_postfix}_hdpi.png ./res/drawable-hdpi/ic_launcher.png")
system("cp ./changeables/#{changeable_image_file_name_header}#{apkfile_postfix}_mdpi.png ./res/drawable-mdpi/ic_launcher.png")
system("mv ./src/com/yourdomain/#{package_name_postfix_from} ./src/com/yourdomain/#{package_name_postfix_to}")
system("ant release")
system("mv ./bin/*-release.apk ./#{project_name}.apk")
end
build( "yourappname", "yourappname1", "true", "kmyapp_free", "k_myapp", "MyApp for Korean Free")
build( "yourappname1", "yourappname2", "false", "kmyapp", "k_myapp", "MyApp for Korean")
build( "yourappname2", "yourappname3", "true", "jmyapp_free", "j_myapp", "MyApp for Japanese Free")
build( "yourappname3", "yourappname", "false", "jmyapp", "j_myapp", "MyApp for Japanese")