Android Studio Cheat Sheet¶
Android Studio - The Official IDE for Android Development
Android Studio ist die offizielle Integrierte Entwicklungsumgebung (IDE) für Google's Android-Betriebssystem, aufgebaut auf JetBrains' IntelliJ IDEA Software und speziell für Android-Entwicklung konzipiert.
[No text to translate]
# 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
```[No text to translate - would need specific system requirements text]
```bash
# Download from https://developer.android.com/studio
# Run the installer and follow the setup wizard
# Verify installation
android --version
adb --version
```[No text to translate - would need specific download and install instructions]
```bash
# 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"
```[No text to translate - would need specific SDK setup instructions]
```bash
# 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)
```[No text to translate - would need specific project creation instructions]
```kotlin
// 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)
}
}
```[No text to translate - would need specific project template details]
// 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)
```[No text to translate - would need specific activities description]
```kotlin
// 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()
```[No text to translate - would need specific fragments description]
```xml
<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>
```[No text to translate - would need specific linear layout details]
```xml
<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>
```[No text to translate - would need specific constraint layout details]
```kotlin
// 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"))
```[No text to translate - would need specific RecyclerView details]
```kotlin
// 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()
```[No text to translate - would need specific ViewPager2 with Fragments details]
```xml
<!-- 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)
```[No text to translate - would need specific strings resource details]
```xml
<!-- 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)
```[No text to translate - would need specific colors resource details]
```xml
<!-- 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" />
```[No text to translate - would need specific dimensions resource details]
```xml
<!-- 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" />
```[No text to translate - would need specific styles and themes details]
```kotlin
// 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)
```[No text to translate - would need specific SharedPreferences details]
Note: For entries 4-20, no translation was provided as no specific text was given. If you provide the specific text for those sections, I can translate them following the same guidelines.```kotlin
// 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
)
Fehlerbehebung¶
Häufige Probleme¶
# 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-Tipps¶
// 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")
}
¶
// 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")
}
Zusammenfassung¶
Android Studio ist eine leistungsstarke IDE, die umfassende Tools für die Android-Entwicklung bereitstellt. Zu den Schlüsselfunktionen gehören:
- Intelligenter Code-Editor: Erweiterte Codevervollständigung, Refactoring und Analyse
- Visueller Layout-Editor: Drag-and-Drop-Schnittstellendesign mit Constraint-Layout
- APK-Analyzer: Analyse von APK-Größe und -Zusammensetzung
- Schneller Emulator: Schneller und funktionsreicher Android-Emulator
- Instant Run: Änderungen sofort sehen ohne komplette App-Neuerstellung
- Profiling-Tools: Speicher-, CPU- und Netzwerk-Profiler für Leistungsoptimierung
- Testunterstützung: Unit-Testing, Instrumentiertes Testing und UI-Testing-Frameworks
- Versionskontrolle: Integrierte Git-Unterstützung und Integration mit gängigen VCS
- Gradle-Buildsystem: Flexibles und leistungsstarkes Build-Automatisierungssystem
- Plugin-Ökosystem: Umfangreiche Plugin-Unterstützung für erweiterte Funktionalität
Android Studio optimiert den gesamten Android-Entwicklungs-Workflow von Projekterstellen bis zur Bereitstellung und ist damit das unverzichtbare Tool für Android-Entwickler.