On July 9, the first stock with dual-class share structure in Hong Kong, Xiaomi (01810-HK), officially listed on the Hong Kong Stock Exchange, opening at HKD 16.6, and then once dropped to HKD 16. Based on the opening price, Xiaomi’s market value exceeds USD 47 billion. Lei Jun’s personal wealth reached USD 12.4 billion, ranking 115th in the world. After opening below the issue price, Xiaomi’s stock price once rose to the issue price, reaching HKD 17.
This article is contributed by Wang Xin, sharing a comprehensive analysis from writing the simplest Android program by hand to the Android Studio project template. Let’s take a look! Hope everyone enjoys it.
Wang Xin’s Blog:
https://relish.wang/posts/24586/
First, here is a comparison image (the left side is the simplest Android project we manually constructed; the right side is the project template created by Android Studio by default):
Before getting into the main topic, let’s talk about a scenario similar to writing a program (without using an IDE). I recall the time when I first learned Java—how to write a HelloWorld in Notepad.
First, let’s write the simplest Java program:
public class A{
public static void main(String[] args){
System.out.println("Hello Java!");
}
}
Run javac A.java in the current directory (compile A.java file, generating A.class file)
Run A in the current directory (execute class file)
Then you will see Hello Java! printed on the console (terminal)
Developing Java programs with Notepad is indeed simple; it can be explained in just a few sentences. Android programs have so many files (AndroidManifest.xml, the starting Activity, layout files, icon files, strings.xml, styles.xml files), how can we handle that?
If you agree with this statement, it means you have already gotten used to the project template generated by Android Studio (hereinafter referred to as “AS project template”) and have lost your own judgment as an Android developer—what files are actually needed for the simplest Android program.
The AS project template (files/folders not marked in parentheses are gradle-related, which will be explained in detail later):
├── app (main project)
│ ├── app.iml (AS project configuration file)
│ ├── build (project build output directory, contains apk)
│ ├── build.gradle
│ ├── libs (empty folder)
│ ├── proguard-rules.pro (obfuscation configuration)
│ └── src (source code)
├── .gitignore (records files/folders to be ignored by git)
├── build
├── build.gradle
├── demo.iml (AS project configuration file)
├── gradle (folder)
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle
I can tell you, among the files mentioned above, only AndroidManifest.xml is essential (the graphical interface is not mandatory for Android programs, for example, services running in the background). However, for the sake of presentation in this article, we will still keep the Activity. (Having an Activity does not mean that a layout.xml is necessarily required)
Preparation
We have dropped some hints earlier, but we still cannot get into the main topic. We need to prepare three things: Java SDK, Android SDK, Gradle SDK. The first two are essential environments for Android development, which naturally need no further explanation for Android developers. (Please ensure that ANDROID_HOME is configured). The third is the project automation build tool (gradle), which can be used to build Android projects.
-
Configure Gradle
If you are a macOS user, you can configure the latest version of the gradle environment by executing a single command:
brew install gradle
If you are using another operating system, you can refer to:
https://gradle.org/install/
(Ensure that GRADLE_HOME is configured). After configuration, run gradle -v to check if the configuration is complete.
Write the Simplest Android Program
Let’s get straight to the point; first, let’s show the directory structure. The three files that need to be manually written are:
-
MainActivity.java
-
AndroidManifest.xml
-
build.gradle
demo
└─ src
├─ main
│ ├─ java
│ │ └─ wang.relish.demo
│ │ └─ MainActivity.java
│ └─ AndroidManifest.xml
└─ build.gradle
The content of MainActivity.java is as follows:
package wang.relish.demo;
import android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;
public class MainActivity extends Activity{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("Hello, beautiful world!");
setContentView(tv);
}
}
The content of AndroidManifest.xml is as follows:
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="wang.relish.demo">
<application>
<activity anroid:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The content of build.gradle is as follows:
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
}
}
allprojects{
repositories{
jcenter()
google()
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
}
Generating Project Build Files
Next is the moment to witness the miracle.
In the demo (project root directory), execute gradle assemble. If you see the execution result shown in the image below, it means it ran successfully; you can skip the error explanation below and look at the execution completion.
If you encounter the following error, it means you have not configured the ANDROID_HOME environment variable.
There are two solutions:
1) Configure the ANDROID_HOME environment variable. The following image shows the configuration on my computer:
2) Create a local.properties file in the demo (project root directory) and input the path of the AndroidSDK:
sdk.dir=/Users/relish/Library/Android/sdk
The following image shows the configuration on my computer:
Execution Completion
After execution, the directory structure is as follows:
├── .gradle (gradle task related cache)
├── build (files generated after executing the assemble command, apk files are also here)
├── build.gradle
└── src
└── main
├── java
│ └── wang.relish.demo
│ └── MainActivity.java
└── AndroidManifest.xml
You can find the generated apk files in the /build/outputs/apk under debug and release folders. After installing the apk file, the desktop icon looks like this (the default logo displayed varies across different Android versions):
Running it will show the following screen:
Perhaps readers are starting to have some questions here. Why are only these three files enough? Even after building, only two more folders are added? The empty project generated by Android Studio also contains settings.gradle, .idea folder, gradle folder, *.iml files, gradlew, gradlew.bat, etc., so many files!
From Simple to Complex
If you think this article ends here, then you are thinking too simply. Don’t worry, the main part is starting! Next, I will explain how this project gradually transforms into the Android Studio’s default created project.
-
Gradle Wrapper
Gradle Wrapper is a layer of packaging for Gradle, making it easier to unify the version of Gradle used in team development. In our project development process, we use the Wrapper way. Therefore, the gradle-related commands used before are recommended to be changed to the commands of gradlew (for example: change gradle assemble to ./gradlew assemble).
In the demo (project root directory), execute gradle wrapper, and after completion, the following files/folders will be added to the directory structure:
├─ gradle
│ └─ wrapper
│ ├─ gradle-wrapper.jar
│ └─ gradle-wrapper.properties
├─ gradlew
└─ gradlew.bat
-
gradlew and gradlew.bat are executable scripts for Linux and Windows, respectively.
-
gradle-wrapper.jar is the jar package that implements specific business logic. gradlew ultimately uses this jar package to perform related Gradle operations.
-
gradle-wrapper.properties is a configuration file used to configure which version of Gradle to use, etc.
Open the gradle-wrapper.properties file, you will see the following content:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Field Name | Description |
distributionBase | The main directory where the downloaded Gradle compressed package is stored after decompression |
distributionPath | The path of the decompressed Gradle compressed package relative to distributionBase |
zipStoreBase | Same as distributionBase, but for storing the zip compressed package |
zipStorePath | Same as distributionPath, but for storing the zip compressed package |
distributionUrl | The download address of the Gradle release compressed package |
Here, we only need to focus on distributionUrl, this field determines which Gradle version your gradle wrapper depends on.
Why Use Wrapper? What Are the Benefits of Using Wrapper?
Because using the gradle wrapper way does not require pre-downloading gradle, but will automatically decide whether to download gradle online based on local cache status. Moreover, if a developer’s computer does not have gradle installed/configured, he/she can still execute gradle-related tasks by executing the gradlew command. Additionally, the wrapper specifies the Gradle version to be used, so in team development, the gradlew command runs the same version of Gradle for everyone. This avoids differences in execution results caused by different Gradle versions configured on each team member’s computer.
-
Multi-Project Configuration
settings.gradle
Up to this point, let’s take a look at our project directory structure:
├─ .gradle (folder)
├─ build (folder, can be deleted)
├─ gradle (folder)
├─ gradlew
├─ gradlew.bat
├─ build.gradle
├─ local.properties (optional)
└─ src
└─ main
├─ java
│ └─ wang.relish.demo
│ └─ MainActivity.java
└─ AndroidManifest.xml
Upon careful observation, we find that the settings.gradle file is missing, and the src directory should be wrapped in an app directory… Even so, the app is still missing some other files… Don’t worry, we will analyze it step by step.
The settings.gradle file is mostly used to configure subprojects (modules). However, since our “simplest Android program” is a single project, settings.gradle is not required. However, in actual development, projects (Projects) are mostly multi-project (modules), so the AS project template will be configured with settings.gradle by default, and the main project is also wrapped into a subproject (module) named app.
The content of settings.gradle is also very simple:
include ':app'
If there are multiple modules, separate them with commas (but we don’t need that this time):
include ':app', ':module1', ':module2'
Subproject Configuration
In the demo (project root directory), create an app folder, and move/cut the src folder and build.gradle from the demo (project root directory) into the newly created app directory.
At this point, if you execute ./gradlew assemble again, you can already generate the apk file. But it still feels a bit different from the AS project template—the root directory is missing a build.gradle file. The current project directory:
├─ .gradle (folder)
├─ gradle (folder, contains wrapper configuration)
├─ gradlew
├─ gradlew.bat
├─ local.properties (optional)
├─ settings.gradle (multi-project configuration)
└─ app
├─ build.gradle (moved from the root directory)
└─ src (source code folder)
Since this is a multi-project configuration, the configuration needed for each subproject (module) can be made into project (project) configuration to reduce duplicate code. Therefore, we will move some contents from ./app/build.gradle to the build.gradle in the root directory.
The content of ./build.gradle:
buildscript { // A place for preparing and initializing related configuration dependencies for the project before the project builds
repositories { // Configure the repository address where the required gradle plugins are located
jcenter()
google()
}
dependencies { // Configure the required gradle plugin dependencies
classpath 'com.android.tools.build:gradle:3.1.3' // Android Gradle plugin
}
}
allprojects{
repositories{
jcenter()
google()
}
}
The content of ./app/build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
}
We can see that the build.gradle in the root directory is already very similar to the AS project template, the difference is the following code:
task clean(type: Delete) {
delete rootProject.buildDir
}
Here, a gradle task named clean is defined, and we can execute this task by running ./gradlew clean. The execution result will be to delete the build folder of the main project. Forget to mention, now if we execute ./gradlew assemble, the build folder will be generated in the app directory. Executing ./gradlew clean will delete the build folder under the app directory.
-
Other Unmentioned Files
In fact, by this point, the project we manually modified is already roughly the same as the AS project template. Because the focus of this article is actually on the gradle build tool and what files are needed for the simplest Android project. However, since there are still some files not mentioned here, I decided to briefly introduce them.
File | Description |
*.iml, .idea folder | Project configuration files for JetBrain’s IDEA series IDEs (Android Studio is based on IntelliJ IDEA), can be deleted, they will be automatically generated next time you open with Android Studio |
gradle.properties | Can place global constant declarations related to gradle and project runtime memory settings, etc. |
local.properties | Declare the paths of AndroidSDK and NDK |
app/proguard-rules.pro | Code obfuscation configuration |
app/libs | Store jar/aar packages |
.gitignore, app/.gitignore | Record files/folders to be ignored by git |
The contents that are usually needed in the .gitignore file are:
.gradle/
.idea/
build/
*.iml
local.properties
The contents that are usually needed in the app/.gitignore file are:
build/
*.iml
App Main Project
Up to this point, the differences between the project we manually modified and the AS project template are mainly the content of the files in the app (main project directory) and the directory structure.
The differences have been marked with yellow and blue frames. The files marked with yellow frames have been introduced in the previous text. Now let’s introduce the files/folders in the blue frame.
-
Unit Tests
The androidTest folder and the test folder are the directories related to Android unit tests and Java unit tests, respectively. The testing frameworks used in the AS template project are the Java unit testing framework junit, AndroidJUnit, and the Android UI automation testing framework espresso.
Due to space limitations, the usage of unit tests will not be described in detail in this article. Interested readers can refer to the cited materials at the end of the article or search for relevant materials for self-study.
-
Resource Files
All resource file folders have a form of -v[api-level] and -[language abbreviation] for internationalization resources, such as: drawable-zh-ldpi, values-en, drawable-v21.
drawable
Stores image/icon files and style-related files
Folder | Description |
drawable | Stores selector, shape, vector files |
drawable-mdpi | Medium resolution icons (72*72, 320*480) |
drawable-hdpi | High resolution icons (standard size: 48*48, corresponding phone resolution: 480*800) |
drawable-xhdpi | Extra high resolution icons (96*96, 720*1280) |
drawable-xxhdpi | Extra extra high resolution icons (144*144, 1080*1920), mainstream resolution |
drawable-xxxhdpi | Extra extra extra high resolution icons (192*192, 3840*2160) |
drawable-v19 | Styles/icons specifically set for Android 4.4 (API19) and above |
drawable-v21 | Styles/icons specifically set for Android 5.0 (API21) and above |
drawable-v24 | Styles/icons specifically set for Android 7.0 (API24) and above |
For example, ic_avatar.png has different icons in drawable-v19 and drawable-v21, if the running phone is Android 5.1 (API22), it will load ic_avatar.png from drawable-v21; but if ic_avatar.png is only placed in drawable-v19 and drawable-v21 is absent, then the phone will load ic_avatar.png from drawable-v19; if the folder named drawable-v[API-version] does not contain ic_avatar.png, it will load ic_avatar.png from the corresponding resolution folder; if ic_avatar.png is also missing from the folders of each resolution, it will load ic_avatar.png from the drawable folder.
The loading order of icons:
drawable-v[corresponding high API] -> drawable-v[low API] -> drawable-[corresponding high resolution] dpi -> drawable-[low resolution] dpi -> drawable
layout
Folder | Description |
layout | The default loaded layout file |
layout-land | The layout file loaded in landscape mode |
layout-port | The layout file loaded in portrait mode |
The loading order of layout files:
(Depends on the screen state) layout-land or layout-port -> layout
mipmap
Usage is the same as drawable. The difference is that the mipmap folder is exclusively for placing the app’s logo icon.
values
AS project template:
File | Description |
colors.xml | Color value constants |
strings.xml | String constants |
layout-port | Style constants |
In addition, there can be the following files:
File | Description |
ids.xml | ID constants (int type) |
arrays.xml | Array constants |
attrs.xml | Attribute declarations, used for custom views |
dimen.xml | Size constants. Length (dp), font size (sp), pixel value (px), etc. |
In fact, you don’t need to be rigid about these filenames; you can name the files whatever you want, and even the content of these files can be written in a single file (but the recommended practice is still to write them separately, each serving its purpose):
<resources>
<!-- from arrays.xml -->
<string-array name="sex">
<item>Female</item>
<item>Male</item>
</string-array>
<!-- from attrs.xml -->
<declare-styleable name="customView">
<attr name="customAttr" format="string" />
</declare-styleable>
<!-- from colors.xml -->
<color name="colorPrimary">#3F51B5</color>
<!-- from dimen.xml -->
<dimen name="title_text_size">16sp</dimen>
<!-- from strings.xml -->
<string name="app_name">demo</string>
<!-- from styles.xml -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- ... -->
</resources>
Other Resource Files
There are still some common resource files/folders that have not been mentioned, which will be listed in the table below:
Folder | Description |
anim | Animation files |
raw | Media files (audio, video files) |
xml | Other xml type files |
menu | Menu files |
-
app/build.gradle
The content of the app/build.gradle file in the AS project template:
apply plugin: 'com.android.application' // Apply the Android Gradle plugin. (Some netizens describe it as "declaring it as an Android program" which is completely nonsense)
android { // An extension added to the Project object by the Android Gradle plugin
compileSdkVersion 26 // Compile version
defaultConfig {
applicationId "wang.relish.demo" // Application package name
minSdkVersion 14 // The minimum API version of the phone that this app can run on
targetSdkVersion 26 // The maximum version that allows the use of new features. In other words, phones with API 27 and above can also install this app, but only the new features of API 26 and below
versionCode 1 // App version number
versionName "1.0" // App version number. It can be any string. The industry standard uses the format xxx.xxx.xxx.
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // Unit test: AndroidJUnit
}
buildTypes {
release {
// Whether to obfuscate
minifyEnabled false
// The location of the obfuscation file
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies { // Declare dependencies
implementation fileTree(dir: 'libs', include: ['*.jar']) // Dependencies on jar packages located in app/libs/
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12' // Unit test: junit
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' // Android UI automation testing framework
}
implementation vs compile
Since Android Gradle plugin 3.0, it is recommended to use implementation and api to replace the original compile. Theoretically, you can replace all compile with api. Let’s discuss their differences:
Command | Description |
compile | The contents of the module will leak its dependencies to external |
api | Same as compile |
implementation | The module will not expose its dependency module content through its interface to the outside. It is recommended to use implementation for dependencies (instead of api or compile), which will greatly improve the build time of the project |
Other changes:
-
provided -> compileOnly
-
apk -> runtimeOnly
-
testCompile -> testImplementation // Dependencies when compiling test cases, will not be packaged into the released product
-
androidTestCompile -> androidTestImplementation // Dependencies when compiling test cases, will not be packaged into the released product
-
AndroidManifest.xml
The content of the AndroidManifest.xml file in the AS project template:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="wang.relish.demo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher" // App desktop logo. If not set, it defaults to the Android system's built-in icon (which looks different across Android versions).
android:label="@string/app_name" // App name. If not set, it defaults to the full class name of the launching Activity
android:roundIcon="@mipmap/ic_launcher_round" // A new feature of Android O, the round icon
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
This article originally aimed to introduce a tutorial on writing the simplest Android program without using an IDE. As I was writing, I ended up explaining the directory structure and file functions of the Android Studio template project. Unconsciously, I have written so much. I have also gradually expanded our simplest Android program into the Android Studio template project. I hope that readers can appreciate the tremendous help that Android Studio has brought to our Android development work while understanding what Android Studio has done for us.
Cited Materials
Wrapper (gradlew):
https://www.zybuluo.com/xtccc/note/275168
Gradle Official Documentation:
https://docs.gradle.org/current/dsl/index.html
How to Start Android Unit Testing?
https://www.jianshu.com/p/bc99678b1d6e
Adaptive Icons:
https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive
Migrating to Plugin 3.0.0:
https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#new_configurations
Welcome long press the image below -> recognize the QR code in the image
Or scan the code to follow my public account