Android'deki en yeni kısıtlamalardan sonra tam olarak programlanacak bir alarm nasıl ayarlanır?


27

Not: Burada StackOverflow üzerinde yazılmış çeşitli çözümler denedim (örnek burada). Lütfen aşağıda yazdığım testi kullanarak bulduğunuz çözümün işe yarayıp yaramadığını kontrol etmeden bunu kapatmayın.

Arka fon

Uygulamada, kullanıcının belirli bir zamanda programlanacak bir hatırlatıcı ayarlaması gerektiği için bir gereksinim vardır, bu nedenle uygulama bu zamanda tetiklendiğinde, arka planda küçük bir şey yapar (sadece bazı DB sorgu işlemi) ve hatırlatma hakkında anlatmak için basit bir bildirim.

Geçmişte, nispeten belirli bir zamanda programlanacak bir şeyi ayarlamak için basit bir kod kullandım:

            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
            when {
                VERSION.SDK_INT >= VERSION_CODES.KITKAT -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
                else -> alarmManager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
            }
class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("AppLog", "AlarmReceiver onReceive")
        //do something in the real app
    }
}

Kullanımı:

            val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
            setAlarm(this, timeToTrigger, 1)

Sorun

Şimdi bu kodu yeni Android sürümlerindeki emülatörlerde ve Android 10 ile Pixel 4'te test ettim ve tetiklemiyor gibi görünüyor, ya da sağladığımdan bu yana çok uzun bir süre sonra tetikleniyor. Ben çok iyi biliyorum korkunç davranışları o bazı OEM son görevleri uygulamaları kaldırmayı eklendi, ama bu her iki öykünücülerinde ve Piksel 4 cihazı (stok).

Bir alarm ayarlama hakkında dokümanlar üzerinde okudum, uygulamalar için kısıtlandığını, böylece çok sık meydana gelmeyeceğini, ancak bu, belirli bir zamanda bir alarmın nasıl ayarlanacağını açıklamıyor ve açıklamıyor Google'ın Saat uygulaması nasıl olur? bunu başarabilir.

Sadece bu değil, aynı zamanda anladığım kadarıyla, kısıtlamalar özellikle cihazın düşük güç durumu için uygulanmalıdır, ancak benim durumumda, hem cihazda hem de emülatörlerde bu duruma sahip değildim. Alarmları bir dakika içinde tetiklenecek şekilde ayarladım.

Birçok çalar saat uygulamasının eskisi gibi artık çalışmadığını görünce, dokümanlarda eksik olan bir şey olduğunu düşünüyorum. Bu tür uygulamalara örnek olarak, Google tarafından satın alınan ancak yeni kısıtlamalarla başa çıkacak yeni güncellemeler bulunmayan popüler Timely uygulaması veriliyor ve şimdi kullanıcılar geri istiyor. . Ancak, bazı popüler uygulamalar bunun gibi iyi çalışır .

Ne denedim

Gerçekten alarmın çalıştığını test etmek için, uygulamayı ilk kez yükledikten sonra, cihaz PC'ye bağlıyken (günlükleri görmek için) alarmı bir dakika sonra tetiklemeye çalışırken bu testleri gerçekleştiriyorum:

  1. Uygulamanın kullanıcı tarafından görülebildiği ön planda olup olmadığını test edin. - 1-2 dakika sürdü.
  2. Uygulamanın arka plana ne zaman gönderildiğini test edin (örneğin ana sayfa düğmesini kullanarak) - yaklaşık 1 dakika sürdü
  3. Uygulamanın görevinin son görevlerden ne zaman kaldırıldığını test edin. - 20 dakikadan fazla bekledim ve alarmın tetiklendiğini, günlüklere yazdığını görmedim.
  4. # 3 gibi, ama aynı zamanda ekranı kapatın. Muhtemelen daha kötü olurdu ...

Sonraki şeyleri kullanmaya çalıştım, hepsi işe yaramıyor:

  1. alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)

  2. alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  3. AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  4. yukarıdakilerin herhangi birinin kombinasyonu:

    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)

  5. BroadcastReceiver yerine bir hizmet kullanmayı denedi. Ayrıca farklı bir süreç üzerinde çalıştı.

  6. Uygulamayı pil optimizasyonundan yok saymaya çalıştım (yardım etmedi), ancak diğer uygulamalar buna ihtiyaç duymadığından da kullanmamalıyım.

  7. Bunu kullanarak denedim:

            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
                alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
            AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
  1. Alarmı yeniden programlamak için onTaskRemoved tetikleyicisine sahip bir hizmete sahip olmayı denedim , ancak bu da yardımcı olmadı (hizmet olsa da iyi çalıştı).

Google'ın Saat uygulamasına gelince, tetiklenmeden önce bir bildirim göstermesi dışında özel bir şey görmedim ve ayrıca pil optimizasyon ayarları ekranının "optimize edilmedi" bölümünde de görmüyorum.

Bunun bir hata gibi göründüğünü görünce , sorunu göstermek için örnek bir proje ve video da dahil olmak üzere burada bunu rapor ettim .

Emülatörün birden fazla sürümünü kontrol ettim ve bu davranış API 27'den (Android 8.1 - Oreo) başladı gibi görünüyor. Dokümanlara baktığımda , AlarmManager'ın bahsedildiğini görmüyorum, ancak bunun yerine çeşitli arka plan çalışmaları hakkında yazılmıştır.

Sorular

  1. Bugünlerde göreceli olarak tam bir zamanda tetiklenecek bir şeyi nasıl ayarlayabiliriz?

  2. Nasıl oluyor da yukarıdaki çözümler artık işe yaramıyor? Bir şey mi kaçırıyorum? İzin? Belki bunun yerine bir İşçi kullanmam gerekiyor? Ama o zaman hiç tetiklenmeyebileceği anlamına gelmez mi?

  3. Google "Clock" uygulaması tüm bunların üstesinden nasıl gelir ve yalnızca bir dakika önce tetiklenmiş olsa bile her zaman tam zamanında tetiklenir? Sadece bir sistem uygulaması olduğu için mi? Yerleşik olmayan bir cihaza kullanıcı uygulaması olarak yüklenirse ne olur?

Bunun bir sistem uygulaması olduğu için söylüyorsanız, burada 2 dakika içinde iki kez bir alarmı tetikleyebilecek başka bir uygulama buldum , ancak bazen bir ön plan hizmeti kullanabileceğini düşünüyorum.

EDIT: Burada fikirleri denemek için küçük bir Github deposu yaptı .


EDIT: sonunda hem açık kaynaklı hem de bu sorunu olmayan bir örnek bulundu . Ne yazık ki çok karmaşık ve hala bu kadar farklı kılan (ve benim POC için eklemem gereken en az kod nedir) anlamaya çalışıyorum, son görevleri app kaldırdıktan sonra alarmları planlı kalmasını sağlar


uzun zamandır hizmet üzerinde çalıştım (önermek için bile pro geliştirici değilim), ancak 5 dakikadan daha az alarm ayarlamak gibi durumlarda alarmManager'dan kaçınmanızı önerebilirim, çünkü Android'de bir süre hizmetten sonra kısıtlamalar nedeniyle arka uç her 5 dakikada bir veya 5 dakikadan az olmamak üzere çağrılır. Bunun yerine Handler'ı kullandım. Ve hizmetimi çalıştırmak için arka planda devam ediyor [ github.com/fabcira/neverEndingAndroidService]
Blu

Peki kesin kısıtlamalar nelerdir? Bir tetikleyicinin nispeten hassas bir zaman vererek çalışacağı garanti edilen minimum süre nedir?
android geliştirici

Kesin kısıtlamaları hatırlayamıyorum ama üzerinde çalışırken, arka plan hizmetinin otomatik olarak öldürülmesinin üstesinden gelmek için günlerce googledim. Ve kişisel gözlemden Samsung, Xiaomi, vb. hiç çalışmıyor. Emülatörler için iyi çalışıyor.
Blu

Android Q'da arka plandan etkinlik başlatamayacağınızı biliyorum, ancak durumunuz gibi görünmüyor.
marcinj

@ greeble31 Şimdi denedim. Hangi çözümün çalıştığını görüyorsunuz? Nedense hala işe yaramıyorum. Alarmı ayarladım, uygulamayı son görevlerden kaldırıyorum ve ekran açık ve cihaz bir şarj cihazına bağlı olsa bile alarmın tetiklendiğini görmüyorum. Hem gerçek bir cihazda (Android 10 ile Pixel 4) hem de emülatörde (örneğin API 27) gerçekleşir. Sizin için çalışıyor mu? Lütfen tam kodu paylaşır mısınız? Belki Github'da?
android geliştirici

Yanıtlar:


4

Yapacak hiçbir şeyimiz yok.

Uygulamanız beyaz listeye alınmadığında, son uygulamalardan kaldırıldığında her zaman öldürülür.

Çünkü Orijinal Ekipman Üreticisi (OME) Android uyumluluğunu sürekli ihlal ediyor .

Dolayısıyla, uygulamanız cihazdan beyaz listeye eklenmemişse, uygulamanızın son uygulamalardan kaldırılması durumunda arka plan çalışması alarmları bile tetiklemez .

Bu davranışa sahip cihazların bir listesini burada bulabilirsiniz. Ayrıca bir yan çözüm bulabilirsiniz, Ancak, iyi çalışmaz.


Çinli OEM'lerin bu sorununun farkındayım. Ama yazdığım gibi, emülatör ve Pixel 4 cihazında bile oluyor. Bunu yapan bazı Çinli OEM değil. Lütfen emülatör ve / veya Pixel cihazını kontrol edin. Sorun burada da var. Bir alarm ayarlayın, uygulamayı son görevlerden kaldırın ve alarmın tetiklenmediğini görün. Bunu bir hata olarak görüyorum ve burada rapor ettim (denemek istiyorsanız bir video ve örnek bir proje içerir): issuetracker.google.com/issues/149556385. Açıklamak için sorumu güncelledim. Soru, bazı uygulamaların nasıl başarılı olduğu.
android geliştirici

@androiddeveloper Emülatörde çalışması gerektiğine inanıyorum .. Hangi emülatöre sahipsiniz?
Ibrahim Ali

Ben de inanıncaya kadar inandım. Örneğin, Android Studio'nun neler sunabileceğini API 29'da deneyin. Aynı şey biraz eski sürümlerde de olacağına eminim.
android geliştirici

4

Android R dahil tüm sürümler için işe yarayan garip bir geçici çözüm ( burada örnek ) bulundu :

  1. Bildiride SAW izninin bildirilmesini sağlayın:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Android R'de de bunu vermelisiniz. Daha önce, verilmesi gerektiği gibi görünmüyor, sadece ilan edildi. Bunun R'de neden değiştiğinden emin değilim, ancak SAW'nin arka planda işleri başlatmak için olası bir çözüm olarak gerekli olabileceğini söyleyebilirim. burada Android 10.

  1. Görevlerin ne zaman kaldırıldığını algılayacak bir hizmetinizin olması ve ne zaman yapıldığının tek yaptığı, kendisini kapatmak olan sahte bir Etkinlik açmaktır:
class OnTaskRemovedDetectorService : Service() {
    override fun onBind(intent: Intent?) = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.e("AppLog", "onTaskRemoved")
        applicationContext.startActivity(Intent(this, FakeActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
        stopSelf()
    }

}

FakeActivity.kt

class FakeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("AppLog", "FakeActivity")
        finish()
    }
}

Ayrıca, bu Temayı kullanarak bu Etkinliği kullanıcı tarafından neredeyse görünmez hale getirebilirsiniz:

    <style name="AppTheme.Translucent" parent="@style/Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

Ne yazık ki, bu garip bir çözüm. Bunun için daha hoş bir çözüm bulmayı umuyorum.

Kısıtlama Faaliyete başlama hakkında konuşuyor, bu yüzden şu anki fikrim belki bir saniyelik bir süre için bir ön plan hizmetine başlarsam da yardımcı olacaktır ve bunun için TESTERE iznine bile ihtiyacım olmayacak.

EDIT: Tamam bir ön plan hizmeti ile denedim ( burada örnek ) ve işe yaramadı. Bir Faaliyetin neden çalıştığını bilmiyorum ama bir hizmet değil. Hatta orada alarmı yeniden programlamaya çalıştım ve yeniden programladıktan sonra bile hizmetin biraz kalmasına izin vermeye çalıştım. Ayrıca normal bir hizmet denedim ama görev kaldırıldı gibi tabii ki hemen kapatıldı ve hiç işe yaramadı (arka planda çalıştırmak için bir iş parçacığı oluşturmuş olsa bile).

Denemediğim bir başka olası çözüm, sonsuza kadar veya en azından görev kaldırılana kadar bir ön plan hizmetine sahip olmaktır, ancak bu biraz garip ve bunu kullandığım uygulamaları görmüyorum.

DÜZENLEME: uygulamanın görev kaldırılmadan önce ve biraz sonra bir ön plan hizmeti çalışmayı denedi ve alarm hala çalıştı. Ayrıca bu hizmet, görev kaldırılan olaydan sorumlu olanı olmaya ve gerçekleştiğinde hemen kendini kapatmaya çalıştı ve yine de çalıştı ( burada örnek ). Bu geçici çözümün avantajı, SAW iznine sahip olmanıza gerek olmamasıdır. Dezavantajı, uygulama kullanıcı tarafından görülebilir durumdayken bildirim içeren bir hizmetinizin olmasıdır. Uygulama zaten Etkinlik aracılığıyla ön planda olduğunda bildirimi gizlemek mümkün olup olmadığını merak ediyorum.


EDIT: Android Studio'da bir hata gibi görünüyor ( sürümleri karşılaştıran videolar da dahil olmak üzere burada bildirilmiştir ). Uygulamayı denediğim sorunlu sürümden başlattığınızda, alarmların silinmesine neden olabilir.

Uygulamayı başlatıcıdan başlatırsanız, iyi çalışır.

Alarmı ayarlamak için geçerli kod budur:

        val timeToTrigger = System.currentTimeMillis() + 10 * 1000
        val pendingShowList = PendingIntent.getActivity(this, 1, Intent(this, SomeActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        val pendingIntent = PendingIntent.getBroadcast(this, 1, Intent(this, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        manager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingShowList), pendingIntent)

"PendingShowList" kullanmak zorunda bile değilim. Null kullanımı da tamam.


Ben sadece AndroidQ onReceive () bir aktivite sart istiyorum. SYSTEM_ALERT_WINDOWİzinsiz bunun için herhangi bir geçici çözüm var mı ?
doctorram

2
Google neden her zaman Android geliştiricilerinin hayatlarını basit şeyler üzerinde canlı bir cehenneme dönüştürüyor ?!
doctorram

@doctorram Evet, çeşitli istisnalar hakkında dokümanlarda yazılmıştır: developer.android.com/guide/components/activities/… . Sadece SYSTEM_ALERT_WINDOW'u seçtim çünkü test etmek en kolayı.
android geliştirici

Son düzenlemenizden, uygulamayı son listeden kaldırdıktan sonra alarmları devam ettirmek için bahsettiğiniz herhangi bir geçici çözümü kullanmak zorunda olmadığımız anlamına mı geliyor?
user3410835

Uygulama son listeden kaldırılsa bile her gün sabah 6 ile akşam 7 arasında bir parça kod çalıştırmak istiyorum. WorkManager veya AlarmManager kullanmalıyım ?? Kullanım kodum için aşağıdaki kodu denedim ve işe yaramadı. Aşağıdaki kodla ilgili sorun nedir? calendar.setTimeInMillis (System.currentTimeMillis ()); calendar.set (Takvim.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, pendingIntent);
user3410835

1
  1. Yayınladığınız amacın açık ve Intent.FLAG_RECEIVER_FOREGROUNDbayrağa sahip olduğundan emin olun .

https://developer.android.com/about/versions/oreo/background#broadcasts

Intent intent = new Intent(context, Receiver.class);
intent.setAction(action);
...
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

PendingIntent operation = PendingIntent.getBroadcast(context, 0, intent, flags);
  1. setExactAndAllowWhileIdle()API 23+'yi hedeflerken kullanın .
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, operation);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, operation);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, time, operation);
}
  1. Alarmınızı Ön Plan Servisi olarak başlatın:

https://developer.android.com/about/versions/oreo/background#migration

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
} else {
    context.startService(intent);
}
  1. Ve izinleri unutmayın:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

BroadcastReceiver'ın kendisi Niyeti hiç almıyorsa (ya da yakın zamanda) hizmet için neden önemlidir? Bu ilk adım ... AlarmManagerCompat zaten aynı kodu sunmuyor mu? Uygulamayı son görevlerden kaldırmak da dahil olmak üzere yazdığım testleri kullanarak bunu denediniz mi? Lütfen tüm kodu gösterebilir misiniz? Belki Github'da paylaşırsın?
android geliştirici

@androiddeveloper cevabı güncelledi.
Maksim Ivanov

Hala çalışmıyor gibi görünüyor. Örnek bir proje: ufile.io/6qrsor7o . Lütfen Android 10'da (emülatör de tamam) deneyin, alarmı ayarlayın ve uygulamayı son görevlerden kaldırın. Son görevlerden kaldırmazsanız, iyi çalışır ve 10 saniye sonra tetiklenir.
android geliştirici

Ayrıca, örnek bir proje ve video içeren bir hata raporuna bir bağlantı olması için soruyu güncelledim, çünkü bunun gerçekleşmesi için başka bir neden göremediğim için bu bir hata olduğunu düşünüyorum.
android geliştirici

0

Bunun etkili olmadığını biliyorum, ancak 60 saniyelik bir doğrulukla daha tutarlı olabilir.

https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK

bu yayın alıcısı bir ön plan hizmeti içinde kullanılıyorsa, her dakika saatini kontrol edebilir ve bir işlem yapmaya karar verebilirsiniz.


Ön plan servisim varsa neden buna ihtiyacım olsun ki? İstersem sadece bir Handler.postDelayed veya benzeri bir çözüm kullanabilirsiniz ...
android geliştirici

0

Kullanıcıdan enerji tasarrufu modunu devre dışı bırakması için izin ayarlamasını isteyebilir ve kullanmazsa kesin zamanların elde edilemeyeceği konusunda kullanıcıyı uyarabilirsiniz.

İşte talep etmek için kod:

PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
            String packageName = "your Package name";
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Intent i = new Intent();
                if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
                    i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                    i.setData(Uri.parse("package:" + packageName));
                    startActivity(i);

Zaten bunu denedim, çünkü başka hiçbir uygulama bunu yapmadı ve yardımcı olup olmadığını merak ediyordu. İşe yaramadı. Güncel soru.
android geliştirici

0

Sorunuzda bahsettiğiniz açık kaynak projesinin yazarıyım ( basit çalar saat) .

AlarmManager.setAlarmClock kullanmanın sizin için işe yaramadığına şaşırdım, çünkü uygulamam tam olarak bunu yapıyor. Kod AlarmSetter.kt dosyasındadır. İşte bir pasaj:

  val pendingAlarm = Intent(ACTION_FIRED)
                .apply {
                    setClass(mContext, AlarmsReceiver::class.java)
                    putExtra(EXTRA_ID, id)
                    putExtra(EXTRA_TYPE, typeName)
                }
                .let { PendingIntent.getBroadcast(mContext, pendingAlarmRequestCode, it, PendingIntent.FLAG_UPDATE_CURRENT) }

            val pendingShowList = PendingIntent.getActivity(
                    mContext,
                    100500,
                    Intent(mContext, AlarmsListActivity::class.java),
                    PendingIntent.FLAG_UPDATE_CURRENT
            )

            am.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingShowList), pendingAlarm)

Temelde özel bir şey değil, sadece niyetimin bir eylem ve hedef sınıfım olduğundan emin olun, bu benim durumumda bir yayın alıcısı.


Ne yazık ki işe yaramadı. Ben de öyle denedim. Buradaki dosyalara bakın: github.com/yuriykulikov/AlarmClock/issues/…
android geliştirici

Kodunuzu GitHub'da kontrol ettim. Yayın alıcı, uygulama Moto Z2 Play'deki sonlardan kaldırıldıktan sonra çalışır. Bunu bir Pixel'de deneyebilirim, ancak kod bana iyi geliyor. Uygulamayı zorla durdurma zamanlanmış alarmı kaldırır, ancak bu zorla durdurulmuş herhangi bir uygulamada gerçekleşir.
Yuriy Kulikov

Zaten birkaç kez gösterdim: zamanlama sonra tüm yaptığım son görevlerden kaldırmaktır. Hem emülatörde hem de Pixel 4'te yaptım.
android geliştirici

Lütfen pendingShowList kullanmanın sorunu çözüp çözmediğini kontrol edin ve evet ise cevabı güncelleyeceğim. Belki birisi için yararlı olacaktır.
Yuriy Kulikov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.