AndyT
AndyT

Reputation: 81

Can Android Studio 4 build projects with API < 9?

I would like my app to support any Android phone api 3 and above.

Why? Because I dislike waste and these older phones are perfectly good enough for the task at hand.

Sadly getting info about basic pre-api-9 SDK builds has proven far harder than getting NDK working.

Upvotes: 1

Views: 149

Answers (1)

AndyT
AndyT

Reputation: 81

I was unable to find a mechanism to build Android applications api < 9 using any of the Android Studio releases available at developer.android.com.

However, it is possible to build them using older command-line tools. (I haven't researched api < 9 use of ActivityCompat libraries nor ndk-builds for debugging JNI apps - These are left as exercises for the reader :-)

NOTE: use of these techniques requires familiarity with present-day command-line builds, without which there is little point reading on.

This task breaks into four:

Android sample projects for api < 9

https://android.googlesource.com/platform/development/+refs

Has everything from donut-release to android-s-beta-4.
Each has a 'samples' subdirectory, which is a good place to start.

Android tools for building api < 9 projects

https://dl.google.com/android/repository/repository-10.xml

lists all downloads with their digest/checksum, located at:

https://dl.google.com/android/repository/(repo-filename)

You'll need to find, download and install:

  • platform sdk 3
  • build-tools 17.0.0 (the newest that contain aapt)
  • build-tools 24.0.3 (the oldest that contain apksigner)
  • ndk R11C (the newest that supports JNI for apis 3 to 8)

Build Android JNI libraries for api < 9

Note that api < 4 devices seem to prefer linking shared symbols from the device OS rather than from your JNI library, so avoid publishing conflicting symbols from your libraries and statically link any third party libs (license permitting.)

Example Makefile:

API = 3
NDK = ~/Android/Sdk/ndk/r11c
CC = $(NDK)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc
SR = $(NDK)/platforms/android-$(API)/arch-arm
INC = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux

objs = obj/thing1.o obj/thing2.o ...

libexample.so : $(objs)
    $(CC) --sysroot=$(SR) -fPIC -Wall -shared -o libexample.so -O $^

obj/%.o : %.c
    $(CC) --sysroot=$(SR) -fPIC -Wall -mthumb -c $< -o $@ $(INC)

Should you be building openssl, you'll need an extra configure flag:

--with-rand-seed=devrandom

as well as no-threads no-asm.

Build Android Java apps for api < 9

Sadly I was unable to find a way to use present-day Android Studio to build these low api projects, so below is some nasty command line stuff to do it instead.

You'll need to find, download and install:

  • platform sdk 3
  • build-tools 17.0.0 (the newest that contain aapt)
  • build-tools 24.0.3 (the oldest that contain apksigner)

(See above for how to get these.)

Project tree

project/
    Makefile
    app/
        build/
        src/
            main/
                AndroidManifest.xml
                assets/
                jniLibs/
                    armeabi/
                        libexample.so
                res/
                    layout/
                        activity_main.xml
                java/
                    com/
                        example/
                            project/
                                MainActivity.java

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.project">
    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        >
        <uses-sdk android:minSdkVersion="3" />
        <activity android:name="com.example.project.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.example.project.MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/outer"
    android:orientation="vertical"    
    >
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="run"
        /><!--note: no onClick-->
    ...

MainActivity.java

package com.example.project;
import android.os.Bundle;
import android.app.Activity;
import android.widget.Button;
public class MainActivity extends Activity {
    @Override protected void onCreate(Bundle bdl) {
        super.onCreate(bdl);
        setContentView(R.layout.activity_main);        
        final Button btn = (Button)findViewById(R.id.btn);
        btn.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                ...
            }
        });
    }
};

Makefile

API = 3
KEY_PATH = ~/jkeystore
DROID_HOME = ~/Android/Sdk
JAVA_HOME = /usr/lib/jvm/java-8-openjdk-amd64
BLD_TOOLS=17.0.0
APK_SIGNR=24.0.3

PKG_PATH = com/example/project
JVA_ROOT = app/src/main/java
RES_ROOT = app/src/main/res
AST_ROOT = app/src/main/assets
AMF_PNAM = app/src/main/AndroidManifest.xml
LIB_REAL = app/src/main/jniLibs

CLS_ROOT = app/build/intermediates/javac/debug/classes
DEX_PATH = app/build/intermediates/dex/debug/mergeDexDebug
APK_PATH = app/build/outputs/apk/debug
APK_NAME = app-debug
LIB_TEMP = lib

JVA_PRJT = $(JVA_ROOT)/$(PKG_PATH)
CLS_PRJT = $(CLS_ROOT)/$(PKG_PATH)
APK_PNAM = $(APK_PATH)/$(APK_NAME)
DRD_JAR =  $(DROID_HOME)/platforms/android-$(API)/android.jar

jopt  = -proc:none -Xlint:all -Xlint:-fallthrough -Werror -Xmaxerrs 5
jopt += -implicit:none -target 1.6 -source 1,6 -bootclasspath ~/jre1.6.0_45/lib/rt.jar

# wildcard matches symlinks awa directories. dir filters, sort dedupes.
JVA_DIRS := $(dir $(wildcard $(JVA_PRJT)/*/) )
JVA_DIRS := $(sort $(JVA_DIRS) )
JVA_DIRS += $(JVA_PRJT)/

# all in java tree except R.java (and thus R.class)
temp_jva := $(foreach pth, $(JVA_DIRS), $(wildcard $(pth)*.java) )
JVA_LIST := $(patsubst $(JVA_PRJT)/R.java, , $(temp_jva))

temp_cls := $(patsubst $(JVA_PRJT)/%.java,$(CLS_PRJT)/%.class, $(JVA_LIST))
CLS_LIST := $(patsubst $(JVA_PRJT)/%,$(CLS_PRJT)/%, $(temp_cls))

LIB_LIST := $(shell find $(LIB_REAL) -type f 2> /dev/null)
LIB_TLST := $(patsubst $(LIB_REAL)/%, $(LIB_TEMP)/%, $(LIB_LIST))

AST_LIST := $(shell find $(AST_ROOT) -type f 2> /dev/null)
ifneq ($(strip $(AST_LIST)),)
    AST_OPTN = -A $(AST_ROOT)
endif

RES_LIST := $(shell find $(RES_ROOT) -type f)

.DELETE_ON_ERROR:

assembleDebug: $(APK_PNAM).apk
    
$(APK_PNAM).apk : $(APK_PNAM).nosig.apk $(AMF_PNAM) $(RES_LIST) $(AST_LIST) $(LIB_LIST)
    #****************************** SIGN **********************************
    $(DROID_HOME)/build-tools/$(APK_SIGNR)/apksigner sign -v --verbose \
        --ks $(KEY_PATH) --ks-pass pass:debug_pw --min-sdk-version $(API) \
        --out $@ $(APK_PNAM).nosig.apk

$(APK_PNAM).nosig.apk : $(DEX_PATH)/classes.dex $(AMF_PNAM) $(RES_LIST) $(AST_LIST) $(LIB_LIST)
    #****************************** PACKAGE *******************************
    @-mkdir -p $(APK_PATH)
    $(DROID_HOME)/build-tools/$(BLD_TOOLS)/aapt package -v -f -M $(AMF_PNAM) \
        -S $(RES_ROOT) $(AST_OPTN) -I $(DRD_JAR) -F $(APK_PNAM).nosig.apk $(DEX_PATH)/
ifneq ($(strip $(LIB_LIST)),)
    #****************************** LIBS **********************************
    cp -r $(LIB_REAL) $(LIB_TEMP)/
    $(DROID_HOME)/build-tools/$(BLD_TOOLS)/aapt add -v $(APK_PNAM).nosig.apk $(LIB_TLST)
    @-rm -r $(LIB_TEMP)
endif

$(DEX_PATH)/classes.dex : $(CLS_LIST) $(CLS_PRJT)/R.class
    #****************************** LINK **********************************
    $(JAVA_HOME)/bin/javac $(jopt) -sourcepath $(JVA_ROOT)/ -cp $(DRD_JAR) \
        -d $(CLS_ROOT)/ $(JVA_PRJT)/$(START).java
    @-mkdir -p $(DEX_PATH)
    $(DROID_HOME)/build-tools/$(BLD_TOOLS)/dx --dex --verbose --output=$@ $(CLS_ROOT)

$(CLS_PRJT)/%.class : $(JVA_PRJT)/%.java $(CLS_PRJT)/R.class
    @-mkdir -p $(CLS_PRJT)
    $(JAVA_HOME)/bin/javac $(jopt) -sourcepath $(JVA_ROOT)/ -cp $(DRD_JAR) -d $(CLS_ROOT)/ $<
    
$(CLS_PRJT)/R.class : $(JVA_PRJT)/R.java
    #****************************** R.CLASS *******************************
    @-mkdir -p $(CLS_PRJT)
    $(JAVA_HOME)/bin/javac $(jopt) -sourcepath $(JVA_ROOT)/ -cp $(DRD_JAR) -d $(CLS_ROOT)/ $<

$(JVA_PRJT)/R.java : $(RES_LIST) $(AMF_PNAM)
    #****************************** R.JAVA ********************************
    $(DROID_HOME)/build-tools/$(BLD_TOOLS)/aapt package -v -f -M $(AMF_PNAM) \
        -S $(RES_ROOT) -m -J $(JVA_ROOT) -I $(DRD_JAR)

You will probably need a 32 bit zlib:

sudo apt install zlib1g:i386

...and should really get java 1.6 for bootstrapping:

https://www.oracle.com/uk/java/technologies/javase-java-archive-javase6-downloads.html

download jre-6u45-linux-x64.bin (requires registration)

You also need to configure a keystore for apk signing:

/usr/lib/jvm/java-8-openjdk-amd64/bin/keytool \
    -genkeypair -validity 1000 -dname "CN=some company,O=Android,C=JPN" -keystore ~/jkeystore \
    -storepass debug_pw -keypass debug_pw -alias cert -keyalg RSA -v

IMPORTANT:

I should point out that api < 9 devices are best considered hopelessly vulnerable to attack via Mobile Data, wifi, tethering, adb, bluetooth, GSM etc.

Upvotes: 3

Related Questions