Android Studio Cheatsheet
Android Studio - The Official IDE for Android Development
Android Studio is the official Integrated Development Environment (IDE) for Google's Android operating system, built on JetBrains' IntelliJ IDEA software and designed specifically for Android development.
Table of Contents
- Installation
- Getting Started
- Project Structure
- User Interface
- Activities and Fragments
- Layouts and Views
- Resources
- Data Storage
- Networking
- Testing
- Debugging
- Performance
- Build System
- Publishing
- Keyboard Shortcuts
- Best Practices
- Troubleshooting
Installation
System Requirements
# Windows
# 64-bit Microsoft Windows 8/10/11
# x86_64 CPU architecture; 2nd generation Intel Core or newer, or AMD CPU with support for Windows Hypervisor
# 8 GB RAM or more
# 8 GB of available disk space minimum (IDE + Android SDK + Android Emulator)
# 1280 x 800 minimum screen resolution
# macOS
# macOS 10.14 (Mojave) or higher
# ARM-based chips, or 2nd generation Intel Core or newer with support for Hypervisor.Framework
# 8 GB RAM or more
# 8 GB of available disk space minimum (IDE + Android SDK + Android Emulator)
# 1280 x 800 minimum screen resolution
# Linux
# Any 64-bit Linux distribution that supports Gnome, KDE, or Unity DE; GNU C Library (glibc) 2.31 or later
# x86_64 CPU architecture; 2nd generation Intel Core or newer, or AMD processor with support for AMD Virtualization (AMD-V) and SSSE3
# 8 GB RAM or more
# 8 GB of available disk space minimum (IDE + Android SDK + Android Emulator)
# 1280 x 800 minimum screen resolution
Download and Install
# Download from https://developer.android.com/studio
# Run the installer and follow the setup wizard
# Verify installation
android --version
adb --version
SDK Setup
# Open Android Studio
# Go to Tools > SDK Manager
# Install required SDK platforms and tools
# Command line SDK manager
sdkmanager --list
sdkmanager "platforms;android-33"
sdkmanager "build-tools;33.0.0"
sdkmanager "system-images;android-33;google_apis;x86_64"
Getting Started
Create New Project
# File > New > New Project
# Choose a template (Empty Activity, Basic Activity, etc.)
# Configure project settings:
# - Name: MyApp
# - Package name: com.example.myapp
# - Save location: /path/to/project
# - Language: Java/Kotlin
# - Minimum SDK: API 21 (Android 5.0)
Project Templates
// Empty Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
// Basic Activity with Fragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commitNow()
}
}
}
// Bottom Navigation Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
}
}
Project Structure
MyApp/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/myapp/
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── fragments/
│ │ │ │ ├── adapters/
│ │ │ │ ├── models/
│ │ │ │ └── utils/
│ │ │ ├── res/
│ │ │ │ ├── layout/
│ │ │ │ ├── values/
│ │ │ │ ├── drawable/
│ │ │ │ ├── mipmap/
│ │ │ │ └── menu/
│ │ │ └── AndroidManifest.xml
│ │ ├── test/
│ │ └── androidTest/
│ ├── build.gradle
│ └── proguard-rules.pro
├── gradle/
├── build.gradle
├── settings.gradle
└── gradle.properties
User Interface
Activities
// Basic Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize views
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
// Handle click
Toast.makeText(this, "Button clicked!", Toast.LENGTH_SHORT).show()
}
}
override fun onStart() {
super.onStart()
// Activity is becoming visible
}
override fun onResume() {
super.onResume()
// Activity is now visible and interactive
}
override fun onPause() {
super.onPause()
// Activity is partially obscured
}
override fun onStop() {
super.onStop()
// Activity is no longer visible
}
override fun onDestroy() {
super.onDestroy()
// Activity is being destroyed
}
}
// Activity with Intent
class SecondActivity : AppCompatActivity() {
companion object {
const val EXTRA_MESSAGE = "extra_message"
fun newIntent(context: Context, message: String): Intent {
return Intent(context, SecondActivity::class.java).apply {
putExtra(EXTRA_MESSAGE, message)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val message = intent.getStringExtra(EXTRA_MESSAGE)
findViewById<TextView>(R.id.textView).text = message
}
}
// Starting an Activity
val intent = SecondActivity.newIntent(this, "Hello from MainActivity!")
startActivity(intent)
// Starting Activity for Result
private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data?.getStringExtra("result_data")
// Handle result
}
}
launcher.launch(intent)
Fragments
// Basic Fragment
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val button = view.findViewById<Button>(R.id.button)
button.setOnClickListener {
// Handle click
}
}
}
// Fragment with Arguments
class DetailFragment : Fragment() {
companion object {
private const val ARG_ITEM_ID = "item_id"
fun newInstance(itemId: String) = DetailFragment().apply {
arguments = Bundle().apply {
putString(ARG_ITEM_ID, itemId)
}
}
}
private val itemId: String by lazy {
arguments?.getString(ARG_ITEM_ID) ?: ""
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_detail, container, false)
}
}
// Fragment Transaction
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailFragment.newInstance("123"))
.addToBackStack(null)
.commit()
Layouts and Views
Linear Layout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="18sp" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click Me" />
</LinearLayout>
Constraint Layout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecyclerView
// Adapter
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = items[position]
}
override fun getItemCount() = items.size
}
// Setup RecyclerView
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = MyAdapter(listOf("Item 1", "Item 2", "Item 3"))
ViewPager2 with Fragments
// Fragment Adapter
class ViewPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> FirstFragment()
1 -> SecondFragment()
2 -> ThirdFragment()
else -> FirstFragment()
}
}
}
// Setup ViewPager2
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
viewPager.adapter = ViewPagerAdapter(this)
// With TabLayout
val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = "Tab ${position + 1}"
}.attach()
Resources
Strings
<!-- res/values/strings.xml -->
<resources>
<string name="app_name">My App</string>
<string name="hello_world">Hello World!</string>
<string name="welcome_message">Welcome, %1$s!</string>
<plurals name="numberOfItems">
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
</resources>
<!-- Usage in Kotlin -->
val appName = getString(R.string.app_name)
val welcomeMessage = getString(R.string.welcome_message, "John")
val itemCount = resources.getQuantityString(R.plurals.numberOfItems, count, count)
Colors
<!-- res/values/colors.xml -->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
<!-- Usage in XML -->
<TextView
android:textColor="@color/purple_500"
android:background="@color/teal_200" />
<!-- Usage in Kotlin -->
val color = ContextCompat.getColor(this, R.color.purple_500)
textView.setTextColor(color)
Dimensions
<!-- res/values/dimens.xml -->
<resources>
<dimen name="margin_small">8dp</dimen>
<dimen name="margin_medium">16dp</dimen>
<dimen name="margin_large">24dp</dimen>
<dimen name="text_size_small">12sp</dimen>
<dimen name="text_size_medium">16sp</dimen>
<dimen name="text_size_large">20sp</dimen>
</resources>
<!-- Usage in XML -->
<TextView
android:layout_margin="@dimen/margin_medium"
android:textSize="@dimen/text_size_large" />
Styles and Themes
<!-- res/values/styles.xml -->
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
</style>
<style name="CustomButton" parent="Widget.MaterialComponents.Button">
<item name="android:textColor">@color/white</item>
<item name="backgroundTint">@color/purple_500</item>
<item name="cornerRadius">8dp</item>
</style>
</resources>
<!-- Usage in XML -->
<Button
style="@style/CustomButton"
android:text="Custom Button" />
Data Storage
SharedPreferences
// Save data
val sharedPref = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
with(sharedPref.edit()) {
putString("username", "john_doe")
putInt("user_id", 123)
putBoolean("is_logged_in", true)
apply()
}
// Read data
val username = sharedPref.getString("username", "")
val userId = sharedPref.getInt("user_id", 0)
val isLoggedIn = sharedPref.getBoolean("is_logged_in", false)
Room Database
// Entity
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val name: String,
val email: String
)
// DAO
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): LiveData<List<User>>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}
// Usage
class UserRepository(private val userDao: UserDao) {
val allUsers: LiveData<List<User>> = userDao.getAllUsers()
suspend fun insert(user: User) {
userDao.insertUser(user)
}
}
File Storage
// Internal storage
val filename = "myfile.txt"
val fileContents = "Hello, Android!"
openFileOutput(filename, Context.MODE_PRIVATE).use {
it.write(fileContents.toByteArray())
}
// Read from internal storage
val content = openFileInput(filename).bufferedReader().useLines { lines ->
lines.fold("") { some, text ->
"$some\n$text"
}
}
// External storage
if (isExternalStorageWritable()) {
val file = File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "myfile.txt")
file.writeText("Hello, External Storage!")
}
private fun isExternalStorageWritable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
Networking
Retrofit
// Add dependencies to build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Data model
data class Post(
val id: Int,
val title: String,
val body: String,
val userId: Int
)
// API interface
interface ApiService {
@GET("posts")
suspend fun getPosts(): List<Post>
@GET("posts/{id}")
suspend fun getPost(@Path("id") id: Int): Post
@POST("posts")
suspend fun createPost(@Body post: Post): Post
@PUT("posts/{id}")
suspend fun updatePost(@Path("id") id: Int, @Body post: Post): Post
@DELETE("posts/{id}")
suspend fun deletePost(@Path("id") id: Int): Response<Unit>
}
// Retrofit instance
object RetrofitInstance {
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
val api: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
// Usage in ViewModel
class PostViewModel : ViewModel() {
private val _posts = MutableLiveData<List<Post>>()
val posts: LiveData<List<Post>> = _posts
fun fetchPosts() {
viewModelScope.launch {
try {
val response = RetrofitInstance.api.getPosts()
_posts.value = response
} catch (e: Exception) {
// Handle error
}
}
}
}
OkHttp
// Add dependency
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
// Basic usage
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle failure
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val responseBody = response.body?.string()
// Handle response
}
}
})
// With interceptors
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
chain.proceed(request)
}
.build()
Testing
Unit Testing
// Add dependencies
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.6.1'
// Test class
class CalculatorTest {
private lateinit var calculator: Calculator
@Before
fun setUp() {
calculator = Calculator()
}
@Test
fun addition_isCorrect() {
val result = calculator.add(2, 2)
assertEquals(4, result)
}
@Test
fun division_byZero_throwsException() {
assertThrows(ArithmeticException::class.java) {
calculator.divide(10, 0)
}
}
}
// Testing with Mockito
class UserRepositoryTest {
@Mock
private lateinit var apiService: ApiService
@Mock
private lateinit var userDao: UserDao
private lateinit var repository: UserRepository
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
repository = UserRepository(apiService, userDao)
}
@Test
fun getUser_returnsUser() = runTest {
val user = User(1, "John", "john@example.com")
`when`(apiService.getUser(1)).thenReturn(user)
val result = repository.getUser(1)
assertEquals(user, result)
verify(apiService).getUser(1)
}
}
Instrumented Testing
// Add dependencies
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
// Test class
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun button_click_showsToast() {
onView(withId(R.id.button))
.perform(click())
onView(withText("Button clicked!"))
.inRoot(withDecorView(not(activityRule.scenario.onActivity { it.window.decorView })))
.check(matches(isDisplayed()))
}
@Test
fun recyclerView_displaysItems() {
onView(withId(R.id.recyclerView))
.check(matches(isDisplayed()))
onView(withId(R.id.recyclerView))
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(2))
onView(withText("Item 3"))
.check(matches(isDisplayed()))
}
}
Debugging
Logging
// Log levels
Log.v(TAG, "Verbose message")
Log.d(TAG, "Debug message")
Log.i(TAG, "Info message")
Log.w(TAG, "Warning message")
Log.e(TAG, "Error message")
// With exception
try {
// Some code
} catch (e: Exception) {
Log.e(TAG, "Error occurred", e)
}
// Timber (recommended logging library)
implementation 'com.jakewharton.timber:timber:5.0.1'
// Initialize in Application class
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
// Usage
Timber.d("Debug message")
Timber.e(exception, "Error occurred")
Debugging Tools
// Breakpoints
// Set breakpoints by clicking on the line number gutter
// Conditional breakpoints: right-click on breakpoint
// Debug console
// Use "Evaluate Expression" (Alt+F8) to execute code during debugging
// Layout Inspector
// Tools > Layout Inspector
// Inspect view hierarchy and properties
// Database Inspector
// View > Tool Windows > Database Inspector
// Inspect Room database contents
// Network Inspector
// View > Tool Windows > Network Inspector
// Monitor network requests and responses
Performance
Memory Profiling
// Memory Profiler
// View > Tool Windows > Profiler
// Select your app and start a memory profiling session
// Detecting memory leaks
class MainActivity : AppCompatActivity() {
private var leakyHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Bad: This creates a memory leak
leakyHandler = Handler(Looper.getMainLooper())
leakyHandler?.postDelayed({
// This runnable holds a reference to the activity
}, 60000)
}
override fun onDestroy() {
super.onDestroy()
// Good: Clean up to prevent memory leaks
leakyHandler?.removeCallbacksAndMessages(null)
leakyHandler = null
}
}
// Using WeakReference
class MyHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
private val activityRef = WeakReference(activity)
override fun handleMessage(msg: Message) {
val activity = activityRef.get()
if (activity != null && !activity.isFinishing) {
// Handle message
}
}
}
CPU Profiling
// CPU Profiler
// View > Tool Windows > Profiler
// Select your app and start a CPU profiling session
// Optimizing RecyclerView
class OptimizedAdapter : RecyclerView.Adapter<OptimizedAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// Reuse view holders
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Minimize work in onBindViewHolder
holder.bind(items[position])
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.textView)
fun bind(item: String) {
textView.text = item
}
}
}
// Using DiffUtil for efficient updates
class MyDiffCallback(
private val oldList: List<String>,
private val newList: List<String>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
}
// Update adapter with DiffUtil
fun updateItems(newItems: List<String>) {
val diffCallback = MyDiffCallback(items, newItems)
val diffResult = DiffUtil.calculateDiff(diffCallback)
items.clear()
items.addAll(newItems)
diffResult.dispatchUpdatesTo(this)
}
Build System
Gradle Configuration
// app/build.gradle
android {
compileSdk 33
defaultConfig {
applicationId "com.example.myapp"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
debuggable true
}
}
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
}
paid {
dimension "version"
applicationIdSuffix ".paid"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
dataBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
Build Variants
# Build debug APK
./gradlew assembleDebug
# Build release APK
./gradlew assembleRelease
# Build specific flavor
./gradlew assembleFreeDebug
./gradlew assemblePaidRelease
# Install on device
./gradlew installDebug
# Run tests
./gradlew test
./gradlew connectedAndroidTest
# Clean build
./gradlew clean
Publishing
Signing Configuration
// app/build.gradle
android {
signingConfigs {
release {
storeFile file('path/to/keystore.jks')
storePassword 'store_password'
keyAlias 'key_alias'
keyPassword 'key_password'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Generate Signed APK
# Using Android Studio
# Build > Generate Signed Bundle / APK
# Choose APK or Android App Bundle
# Select keystore and enter passwords
# Choose build variant
# Click Finish
# Using command line
./gradlew assembleRelease
# Generate App Bundle (recommended for Play Store)
./gradlew bundleRelease
ProGuard/R8 Configuration
# proguard-rules.pro
# Keep model classes
-keep class com.example.myapp.model.** { *; }
# Keep Retrofit interfaces
-keep interface com.example.myapp.api.** { *; }
# Keep Gson annotations
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.google.gson.** { *; }
# Keep Parcelable classes
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# Remove logging in release builds
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
}
Keyboard Shortcuts
Navigation
# General
Ctrl+Shift+A # Find Action
Ctrl+Shift+N # Find File
Ctrl+Alt+Shift+N # Find Symbol
Ctrl+E # Recent Files
Ctrl+Shift+E # Recent Locations
# Code Navigation
Ctrl+B # Go to Declaration
Ctrl+Alt+B # Go to Implementation
Ctrl+U # Go to Super Method
Ctrl+H # Type Hierarchy
Ctrl+Alt+H # Call Hierarchy
# Search and Replace
Ctrl+F # Find
Ctrl+R # Replace
Ctrl+Shift+F # Find in Path
Ctrl+Shift+R # Replace in Path
Editing
# Code Generation
Alt+Insert # Generate Code
Ctrl+O # Override Methods
Ctrl+I # Implement Methods
Ctrl+Alt+T # Surround With
# Refactoring
Shift+F6 # Rename
Ctrl+Alt+M # Extract Method
Ctrl+Alt+V # Extract Variable
Ctrl+Alt+F # Extract Field
Ctrl+Alt+C # Extract Constant
# Code Formatting
Ctrl+Alt+L # Reformat Code
Ctrl+Alt+O # Optimize Imports
Ctrl+Shift+Up/Down # Move Line Up/Down
Debugging
# Debug Actions
F8 # Step Over
F7 # Step Into
Shift+F8 # Step Out
F9 # Resume Program
Ctrl+F8 # Toggle Breakpoint
Ctrl+Shift+F8 # View Breakpoints
# Run Actions
Shift+F10 # Run
Shift+F9 # Debug
Ctrl+F2 # Stop
Best Practices
Architecture
// MVVM Architecture
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
fun loadUsers() {
viewModelScope.launch {
_loading.value = true
try {
val userList = repository.getUsers()
_users.value = userList
} catch (e: Exception) {
// Handle error
} finally {
_loading.value = false
}
}
}
}
// Repository Pattern
class UserRepository(
private val apiService: ApiService,
private val userDao: UserDao
) {
suspend fun getUsers(): List<User> {
return try {
val users = apiService.getUsers()
userDao.insertUsers(users)
users
} catch (e: Exception) {
userDao.getAllUsers()
}
}
}
// Dependency Injection with Hilt
@HiltAndroidApp
class MyApplication : Application()
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var userRepository: UserRepository
}
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
Performance
// Use ViewBinding instead of findViewById
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
// Handle click
}
}
}
// Lazy initialization
class MyActivity : AppCompatActivity() {
private val adapter by lazy { MyAdapter() }
private val viewModel by viewModels<MyViewModel>()
}
// Use coroutines for background work
class DataRepository {
suspend fun fetchData(): List<Data> = withContext(Dispatchers.IO) {
// Network call or database operation
apiService.getData()
}
}
Security
// Network Security Config
// res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
</domain-config>
</network-security-config>
// AndroidManifest.xml
<application
android:networkSecurityConfig="@xml/network_security_config">
// Certificate Pinning
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
// Secure SharedPreferences
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
Troubleshooting
Common Issues
# Gradle sync issues
# File > Invalidate Caches and Restart
# Delete .gradle folder and rebuild
# Build errors
./gradlew clean
./gradlew build --stacktrace
# Emulator issues
# AVD Manager > Wipe Data
# Cold Boot Now
# Memory issues
# Increase heap size in gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=512m
# Dependency conflicts
./gradlew app:dependencies
# Check for version conflicts and exclude transitive dependencies
Debugging Tips
// Use meaningful log tags
companion object {
private const val TAG = "MainActivity"
}
// Log method entry and exit
override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "onCreate() called")
super.onCreate(savedInstanceState)
// ...
Log.d(TAG, "onCreate() finished")
}
// Use assertions for debugging
assert(user != null) { "User should not be null at this point" }
// Use TODO() for unimplemented code
fun notImplementedYet() {
TODO("This function is not implemented yet")
}
Summary
Android Studio is a powerful IDE that provides comprehensive tools for Android development. Key features include:
- Intelligent Code Editor: Advanced code completion, refactoring, and analysis
- Visual Layout Editor: Drag-and-drop interface design with constraint layout
- APK Analyzer: Analyze APK size and composition
- Fast Emulator: Quick and feature-rich Android emulator
- Instant Run: See changes instantly without rebuilding the entire app
- Profiling Tools: Memory, CPU, and network profilers for performance optimization
- Testing Support: Unit testing, instrumented testing, and UI testing frameworks
- Version Control: Built-in Git support and integration with popular VCS
- Gradle Build System: Flexible and powerful build automation
- Plugin Ecosystem: Extensive plugin support for enhanced functionality
Android Studio streamlines the entire Android development workflow from project creation to deployment, making it the essential tool for Android developers.