Zalecane: API 30.
Dzisiaj poznamy 2 pozostałe podstawowe elementy aplikacji Android:
Activity- Services
- BroadCast receiver
- Content providers (następne ćwiczenia)
Co to są coroutines? Jaka jest ich wartość - przykłady.
-
Utwórz aplikację AsyncApp z pustą aktywnością, w której zdefiniujemy takie komponenty jak TextView, Button oraz ProgressBar w RelativeLayout.
Przykładowy layout:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/btnDoAsync" android:gravity="center" android:text="Value if returned from the Async, will be displayed here" /> <Button android:id="@+id/btnDoAsync" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_margin="16dp" android:text="Start ASYNC task" /> <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/btnDoAsync" android:layout_centerHorizontal="true" android:visibility="gone" /> </RelativeLayout>
-
Przed napisaniem kodu, musimy pobrać dodatkowe bibliotki i dołączyć do projektu (wersje możecie znaleźć na Kotlin Coroutines releases):
dependencies { ... implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" implementation "androidx.activity:activity-ktx:1.5.1" }
Jest kilka opcji uruchamiania zadań w tle, kilka przykładów znajdziemy w colabie dla Kotlin Coroutines i architektura coroutines oraz w najlepszych praktykach.
-
Przejdźmy wspólnie z prowadzącym przez przykład z codelabs o coroutines:
/** * Wait one second then display a snackbar. */ fun updateTaps() { // launch a coroutine in viewModelScope viewModelScope.launch { tapCount++ // suspend this coroutine for one second delay(1_000) // resume in the main dispatcher // _snackbar.value can be called directly from main thread _taps.postValue("$tapCount taps") } }
-
W naszym ćwiczeniu wykorzystamy bardziej złożoną implementację Timera dla zademonstrowania pojęcia równoległości:
package com.example.asyncapp import android.os.CountDownTimer import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class ProgressViewModel : ViewModel() { private val mutableCounter = MutableLiveData<Int>() val counter: LiveData<Int> get() = mutableCounter private val mutableInProgress = MutableLiveData<Boolean>() val inProgress: LiveData<Boolean> get() = mutableInProgress private val timer = object: CountDownTimer(4000, 1000) { override fun onTick(millisUntilFinished: Long) { mutableCounter.value = mutableCounter.value?.plus(1) } override fun onFinish() { mutableInProgress.value = false } } fun start() { mutableCounter.value = 0 mutableInProgress.value = true timer.start() } }
-
Plik
MainActivity.kt
jest nie co bardziej skomplikowany.package com.example.asyncapp class MainActivity : AppCompatActivity() { private val viewModel: ProgressViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val btnDoAsync = findViewById<Button>(R.id.btnDoAsync) val pb = findViewById<ProgressBar>(R.id.progressBar) viewModel.counter.observe(this) { c -> // Update the selected filters UI val tx = findViewById<TextView>(R.id.textView) tx.text = c.toString() } viewModel.inProgress.observe(this) { isInProgress -> if (isInProgress) { pb.visibility = View.VISIBLE } else { pb.visibility = View.GONE } } btnDoAsync.setOnClickListener { viewModel.start() } } }
-
Test the application.
Wykonaj ćwiczenie:
- Colab Kotlin Coroutines;
- przeanalizuj przykłady dla najlepszych praktykach;
- Napisz aplikację z użyciem JobSchedulera.
Notifications w systemie Android są jednym z mechanizmów komunikacji między aplikacjami zainstalowanymi na urządzeniu a użytkownikiem. Powiadomienia wykorzystywane są w przypadku wystąpienia szczególnych zdarzeń, które mogą być ważne i interesujące dla użytkownika. Projektując własny system powiadomień, należy zwrócić uwagę, aby nie były one uciążliwe dla użytkownika. Przykładowymi powiadomieniami stosowanymi w codziennej pracy są:
- aplikacja obsługująca pocztę powiadamia o przychodzącej nowej poczcie; - system Android powiadamia o nowej wersji programu.
Powiadomienia są wyświetlane w górnej części ekranu, jeśli urządzenie jest odblokowane lub, w zależności od ustawień zabezpieczeń, na ekranie blokady, gdy urządzenie jest zablokowane.
Utwórzymy aplikację, która pozwoli nam przesłać powiadomienie z aplikacji i wyświetlić te powiadomienie na ekranie startowym naszego smartfonu.
-
Utwórz aplikację o nazwie NotificationApp.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="50dp" android:text="WSB" android:textAlignment="center" android:textColor="@android:color/holo_green_dark" android:textSize="32sp" android:textStyle="bold" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:onClick="createNotification" android:text="create notification" /> </RelativeLayout>
-
W pliku
MainActivity.kt
dodaj:class MainActivity : AppCompatActivity() { private var count: Int = 0 private val channelId = "10001" private val defaultChannelId = "default" override fun onResume() { super.onResume() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) title = "KotlinApp" count = 0 } fun createNotification(view: View) { count++ val notificationIntent = Intent(applicationContext, MainActivity::class.java) notificationIntent.putExtra("fromNotification", true) notificationIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val builder = NotificationCompat.Builder(applicationContext, defaultChannelId) builder.setContentTitle("My Notification") builder.setContentIntent(pendingIntent) builder.setContentText("Hello World, to jest napis") builder.setSmallIcon(R.drawable.ic_launcher_foreground) builder.setAutoCancel(true) builder.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) builder.setNumber(count) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val importance = NotificationManager.IMPORTANCE_HIGH val notificationChannel = NotificationChannel(channelId, "NOTIFICATION_CHANNEL_NAME", importance) builder.setChannelId(channelId) notificationManager.createNotificationChannel(notificationChannel) } notificationManager.notify(System.currentTimeMillis().toInt(), builder.build()) } }
-
W manifeście (
manifest/AndroidManifest.xml
) dodaj zezwolenia:<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-
(dodatkowe) Zmodyfikuj program by notyfikacje były wyświetlane w innej kolorystyce. Zastanów się jak dodać ikonę/obrazek do powiadomienia oraz wywołać intencję po kliknięciu w powiadomienie. Zapoznaj się z dokumentacją.
BroadcastReceiver to komponent który umożliwia rejestrację zdarzeń systemowych lub aplikacji. Wszyscy odbiorcy zdarzenia są powiadamiani przez środowisko wykonawcze systemu Android o wystąpieniu tego zdarzenia np. możemy otrzymać informację po włączeniu trybu samolotowego, że ten tryb jest uruchomiony. Więcej o broadcast znajdziesz w dokumentacji.
-
Utwórzmy aplikację, która pozwoli nam zmienić status sieci WiFi czy też nie. Stwórz aplikację o nazwie BroadCastReceiver.
-
Layout:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Switch android:id="@+id/wifiSwitch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout>
-
Utwórzmy teraz BroadcastReceiver w pliku
MainActivity.kt
:package com.example.broadcastreceiver class MainActivity : AppCompatActivity() { lateinit var wifiSwitch: Switch lateinit var wifiManager: WifiManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) wifiSwitch = findViewById(R.id.wifiSwitch) wifiSwitch.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { val panelIntent = Intent(Settings.Panel.ACTION_WIFI) startActivityForResult(panelIntent, 1); } else { val panelIntent = Intent(Settings.Panel.ACTION_WIFI) startActivityForResult(panelIntent, 0); } } } override fun onStart() { super.onStart() val intentFilter = IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION) registerReceiver(wifiStateReceiver, intentFilter) } private val wifiStateReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(p0: Context?, p1: Intent?) { Log.println( Log.INFO,"wifi receiver", "received!") Log.println(Log.INFO, "wifi receiver", intent.getIntExtra( WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN ).toString()) when (intent.getIntExtra( WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN )) { WifiManager.WIFI_STATE_ENABLED -> { wifiSwitch.isChecked = true wifiSwitch.text = "WiFi is ON" Toast.makeText(this@MainActivity, "Wifi is On", Toast.LENGTH_SHORT).show() } WifiManager.WIFI_STATE_DISABLED -> { wifiSwitch.isChecked = false wifiSwitch.text = "WiFi is OFF" Toast.makeText(this@MainActivity, "Wifi is Off", Toast.LENGTH_SHORT).show() } WifiManager.WIFI_STATE_UNKNOWN -> { wifiSwitch.text = "WiFi status changed" Toast.makeText(this@MainActivity, "Wifi is Unknown", Toast.LENGTH_SHORT).show() } } } } }
-
W Manifeście dołącz zezwolenie na zmianę statusu WiFi.
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-
(dodatkowe) Dodaj do swojej aplikacji ImageView z logo WiFi. Dostosuj aplikację do pracy w trybie poziomym.
Services czyli usługi systemu Android to składniki aplikacji, które mogą wykonywać długotrwałe operacje w tle i nie zapewniają interfejsu użytkownika. Sterowanie usługami realizowane jest zazwyczaj z Aktywności i do niej przekazywane są wyniki działania usług. Dla systemu operacyjnego Android Usługi mają wyższy priorytet niż Aktywności, dlatego w przypadku braku zasobów ich działanie jest wstrzymywane na samym końcu, gdy brak jest możliwości uruchomienia nowej aplikacji.
Źródło: developer.android.com/guide/components/services
-
Utwórz aplikację o nazwie AlarmManagerApp z dwoma przyciskami buton.
-
Plik
activity_main.xml
powinien wyglądać mniej więcej tak:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" android:padding="16sp"> <Button android:id="@+id/btnStartService" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:text="Start Service Alarm" /> <Button android:id="@+id/btnStopService" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="Cancel Service" /> </LinearLayout>
-
Musimy poinformować, że będziemy chcieli korzystać z alarmu:
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
- Utwórz plik
MainActivity.kt
i wklej poniższe fragmenty kodu w odpowiednie miejsca.
class MainActivity : AppCompatActivity() {
private lateinit var btnStart: Button
private lateinit var btnStop: Button
lateinit var pendingIntent: PendingIntent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStart = findViewById<Button>(R.id.btnStartService)
btnStop = findViewById<Button>(R.id.btnStopService)
btnStart.setOnClickListener {
startService(Intent(this, MyAlarmService::class.java))
Toast.makeText(baseContext, "Start Service", Toast.LENGTH_LONG).show()
}
btnStop.setOnClickListener {
stopService(Intent(this, MyAlarmService::class.java))
Toast.makeText(baseContext, "Stop Service", Toast.LENGTH_LONG).show()
}
}
}
-
Dodajmy teraz plik
MyAlarmService.kt
, a w nim:class MyAlarmService : Service() { private var player: MediaPlayer? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { player = MediaPlayer.create(this, Settings.System.DEFAULT_RINGTONE_URI) player!!.isLooping = true player!!.start() return START_STICKY } override fun onDestroy() { super.onDestroy() player!!.stop() } override fun onBind(intent: Intent?): IBinder? { return null } }
-
W
manifest/AndroidManifest.xml
musimy zadeklarować nasz serwis (pod elementemapplication
):<application ...> <service android:name=".MyAlarmService" /> </application>
-
Przetestuj aplikację.
Wspólnie z prowadzącym omówimy sekcję Extending the Service class oraz przykład DownloadService z tutorialu vogella.com.