Repacking and Resigning an Android APK to Target Different Environments

In my previous post talking about targeting different environments I ended with the proposition that what we need to be able to do as part of the release pipeline for an app is to adjust the configuration file that’s included in the app package. In this post I’m going to manually walk through what this process would look like for an Android APK (and yes, before you all jump up and down and say that I should be using an app bundle, I’m aware of this but let’s do this process step by step).

Ok to begin, what I need is a release-ready APK and for the purpose of this post I’m going to use a Flutter app. The process I’m going to describe will work for any Android APK regardless of the toolchain/framework/technology set that you’re using to build your app. The only difference with say a Xamarin.Forms application would be how you package the configuration file; the code you’d need to write to read the configuration file and of course the XAML for displaying the contents to the screen.

Basic App Structure

My sample Flutter app starts with the default app template you get when creating a new Flutter app in VS Code. I then added a single text file, config.txt, to the assets folder; included the file in the pubspec.yaml and then adjusted the _MyHomePageState class to load the contents of the file (a basic walk through of reading files that are included in the app package are included in this post).

class _MyHomePageState extends State<MyHomePage> {
  String config = '';

  @override
  void initState() {
    super.initState();

    loadConfig(context);
  }

  void loadConfig(BuildContext context) async {
    config =
        await DefaultAssetBundle.of(context).loadString('assets/config.txt');
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'App configuration:',
            ),
            Text(
              '$config',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

The contents of the config.txt file simply says “***Default App Config***” and running the app looks like:

Release Ready APK

In order to walk through the process of repackaging an APK, I firstly need to make sure I have a release-ready APK. By this I mean that I have an APK that’s been built in release mode and that has been signed, as if I were going to submit it to the Google Play store.

The Flutter documentation has very clear instructions on how to package and sign your application. I followed these instructions to generate a keystore that is used as part of the Flutter build process to sign the application.

In order to test to make sure your APK is good to go, simply copy it to a real device and test that you can install and run it. For this I simply uploaded the APK to dropbox and then opened the file on my device. You could also attach to an email or even side load directly via USB cable if you choose.

Repackaging to Change App Configuration

The basic process we want to follow is:

  • Unpack the APK
  • Modify the config.txt file
  • Repack the APK
  • Sign the APK

Unpacking the APK

In order for us to be able to modify the config.txt file that’s packaged in the app, we first need to unpack the APK. For this I’m going to use the APKTool utility. Follow the installation instructions to make sure you have the latest version and the appropriate directories added to the PATH variable (you may need to add the Java directory to your PATH).

Once installed, to unpack an APK you can simply call the APKTool with the decode, or just “d”, argument:

   apktool d app-release.apk -o extracted_apk

Note that I specified the “-o” argument to allow me to specify the output folder.

Modify the Config.txt

In this example we’re simply going to modify the config.txt file that we have included in the app package. You could be more fancy and include a json or xml configuration file but essentially all you’re going to do in this step is modify the contents, or replace the file entirely.

In the case of my sample Flutter app, the location of the config.txt is in the sub-folder “\assets\flutter_assets\assets”. If you’re application is built using Xamarin.Forms, your configuration file may be located in a different folder – you just need to search the extracted folder and locate the file.

I’ve changed the contents of the file to “***Modifited App Config***”

Repack the APK

After making the change to the config.txt file we then need to repack the APK. For this we can again use the APKTool, this time specifying the build, or “d”, argument:

apktool b extracted_apk -o app-release-mod.apk

More detailed information on the APKTool is available from their documentation page.

Sign the APK

The last step is to sign the APK and for this I’m going to use the Uber Apk Signer along with the keystore I setup as part of configuring the Flutter release build.

java -jar uber-apk-signer-1.1.0.jar -a app-release-mod.apk --ks c:\<<path to keystore>>\key.jks --ksAlias key --ksKeyPass <<password>> --ksPass <<password>> -o app-release-mod-signed

And that’s it – if we check in the app-release-mod-signed folder there’ll be a new APK that’s signed and ready to go.

Copy the application to your device and run it and you’ll see the updated configuration value (and yes, complete with spelling mistake!!)

Repackaging Apps During Release Process

As you can see from this process, it’s not too hard to adjust a configuration value by repacking an Android APK. You can simply include the APKTool and the Uber-Apk-Signer alongside your application and script out these steps as part of your Release pipeline.

2 thoughts on “Repacking and Resigning an Android APK to Target Different Environments”

Leave a comment