dependencies {
implementation 'com.android.support:recyclerview-v7:28.0.0'
}
<?xml version="1.0" encoding="utf-8"?>
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
public class MyActivity extends Activity {
private RecyclerView recyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true);
// use a linear layout manager
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// specify an adapter (see also next example)
mAdapter = new MyAdapter(myDataset);
recyclerView.setAdapter(mAdapter);
}
// ...
}
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] mDataset;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class MyViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView textView;
public MyViewHolder(TextView v) {
super(v);
textView = v;
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
...
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.textView.setText(mDataset[position]);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length;
}
}
{% embed url="https://developer.android.com/guide/topics/ui/layout/recyclerview" %}
public static class TimePickerFragment extends DialogFragment
implements TimePickerDialog.OnTimeSetListener {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the current time as the default values for the picker
final Calendar c = Calendar.getInstance();
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
// Create a new instance of TimePickerDialog and return it
return new TimePickerDialog(getActivity(), this, hour, minute,
DateFormat.is24HourFormat(getActivity()));
}
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
// Do something with the time chosen by the user
}
}
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pick_time"
android:onClick="showTimePickerDialog" />
public void showTimePickerDialog(View v) {
DialogFragment newFragment = new TimePickerFragment();
newFragment.show(getSupportFragmentManager(), "timePicker");
}
public static class DatePickerFragment extends DialogFragment
implements DatePickerDialog.OnDateSetListener {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the current date as the default date in the picker
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
// Create a new instance of DatePickerDialog and return it
return new DatePickerDialog(getActivity(), this, year, month, day);
}
public void onDateSet(DatePicker view, int year, int month, int day) {
// Do something with the date chosen by the user
}
}
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pick_date"
android:onClick="showDatePickerDialog" />
public void showDatePickerDialog(View v) {
DialogFragment newFragment = new DatePickerFragment();
newFragment.show(getSupportFragmentManager(), "datePicker");
}
👐 Shared Preference | 💾 Saved Instance State |
---|---|
Uygulama hatta cihaz kapatılsa dahi veriler korunur | Sadece Activity üzerinde verileri saklar |
Temel veriler için kullanılır | Anlık veriler için tercih edilir |
Az sayıda anahtar / değer çifti ile saklanır | Az sayıda anahtar / değer çifti ile saklanır |
Veriler uygulamaya özgüdür (gizli) | Veriler uygulamaya özgüdür (gizli) |
Genellikle kullanıcı tercihleri için kullanılır | Cihazın yapılandırma ayarlarında veri kaybını engellemek için kullanılır |
{% hint style="info" %} 🧙♂ Detaylar için Shared preferences vs. saved instance state alanına bakabilirsin. {% endhint %}
🗃️ Database | 👐 Shared Preference |
---|---|
🎳 Yüksek miktarda veriler | ⚙️ Kullanıcı yapılandırmaları |
🔍 Veriler içerisinde kompleks aramalar (SQL) | 💞 Anahtar / Değer çiftleri |
📉 Daha yavaş okuma hızı | 📈 Daha hızlı okuma hızı |
{% hint style="info" %} 🧙♂ Detaylı bilgi için Pros and Cons of SQL and Shared Preferences alanına bakabilirsin. {% endhint %}
- 🌍 Web sayfalarında saklanan veri formatıdır
- 👁️ JavaScript Object Notation
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}
JSONObject data = new JSONObject(responseString);
JSONArray menuItemArray = data.getJSONArray("menuitem");
JSONObject thirdItem = menuItemArray.getJSONObject(2);
String onClick = thirdItem.getString("onclick");
{% embed url="https://stackoverflow.com/a/9606629/9770490" %}
- 🩹 Kalıcı depolama dizini
getFilesDir()
- 🕘 Geçici depolama dizini
getCacheDir()
- 🎈 1 MB ve daha hafif dosyalar için önerilir
- 🧹 Hafıza yetmemeye başladığında burası silinebilir
File file = new File(context.getFilesDir(), filename);
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
public File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null, context.getCacheDir());
} catch (IOException e) {
// Error while creating file
}
return file;
}
- 👮♂️ Öncelikle okuma izinleri alınır
- 🧐 Harici depolama sisteme bağlı mı kontrol edilir
- 🐣 Erişim hakkımızın olup olmadığı kontrol edilir
- 📂 Ardından dosyalara erişim yapılır
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
// Harici dosyalara erişebilmek için (public external storage)
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File file = new File(path, "DemoPicture.jpg");
// Gizli harici dosyalara erişmek için (private external storage)
File file = new File(getExternalFilesDir(null), "DemoFile.jpg");
- ❣️ Kullanılmayan her dosya temizlenmelidir
myFile.delete();
myContext.deleteFile(fileName);
private String sharedPrefFile = "com.example.android.hellosharedprefs";
mPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE);
@Override
protected void onPause() {
super.onPause();
SharedPreferences.Editor preferencesEditor = mPreferences.edit();
preferencesEditor.putInt("count", mCount);
preferencesEditor.putInt("color", mCurrentColor);
preferencesEditor.apply();
}
mPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE);
if (savedInstanceState != null) {
mCount = mPreferences.getInt("count", 1);
mShowCount.setText(String.format("%s", mCount));
mCurrentColor = mPreferences.getInt("color", mCurrentColor);
mShowCount.setBackgroundColor(mCurrentColor);
} else { ... }
SharedPreferences.Editor preferencesEditor = mPreferences.edit();
preferencesEditor.putInt("number", 42);
preferencesEditor.clear();
preferencesEditor.apply();
public class SettingsActivity extends PreferenceActivity
implements OnSharedPreferenceChangeListener {
public static final String KEY_PREF_SYNC_CONN =
"pref_syncConnectionType";
// ...
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences,
String key) {
if (key.equals(KEY_PREF_SYNC_CONN)) {
Preference connectionPref = findPreference(key);
// Set summary to be the user-description for
// the selected value
connectionPref.setSummary(
sharedPreferences.getString(key, ""));
}
}
}
@Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
SharedPreferences.OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(
SharedPreferences prefs, String key) {
// listener implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
val veri= this.getSharedPreferences(this.packageName, android.content.Context.MODE_PRIVATE) // Veri kaydını değişkene atama
- this.packageName : paket ismi (com.... en üst satırdaki)
- MODE_PRIVATE : sadece benim uygulamamdan erişilebilirlik
var age1 = 30
veri.edit().putInt("userAge", age1).apply() // Veriyi kaydetme
- userAge : anahtar
- age1 : değer / değişken
val age2= veri.getInt("userAge", 0) // Kayıtlı veriyi alma
- userAge : anahtar (put'takini almak için aynı olmalı)
- 0 : Eğer anahtar yoksa, varsayılan değer ataması
println("stored age : $storedAge") // veriyi gösterme
age = 31
veri.edit().putInt("userAge", age).apply() // Daha önceden olan bir anahtarın üstüne kaydedilirse güncelleme olur.
veri.edit().remove("userAge").apply() // Veri silindi
- userAge : silinecek anahtar
val age3 = veri.getInt("userAge", 0) // Veri olmadığı için age3 = 0 olacak.
- userAge : anahtar
- 0 : varsayılan değer
İlk olarak try - catch yapısı kurulur ve olası sorunda programın kapanması engellenir.
try {
...
}
catch (e : Exception){
e.printStackTrace()
}
Bütün kodları
...
olan yere yazacağız. Artık başlayabiliriz.
database = openOrCreateDatabase("Datas", Context.MODE_PRIVATE, null)
- "Datas" : Oluştumak istediğimiz database'in adı ("Veriler", "Hey", "hop" vb.)
- Yazım kuralları gereği database adı büyük harfle başlamalı
- Context.MODE_PRİVATE : Database'i private (özel) sadece bizim erişebileceğimiz halde kurmak.
- (Context.MODE yazıp ALT+ SPACE yaparsanız detaylar çıkacaktır karşınıza)
- null : CursorFactory
try {
val database = openOrCreateDatabase("Datas", Context.MODE_PRIVATE, null)
database.execSQL("CREATE TABLE IF NOT EXIST datas (name VARCHAR, age INT(2)")
// INT(2) ile 2 rakam olacağını belli ediyoruz
} catch (e : Exception){
e.printStackTrace()
}
CREATE TABLE IF NOT EXITS
table oluşturmadatas
table ismiVARCHAR
charINT
Int
Temel yapısı database.execSQL("...")
şeklindedir.
database.execSQL("INSERT INTO datas (name, age) VALUES ('Yunus' , 21)") // Veri Ekleme
database.execSQL("INSEER INTO datas (name, age) VALUES ('Emre', 15") // Veri Ekleme
database.execSQL("UPDATE datas SET age = 21 WHERE name = 'Yunus") // Veri güncelleme
database.execSQL("DELETE FROM datas WHERE age = 15") // Veri silme
database.execSQL("SELECT FROM datas WHERE name = 'Yunus") // Yunus isimli olan dataları alır
database.execSQL("SELECT FROM datas WHERE name LIKE '%s'") // sonunda 's' harfi olanları alır
database.execSQL("SELECT FROM datas WHERE name LIKE 'y%") // başında 'y' harfi olanları alır
database.execSQL("SELECT FROM datas WHERE name LIKE '%u%") // içinde 'u' harfi olanları alır
INSERT INTO
Veri ekleme için SQL koduUPDATE
Veri güncellemeSELECT
Veri seçmedatas
table isminame
değişken ismiage
değişken ismiVALUES
değerleri atamak için SQL kodu'Yunus'
VARCHAR (string) tipindeki veri21
INT(2) (Int) tipindeki veri
if (database != null) {
val cursor = database!!.rawQuery("SELECT * FROM datas", null)
val nameIndex = cursor.getColumnIndex("name")
val ageIndex = cursor.getColumnIndex("age")
cursor.moveToFirst()
while (cursor != null){
println("İsim : ${cursor.getString(nameIndex)}" )
println("Yaş : ${cursor.getString(ageIndex)}")
cursor.moveToNext()
}
}
rawQuery(...)
SQL kodu ile veri almaSELECT * FROM
Bütün verileri almak için SQL kodunameIndex
name sütünundaki verilerin indexiageIndex
age sütunundaki verilerin indexiCursor.moveToFirst()
Cursoru ilk elemana atıyoruzCursor.getString()
İstenen indexteki string olarak döndürür.Cursor.moveToNext()
cursoru bir sütün aşağı indirme
- 🤓 SQL komutları ile uğraşmadan direkt android kodları ile çalışmamızı sağlar
- ✨ Optimize edilmiş bir veri tabanı sunar (LiveData)
{% hint style="warning" %} 📢 Sayfanın en altındaki linklerden resmi bağlantılara erişebilirsin. {% endhint %}
dependencies {
def room_version = "2.2.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
{% hint style="info" %} 🧙♂ Detaylar için Declaring dependencies alanına bakabilirsin. {% endhint %}
- 🧱 DB'ye aktarılacak sütun isimlerini temsil ederler
- 🏷️ Annotation yapısı ile özellikleri belirlenir
- 🔸 Tablodaki sütün isimleri entity üzerindeki değişkenlerle temsil edilir
- 👮♂️ Primary key ve Entity etiketini eklemek zorunludur
@Entity(tableName = "word_table")
public class Word {
@PrimaryKey (autoGenerate=true)
private int wid;
@ColumnInfo(name = "first_word")
private String firstWord;
@ColumnInfo(name = "last_word")
private String lastWord;
// Getters and setters are not shown for brevity,
// but they're required for Room to work if variables are private.
}
{% hint style="info" %} 👀 Daha fazlası için Entity ve Defining data using Room entities dokümanlarına bakabilirsin. {% endhint %}
- 🐣 Tablolara erişmek için kullanılan yapıdır
- 🧱 Abstract veya Interface olmak zorundadır
- 🏷️ SQL query metinleri metotlara Annotation yapısı ile tanımlanır
- ✨ LiveData yapısı ile güncel verileri döndürür
@Dao
public interface WordDao {
// The conflict strategy defines what happens,
// if there is an existing entry.
// The default action is ABORT.
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Word word);
// Update multiple entries with one call.
@Update
public void updateWords(Word... words);
// Simple query that does not take parameters and returns nothing.
@Query("DELETE FROM word_table")
void deleteAll();
// Simple query without parameters that returns values.
@Query("SELECT * from word_table ORDER BY word ASC")
List<Word> getAllWords();
// Query with parameter that returns a specific word or words.
@Query("SELECT * FROM word_table WHERE word LIKE :word ")
public List<Word> findWord(String word);
}
{% hint style="info" %} 👀 Daha fazlası için The DAO (data access object) dokümanına bakabilirsin. {% endhint %}
- 🧱 Abstract olmak zorundadır
- 🏗️
Room.databaseBuilder(...)
yapısı ile db tanımlanır - 🏷️ Database etiketi içerisinde
entities
alanında tablo verilerini temsil eden Entity Class'ınızın objesi verilirversion
alanında db'nin en son sürümünü belirtin- 🐛 Versiyon geçişleri arasındaki sorunları engellemek için
fallbackToDestructiveMigration()
özelliği eklenir
@Database(entities = {Word.class}, version = 1)
public abstract class WordRoomDatabase extends RoomDatabase {
public abstract WordDao wordDao();
private static WordRoomDatabase INSTANCE;
static WordRoomDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (WordRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
WordRoomDatabase.class, "word_database")
// Wipes and rebuilds instead of migrating
// if no Migration object.
.fallbackToDestructiveMigration()
.build();
}
}
}
return INSTANCE;
}
}
{% hint style="info" %} 👀 Daha fazlası için Room database dokümanına bakabilirsin. {% endhint %}
- 🚫 Veri tabanına birden çok istek gelmesini engeller
- 🐞 Birden çok isteğin eş zamanlı yapılmaya çalışması conflict oluşturacaktır
- 💔 Conflict yapısı veri tabanındaki verilerin uyuşmazlığını belirtir
- Birden fazla Thread gelmesi durumunda engellemek için synchronized anahtar kelimesi kullanılır
- ✨ Gereksiz Thread engelinden sakınmak için, synchronized yapısı içerisinde tekrardan if kontrolü yapılmalıdır
{% hint style="info" %} 👀 Detaylar için Multi-threading alanına bakabilirsin. {% endhint %}
- 🌃 Alt katmanda olan tüm sınıfları tek bir sınıfmış gibi gösterir
- 😏 Bu sayede ViewModel üzerinden birden fazla sınıfla uğraşmak zorunda kalmayız
- 🚧 DB üzerinde yapılacak olan tüm işlemlerinde burada metot olarak tanımlanması lazımdır
- ✨ LiveData yapısı sayesinde verileri otomatik günceller
- 🦄 Verilerin aktarımı bir defaya mahsus Constructor üzerinde yapılır
- 🌠 Verilerin aktarılması asenkron olması gerektiğinden AsyncTask yapısı kullanılır
public class WordRepository {
private WordDao mWordDao;
private LiveData<List<Word>> mAllWords;
WordRepository(Application application) {
WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
mWordDao = db.wordDao();
mAllWords = mWordDao.getAllWords();
}
LiveData<List<Word>> getAllWords() {
return mAllWords;
}
public void insert (Word word) {
new insertAsyncTask(mWordDao).execute(word);
}
private static class insertAsyncTask extends AsyncTask<Word, Void, Void> {
private WordDao mAsyncTaskDao;
insertAsyncTask(WordDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final Word... words) {
for (Word word : words) {
mAsyncTaskDao.insert(word);
}
return null;
}
}
}
- 🧱 Yapılandırma değişikliklerine karşı dayanıklıdır
- 🐣 Repository ile DB'ye erişir
- 🎳 Activity context objesi gönderilmez, çok maliyetlidir
- 🥚 Context verisi miras alınmalıdır
- 📝 UI ile alakalı bilgilerin kaydı ile uğraşır
public class WordViewModel extends AndroidViewModel {
private WordRepository mRepository;
private LiveData<List<Word>> mAllWords;
public WordViewModel (Application application) {
super(application);
mRepository = new WordRepository(application);
mAllWords = mRepository.getAllWords();
}
LiveData<List<Word>> getAllWords() { return mAllWords; }
public void insert(Word word) { mRepository.insert(word); }
}
- 🔄 Verileri güncel tutmak için kullanılır
- 📈 Performansı artırır
- 🧱 Yapılandırma değişikliklerine karşı dayanıklıdır
- 📳 Telefonu çevirme vs.
- 🍱 Tüm katmanlardaki metotlar kapsüllenmelidir
wordsViewModel.getAllNews().observe(
this,
words -> fillView(new ArrayList<>(news))
);
private void fillView(ArrayList<Words> words) {
// XML layoutu üzerinden tanımlanması lazımdır
RecyclerView recyclerView = findViewById(R.id.rv_words);
// Class olarak tanımlanması lazımdır
WordsAdapter wordsAdapter = new WordsAdapter(this, words);
recyclerView.setAdapter(wordsAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
{% hint style="info" %} 🧙♂ Detaylar için RecycleView alanına bakabilirsiniz. {% endhint %}
- 💫 Ayrı bir Thread üzerinden gerçekleşen bu işlemleri sistemin ilerlemesi engellemez
- 🙋♂️ İşleri tamamlandığı zaman UI Thread'e dahil olurlar
- ⭐ AsyncTask veya AsycnTaskLoader yapıları kullanılır
Her ikisi de sistemi bloklamadan çalışan bir yapıya sahiptir
AsyncTask |
AsyncTaskLoader |
---|---|
Direkt olan çalışır | Dolaylı olarak çalışır |
Yapılandırma ayarları değiştiğinde iptal olur ve yeniden başlatılır | Yapılandırma ayarlarından etkilenmez |
Geri dönüş vermeyecek işlemlerde kullanılır | Geri dönüşümlü işlemlerde kullanılır |
Kısa ve iptal edilebilir işlemlerde tercih edilir | Uzun ve iptal edilemeyecek işlemlerde tercih edilir |
Telefonu döndürme gibi işlemler yapılandırma ayarlarını değiştirir.
{% hint style="success" %}
Genel olarak AsyncTaskLoader
en sık kullanılan yapıdır.
{% endhint %}
Android'teki tüm görüntü işlemlerinin yapıldı alandır.
- UI Thread engellenmemeli
- UI Thread sadece görsel işlemler için kullanılmalıdır
- Tüm işlemler 16ms'den kısa bir sürede tamamlanmalıdır
{% hint style="danger" %} Yaklaşık olarak 5s'den uzun süren işlemler "application not responding" (ANR) diyaloğunu oluşturur ve kullanıcı bunu görmesi durumunda uygulamayı kapatıp, siler 😥 {% endhint %}
Verilen işlemi arkaplanda, sistemi bloklamadan tamamlar.
- Yapılandırma ayarlarından etkilenir, işlem yok edilip yeniden başlatılır
- Telefonu döndürme vs gibi işlemler yapılandırma ayarlarını değiştirir
- Aynı işlemin çokça yapılması RAM tüketimini arttırır
- Uygulama kapatıldığında cancel() metodu çalıştırılmadığı sürece çalışmaya devam eder
{% hint style="warning" %}
Önemli ve kritik işlemler için AsyncTaskLoader
tercih edilir
{% endhint %}
{% tabs %}
{% tab title="🎈 Kullanım" %}
💠 Metot | 📜 Açıklama |
---|---|
onPreExecute() |
İşlem tamamlanmadan önce ara ara çağrılan metottur, genellikle % dolum bilgisi vermek için kullanılır |
doInBackground(Params...) |
onPreExecute() metodu bittiği an çalışır, arkaplan işlemlerini yapan kısımdır. publishProgress() metodu ile değişikleri UI Thread'e aktarır. Bittiğinde onPostExecure() metoduna sonucu aktarır. |
onProgressUpdate(Progress...) |
publishProgress() metodundan sonra çalışır, genellikle raporlama veye ilerleme adımlarını kullanıcıya göstermek için kullanılır |
onPostExecute(Result) |
Arkaplan işlemi tamamlandığında sonuç buraya aktarılır, UI Thread bu metot üzerinden sonucu kullanır. |
{% hint style="warning" %}
onProgressUpdate
metodunda tüm adımları ele alırsanız, asenkron çalışma yapısı bozulur ve senkronize olarak çalışır
{% endhint %}
{% endtab %}
{% tab title="🧱 Prototip" %}
public class MyAsyncTask extends AsyncTask <String, Void, Bitmap>{}
String
değişkeni,doInBackground
metoduna aktarılacak verilerdirVoid
yapısı,publishProgress
veonProgressUpdate
metotlarının kullanılmayacağını belirtirBitmap
tipi de,onPostExecute
ile aktarılan işlem sonucunun tipini belirtir
{% hint style="warning" %}
Son iki parametre (Void
ve Bitmap
) dışarıdan verilmez, sınıf içi parametrelerdir
{% endhint %}
{% endtab %}
{% tab title="❌ İşlemi İptal Etme" %}
İşlemi istediğin zaman cancel()
metodu ile iptal edebilirsin
cancel()
metodu işlem tamamlanmışsaFalse
döndürür- Biten işlemi iptal edemezsin 🙄
- İşlemin iptal edilme durumunu
doInBackground
metodundaisCancalled()
metodu kontrol etmemiz gerekmektedir - İşlem iptal edildiğin
doInBackground
metodundan sonraonPostExecute
yerineonCancelled(Object)
metodu döndürülür
{% hint style="info" %}
Varsayılan olarak onCancelled(Object)
metodu onCancelled()
metodunu çağırır, sonuç görmezden gelinir.
{% endhint %}
{% endtab %}
{% tab title="👨💻 Kod" %}
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
// 0. eleman en son adımı belirtir. (FIFO)
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
// UI Thread'te kullanımı
new DownloadFilesTask().execute(url1, url2, url3);
{% endtab %} {% endtabs %}
- 🤔 Arkaplanda çalışan arayüzü olmayan Activity'ler olarak adlandırılabilir
- 🚧 Android'de arkaplanda çalışmak için Background Tasks dokümanına bakılmalıdır
- ⏳ Uzun süreli işlemler için hızı ve verimliliği artırma adına multi-threading yapısı önerilir
- 👮♂️ Android arka plan işlemlerini bataryayı korumak adına kısıtlar.
- 🚧 Kullanıcıya arayüz (UI) sağlamayan her arkaplan işlemi (Background Service) kısıtlı sürede çalışır
- 🌞 Kısıtlanmayı engellemek adına Foreground Service yapısı kullanılmalıdır
- 🔔 Kullanıcıya kaldırılamayan bir bildirim gösterilir
- 👁🗨 Kullanıcı arkaplan işlemlerinden haberdar olur
- 🌙 Cihaz uyku moduna girdiğinde arka plan işlemleri aksamaya başlar.
- 🙇♂️ WakeLock özelliğinin aktif olması gerekir
{% page-ref page="foreground-service.md" %}
{% hint style="info" %} 🧙♂️ Detaylı bilgi için Challenges in background processing alanına bakabilirsin. {% endhint %}
- 🦄 Servis tek bir işle baş edecek ise IntentService (eski adı JobIntentService) yapısı kullanılabilir
- 👮♂️ Çok fazla kısıtlamaya tabi tutulan
- ❌ İşi bittiğinde kapanan bir sistemdir
- 👪 Servisin birden fazla istekle baş etmesi gerekirse IntentService yerine Service kullanılır
<manifest ... >
...
<application ... >
<service android:name="ExampleService"
android:exported="false" />
...
</application>
</manifest>
- ⏰ Belirli sürelerde tetiklenen
Intent
işlemleridir - 🙇♂️ Uygulama kapalı olsa hatta telefon uykuda olsa bile çalışır
- 📈 Arka plan işlemlerinin tekrarlanma sıklığını azalttığından verimliliği artırır
- 👨💼
AlarmManager
üzerinden,Intent-Filter
yapısı gibi yönetilir
🙄 Telefondaki alarmdan bahsetmiyorum.
{% hint style="info" %} 🧙♂ Detaylı bilgi için Introduction ~ 8.2 Alarm alanına bakabilirsin. {% endhint %}
- 🚩 Uygulaman üzerinde çalışacak olan eylemlerde kullanılmaz
- ✨
Handler
,Timer
veyaThread
yapısı tercih edilmelidir - 🔄 Sunucu ile güncelleme işlemlerini bu yapı ile yapmayın
- 🔥 Firebase Cloud Messaging üzerindeki
SyncAdapter
yapısı ile yapılır
- 🔥 Firebase Cloud Messaging üzerindeki
- ⌚ Beklemeli işlemler için
JobScheduler
yapısını tercih edin- 📶 Wi-Fi bağlandığında haberleri veya hava durumunu güncelleme gibi
- ✨ Dakikada çok fazla tekrarlanacak işlemler için
Handler
yapısını tercih edin - 🐣
getSystemService(ALARM_SERVICE)
metodu ileAlarmManager
sınıfı alınır - ✔️ Alarm türünü belirleyin ve
set...(<tip>, <süre>, <PendingIntent>)
metodu ile tanımlayın
alarmMgr.set(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 1000*300,
alarmIntent);
{% hint style="info" %} 🧙♂ Detaylı bilgi için Scheduling an alarm alanına veya GitHub Koduna bakabilirsin. {% endhint %}
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(),
AlarmManager.INTERVAL_FIFTEEN_MINUTES,
alarmIntent);
boolean alarmExists =
(PendingIntent.getBroadcast(this, 0,
alarmIntent,
PendingIntent.FLAG_NO_CREATE) != null);
alarmManager.cancel(alarmIntent);
- ⏰ Kullanıcıya gösterilen alarm türleridir
- 🕐
AlarmClock
yapısı olarak ele alınır - 🙇♂️ Genellikle uyandırma çağrıları için kullanılır
{% embed url="https://codelabs.developers.google.com/codelabs/android-training-alarm-manager/\#0" %}
- 📃
AndroidManifest.xml
dosyası üzerinden internet izni alınmalıdır - 🐣
<uses-permission android:name="android.permission.INTERNET" />
ile internet erişimi - 🔸
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
ile internet bağlantısı durumu izni alınır
{% hint style="info" %} 🧙♂ Detaylı bilgi için Including permissions in the manifest dokümanına bakabilirsin {% endhint %}
- 🧰 Tüm sistem servislerine
getSystemService
metodu ile erişilir - 📶
ConnectivityManager
veNerworkInfo
sınıflarından bağlantı bilgileri yönetilir
private static final String DEBUG_TAG = "NetworkStatusExample";
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
boolean isWifiConn = networkInfo.isConnected();
networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
boolean isMobileConn = networkInfo.isConnected();
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);
{% hint style="info" %} 🧙♂ Detaylı bilgi için Managing the network state dokümanına bakabilirsin. {% endhint %}
- 🕐 Bağlantı işlemleri uzun sürebilir
- 🚫 UI Thread üzerinden yapılmamalıdır, aksi halde uygulamayı engelleyebilir
- 💫 Bağlantı işlemleri Asenkron İşlemler yazısına göre yapılmalıdır
- 🐣 Veri tabanına direkt erişim olmamalı, API üzerinden erişim olmalı
- 👨💻 Reverse Engineering ile bağlantıda kullandığın bilgileri elde edebilirler
- 🙏 Web sitelerinden içerik istemek için kullanılırlar
- 💫 API işlemlerinde sıklıkla kullanılırlar
- 🧱 GET, SET; POST vb. istek tipleri vardır
📦 Volley | 📦 Retrofit |
---|---|
Önbellek mekanizması | ➖ |
Bağlantıyı tekrardan gönderme | ➖ |
Gömülü olarak resim yüklenme desteği | ➖ |
➖ | JSON verilerini otomatik ayrıştırma |
/**
* https://developer.android.com/training/volley/simple.html
*
* @return
*/
static void requestNewsData(Context context, ResponseListener responseListener) {
RequestQueue queue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest(Request.Method.GET, MAIN_URL, (response) -> {
// https://stackoverflow.com/a/9606629/9770490
try {
// new JSONObject(response).getJSONArray("articles").get(1).getString("source")
JSONObject responseObject = new JSONObject(response);
String status = responseObject.getString("status");
if (status.equals("ok")) {
JSONArray articles = new JSONObject(response).getJSONArray("articles");
ArrayList<News> newsDataList = new ArrayList<>();
for (int i = 0; i < articles.length(); i++) {
JSONObject article = articles.getJSONObject(i);
News news = new News();
news.setTitle(article.getString("title"));
news.setDescription(article.getString("description"));
news.setUrlToImage(article.getString("urlToImage"));
news.setUrl(article.getString("url"));
news.setContent(article.getString("content"));
news.setSource(article.getJSONObject("source").getString("name"));
news.setPublishedAt(article.getString("publishedAt"));
newsDataList.add(news);
Log.i(TAG, "getNewsData: " + newsDataList.get(i));
}
responseListener.onResponse(newsDataList);
}
} catch (JSONException e) {
e.printStackTrace();
}
}, Throwable::printStackTrace);
queue.add(stringRequest);
}
interface ResponseListener {
void onResponse(ArrayList<News> newsDataList);
}
// Base URL for the Books API.
final String BOOK_BASE_URL =
"https://www.googleapis.com/books/v1/volumes?";
// Parameter for the search string
final String QUERY_PARAM = "q";
// Parameter to limit search results.
final String MAX_RESULTS = "maxResults";
// Parameter to filter by print type
final String PRINT_TYPE = "printType";
// Build up the query URI, limiting results to 5 printed books.
Uri builtURI = Uri.parse(BOOK_BASE_URL).buildUpon()
.appendQueryParameter(QUERY_PARAM, "pride+prejudice")
.appendQueryParameter(MAX_RESULTS, "5")
.appendQueryParameter(PRINT_TYPE, "books")
.build();
private String downloadUrl(String myurl) throws IOException {
InputStream inputStream = null;
// Only display the first 500 characters of the retrieved
// web page content.
int len = 500;
try {
URL url = new URL(myurl);
HttpURLConnection conn =
(HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
// Start the query
conn.connect();
int response = conn.getResponseCode();
Log.d(DEBUG_TAG, "The response is: " + response);
inputStream = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString =
convertInputToString(inputStream, len);
return contentAsString;
// Close the InputStream and connection
} finally {
conn.disconnect();
if (inputStream != null) {
inputStream.close();
}
}🧙♂ Detaylı bilgi için TEMP alanına bakabilirsin
{% hint style="warning" %} 📢 İstekte bulunmadan önce 👨💼 Bağlantı Durumunu Yönetme alanından bağlantını kontrol etmelisin {% endhint %}
// Reads an InputStream and converts it to a String.
public String convertInputToString(InputStream stream, int len)
throws IOException, UnsupportedEncodingException {
Reader reader = null;
reader = new InputStreamReader(stream, "UTF-8");
char[] buffer = new char[len];
reader.read(buffer);
return new String(buffer);
}
{% page-ref page="../veriler/json-yoenetimi.md" %}
- 📢 Haber salma olayı olarak ele alınabilir
- 💫 İşletim sistemindeki her uygulamaya bildirilir
- ⭐
📶 Wi-Fi'ya bağladım
,🎈 Cihazı başlattım
gibi örneklendirilebilir
- 🌃 Sırasız olarak tüm uygulamalara duyurulan haber yapısıdır
public void sendBroadcast() {
Intent intent = new Intent();
intent.setAction("com.example.myproject.ACTION_SHOW_TOAST");
// Set the optional additional information in extra field.
intent.putExtra("data","This is a normal broadcast");
sendBroadcast(intent);
}
- 🚩 XML üzerinde belirlenen
android:priority
sırasına göre uygulamalara haber verir - 🎲 Birden fazla aynı
android:priority
değerine sahip uygulamalara için seçim rastgele olur - 👨💼 Her duyuru alan uygulama,
intent
verisini değiştirme hatta silme hakkına sahiptir
public void sendOrderedBroadcast() {
Intent intent = new Intent();
// Set a unique action string prefixed by your app package name.
intent.setAction("com.example.myproject.ACTION_NOTIFY");
// Deliver the Intent.
sendOrderedBroadcast(intent);
}
- 🏠 Sadece uygulama içerisinde haber salınır
- 👮♂️ Daha güvenlidir, çünkü diğer uygulamalar erişemez
- 📈 Daha verimlidir, tüm sisteme haber salmakla uğraşılmaz
LocalBroadcastManager.getInstance(this).sendBroadcast(customBroadcastIntent);
{% hint style="info" %} 🧙♂ Detaylı bilgi için Broadcasts alanına bakabilirsin. {% endhint %}
- 📝 Manifest dosyası üzerinde
uses-permission
ile izin alınması gerekir - 🚫 İzni olmayanlar uygulamaların erişmesi engellenir
sendBroadcast(new Intent("com.example.NOTIFY"),Manifest.permission.SEND_SMS);
<uses-permission android:name="android.permission.SEND_SMS"/>
- 🚫 UI thread üzerinden gerçekleştiğinden uzun işlemler yapılmamalı
- ⛔
onReceive()
metodu içerisinde asenkron işlemler yapmayın- 🤷♂️ Yapsanız bile
return
metodundan sonra broadcast işlemleri sonlandırılır - ☠️ Haliyle işlem asenkron olsa bile broadcast yapısına bağlı olduğundan ölecektir
- 🤷♂️ Yapsanız bile
- 🗨
AlertDialog
gibi işlemler yerineNotification
yapısı tercih edilmelidir
//Subclass of the BroadcastReceiver class.
private class myReceiver extends BroadcastReceiver {
// Override the onReceive method to receive the broadcasts
@Override
public void onReceive(Context context, Intent intent) {
//Check the Intent action and perform the required operation
if (intent.getAction().equals(ACTION_SHOW_TOAST)) {
CharSequence text = "Broadcast Received!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
}
}
{% hint style="info" %} 🧙♂ Detaylı bilgi için Broadcast receivers alanına bakabilirsin. {% endhint %}
- 📝 Manifest üzerinden kayıt edilmeleri gerekir
- 😥 Uygulamamızı hedef almayan yayınlarını Android 8.0'dan itibaren alamaz
- 🎈 implicit broadcast exceptions yayınlarını hala alabilmektedir
<receiver
android:name=".AlarmReceiver"
android:exported="false">
<intent-filter>
<action android:name=
"com.example.myproject.intent.action.ACTION_SHOW_TOAST"/>
</intent-filter>
</receiver>
- 👀 Uygulama üzerinden ilgilendiğimiz broadcast'e erişmek için
IntentFilter
kullanırız - 🏗️ Genel kullanımı
onCreate
üzerinde yapılmaktadır (?)
IntentFilter intentFilter = new IntentFilter();
filter.addAction(Intent.ACTION_POWER_CONNECTED);
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- 🎌 İlk olarak
receiver
yapısını uygulamamızaregisterReceiver
ile kaydederiz - 🙋♂️ Genelde
onResume
içerisinderegisterReceiver
işlemi yapılır - 🚫
onPause
içerisindeunregisterReceiver
metodu ile kaldırırız
mReceiver = new AlarmReceiver();
this.registerReceiver(mReceiver, intentFilter);
unregisterReceiver(mReceiver);
{% hint style="info" %} 🧙♂ Detaylı bilgi için Broadcast receivers alanına bakabilirsin. {% endhint %}
- 👮♂️ Local Broadcast, Dynamic Receiver ile alınmak zorundadır
LocalBroadcastManager.getInstance(this)
.registerReceiver(mReceiver,
new IntentFilter(CustomReceiver.ACTION_CUSTOM_BROADCAST));
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(mReceiver);
<receiver android:name=".MyBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
{% hint style="info" %} 🧙♂ Detaylı bilgi için Restricting broadcasts alanına bakabilirsin. {% endhint %}