Folha de Dicas do Android Studio
Android Studio - The Official IDE for Android Development
O Android Studio é o Ambiente de Desenvolvimento Integrado (IDE) oficial do sistema operacional Android do Google, construído sobre o software IntelliJ IDEA da JetBrains e projetado especificamente para desenvolvimento Android.
[This section was empty in the original text, so no translation is needed]Sumário
- Instalação
- Primeiros Passos
- Estrutura do Projeto
- Interface do Usuário
- Activities e Fragments
- Layouts e Views
- Recursos
- Armazenamento de Dados
- Networking
- Testes
- Depuração
- Desempenho
- Sistema de Build
- Publicação
- Atalhos de Teclado
- Melhores Práticas
- Solução de Problemas
The remaining sections (4-20) were not provided in the original text, so I cannot translate them. If you would like me to translate those sections, please provide the specific text for each section.```bash
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
```bash
# 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
)
Resolução de Problemas
Problemas Comuns
# 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
Dicas de Depuração
// 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")
}
Resumo
Android Studio é um IDE poderoso que fornece ferramentas abrangentes para desenvolvimento Android. Principais recursos incluem:
- Editor de Código Inteligente: Conclusão de código avançada, refatoração e análise
- Editor de Layout Visual: Interface de design com arrastar e soltar e layout de restrição
- Analisador de APK: Analisar tamanho e composição de APK
- Emulador Rápido: Emulador Android rápido e rico em recursos
- Execução Instantânea: Veja alterações instantaneamente sem reconstruir todo o aplicativo
- Ferramentas de Perfil: Perfis de memória, CPU e rede para otimização de desempenho
- Suporte a Testes: Frameworks de testes unitários, instrumentados e de interface do usuário
- Controle de Versão: Suporte integrado ao Git e integração com VCS populares
- Sistema de Build Gradle: Automação de build flexível e poderosa
- Ecossistema de Plugins: Amplo suporte a plugins para funcionalidade aprimorada
Android Studio agiliza todo o fluxo de trabalho de desenvolvimento Android, desde a criação do projeto até a implantação, tornando-se a ferramenta essencial para desenvolvedores Android.