- Published on
Android Fundamentals - Essential Knowledge
- Authors
- Name
- Dan Tech
- @dan_0xff
This is the foundational knowledge, most important for all Android developers to master. You can skip reading this blog and refer to the sample source code I have on GitHub here!
Four elements of all Android applications (The 4 Pillars of an Android Application)
Activity
Activity is the most basic, core component for building an Android Mobile application. Each Android Activity represents a specific screen that the user can interact with. In some special cases, an Activity can be transparent and not receive interaction from the user, however, to avoid digressing during the learning process, I will not mention that issue in this Android programming course.
Role of Activity
- Basic component for building the application screen interface
- Where to access, allocate, and manage application resources through the Activity's Lifecycle
- Receive and process events from users (Touch, Sensor, Vibrate, ..)
- Is a bridge to communicate with the operating system (Request permissions, open deep-links, payment, …)
How to start an Activity
To start any Activity in Android, we need the following elements:
- Context: Context here can be Application Context or Activity Context, or Fragment Context, or any Context in the program
- Main Thread: Executing the start of an Activity can only be successful when executed on the Main Thread. Details about the Main thread will be covered in later lessons in the course.
startActivity(Intent(context, NewActivity::class.java))
Service
Android Service is a component in the Android Framework that allows the application to run tasks in the background.
Based on features, it can be divided into 3 types of Services in Android
Foreground Service
- Foreground Service: Is a Service that runs in the background, but the User can recognize that there is a Service running in the background at a certain time. These types of Services can include: Download file, Play audio, Sync Data, … Foreground Services in Android have a longer lifecycle, and in the case where this Service is killed by the operating system, it will automatically restart and continue running.
Background Service
- Background Service: Is a Service that runs in the background, but the User is not aware that a Service is running in the background. For example, a Service that collects logs and sends them to the server, a Service that scans device memory, a Service that cleans up garbage, …
Bound Service
- Bound Service: Is a Service that runs in the background, but this Service is bound to one or more specific Application Components - may be an Activity, or another Service. Bound Service supports advanced features that we are not in a hurry to use in this program. At the moment, you can accept that Bound Service is a special setup so that the Service can bind to other Application Components to interact and exchange data. Bound Service can also support Interprocess Communication (IPC)
Based on code design, we only have a single Service Class with full options to create the 3 types of Services above.
class ForegroundService : Service() {
private val channelId = "ForegroundServiceChannel"
private val notificationId = 1
private val sharedPrefFile = "com.creative.androidfundamentals"
private val dataField = "dataField"
private var job: Job? = null
private var init = -1
override fun onCreate() {
super.onCreate()
Log.d("ForegroundService", "onCreate $this")
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
startForegroundService()
return START_STICKY // START_NOT_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
channelId,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
private fun startForegroundService() {
val notification = getNotification(0)
startForeground(notificationId, notification)
job = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
val sharedPref: SharedPreferences = getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE)
val data = sharedPref.getInt(dataField, 0)
init += 1
sharedPref.edit().putInt(dataField, data + 1).apply()
val manager = getSystemService(NotificationManager::class.java)
manager.notify(notificationId, getNotification(data))
Log.d("ForegroundService", "Data: $data ${this@ForegroundService}")
if (init >= 20) {
Log.d("ForegroundService", "Stop service ${this@ForegroundService}")
stopSelf()
return@launch
}
delay(3000)
}
}
}
private fun getNotification(data: Int): Notification {
return NotificationCompat.Builder(this, channelId)
.setContentTitle("Foreground Service")
.setContentText("Data: $data")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build()
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
Log.d("ForegroundService", "onDestroy $this")
}
}
Example of using Android Service - Android Mastery Course
In case startService is called multiple times continuously. The Service will not be re-initialized, but the onStartCommand function will be called again.
In this program, to avoid rambling, we will not perform examples related to Bound Service!
Broadcast Receiver
Broadcast Receiver helps Android applications send and receive data. This sending and receiving of data can be done within an app (Data is sent from a Context to the Broadcast Receiver) or between applications.
In a real project, we can use Broadcast Receiver in the following cases:
- Device restart event ACTION_BOOT_COMPLETED
- Battery change event ACTION_BATTERY_LEVEL_CHANGED
- Screen brightness change event ACTION_SCREEN_ON
- Screen on event ACTION_SCREEN_ON
- Schedule Notification using AlarmManager
Refer to the Broadcast Receiver code example according to the code sample below:
class SimpleReceiver : BroadcastReceiver() {
companion object {
const val SIMPLE_ACTION = "com.creative.androidfundamentals.SIMPLE_ACTION"
}
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("SimpleReceiver", "SimpleReceiver onReceive ${intent?.action}")
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context?.startForegroundService(Intent(context, ForegroundService::class.java))
} else {
context?.startService(Intent(context, ForegroundService::class.java))
}
} else if (intent?.action == SIMPLE_ACTION) {
// Do something
}
}
}
// MainActivity
...
registerReceiver(SimpleReceiver(), IntentFilter(SimpleReceiver.SIMPLE_ACTION))
sendBroadcast(Intent(SimpleReceiver.SIMPLE_ACTION))
...
Content Provider
Content Provider is an Android tool that provides Developers access to and management of data.
This data can be shared between Apps within the operating system, or only accessed and used internally.
In this course, we will learn how to use Content Provider to access data that is shared commonly within the Android application: Contacts, SMS, Call history, …
Using Content Provider to create and manage Local data is no longer a suitable solution, instead we can use Room Database, or Data Store (Except for advanced cases, you need to share your app's data with third parties)
Code example to retrieve SMS in the phone:
fun getSmsHistory(contentResolver: ContentResolver): List {
val smsList = mutableListOf()
val cursor: Cursor? = contentResolver.query(
// URI pointing to SMS table
Telephony.Sms.CONTENT_URI,
// projection: columns to retrieve
null,
// selection: WHERE condition
null,
// selectionArgs: Where condition parameters
null,
// sortOrder: Sort order
Telephony.Sms.DATE + " DESC"
)
cursor?.use {
val bodyIndex = it.getColumnIndex(Telephony.Sms.BODY)
val addressIndex = it.getColumnIndex(Telephony.Sms.ADDRESS)
val dateIndex = it.getColumnIndex(Telephony.Sms.DATE)
while (it.moveToNext()) {
val smsBody = it.getString(bodyIndex)
val smsAddress = it.getString(addressIndex)
val smsDate = it.getLong(dateIndex)
smsList.add("SMS from $smsAddress at $smsDate: $smsBody")
}
}
return smsList
}
Other important factors
Android Manifest
This is the file used to declare the components of the application
- Permission
- Activity
- Broadcast Receiver
- Service
In an Android project, there can be many different Android Manifest files. However, when building, all Android Manifest files will be merged into one to ensure consistency.
Android Resources, Assets
Android Resources is a folder containing resources pre-installed with the application. Can list out: Animation, Layout, Color, String, Theme, Array, Font, Drawable …
Assets is a folder containing resources pre-installed with the application. However, to access Assets, you must go through the URI path, not through Resources (R)
Gradle Build
Gradle Build (gradle.build file) is a system of config files to define the libraries, dependencies that a module is using. At the same time configure the rules, features to build the platform of the application.
Within the scope of this course, we are not in a hurry to study the Gradle Build system in depth. Let's accept this as a system that helps us configure the project, install the necessary libraries and dependencies for a module to function correctly.
Local File System
When an Android application is installed on the device, each application will occupy a disk memory partition whose main folder name is the package name of the application.
Inside this package folder there are many other subfolders, however there are 2 folders that developers are allowed to read and write to without having to request permission from the user.
Folder Cache
Folder cache contains files that we do not need to keep persistent. These files will be automatically cleaned by the system without our intervention.
Folder Files
Folder files is where files needed for app operation are stored. Files when saved to this folder will not be automatically cleaned by the system. The Developer needs to actively clean up and optimize this directory to reduce app size.
Visual example of Local File system
lifecycleScope.launch(Dispatchers.IO) {
val filesPath = "${application.filesDir.absolutePath}/"
val fileInsideFiles = File(filesPath, "fileInsideFiles.txt")
if (!fileInsideFiles.exists()) {
fileInsideFiles.createNewFile()
fileInsideFiles.writeText("Hello from fileInsideFiles.txt")
}
val cachePath = "${application.cacheDir.absolutePath}/"
val fileInsideCache = File(cachePath, "fileInsideCache.txt")
if (!fileInsideCache.exists()) {
fileInsideCache.createNewFile()
fileInsideCache.writeText("Hello from fileInsideCache.txt")
}
}
Practice writing File in Android - Android Mastery Course by Dan Tech.