9. RxJava, Android ortamında
9.1. Introduction
Burada, daha önce birçok belgede ele alınmış bir uygulamayı tekrar ele alacağız:
- [Android pour les développeurs JEE : un modèle asynchrone pour clients Android] (bölüm 4);
- [Introduction à la programmation de tablettes Android par l'exemple] (9. bölüm);
- [Introduction à la programmation de tablettes Android par l'exemple - version 2] (1.11. paragraf);
Bu uygulamada, sunucunun rastgele sayıları asenkron olarak sağladığı ve Android istemcisinin bunları görüntülediği bir istemci/sunucu uygulaması ele alınmaktadır:
- Belge 1'de, Android istemcisi standart dışı bir teknoloji kullanmaktadır;
- belge 2'de, Android istemcisi asenkron işlemler için Android'in standart teknolojisini kullanmaktadır;
- Belge 3'te, Android istemcisi Belge 2'dekiyle aynı teknolojiyi kullanmaktadır, ancak Android Annotations kütüphanesindeki anotasyonların kullanımıyla basitleştirilmiştir;
Android istemcisi şu şekildedir:
![]() |
[DAO] katmanı, Android tablet tarafından görüntülenen rastgele sayıları üreten sunucuyla iletişim kurar. Bu sunucu, aşağıdaki gibi iki katmanlı bir mimariye sahiptir:
![]() |
İstemciler, [web / JSON] katmanındaki belirli URL'leri sorgular ve JSON (JavaScript Nesne Notasyonu) biçiminde bir metin yanıtı alır.
Uygulamanın incelenmesini iki aşamaya ayıracağız:
Web sunucusu / jSON
- [métier] katmanı;
- Spring ile uygulanan [web / JSON] hizmeti;
Android istemcisi
- [DAO] katmanı;
- aktivitesi;
- görünümleri;
9.2. Web hizmeti / jSON
Not: / jSON web hizmeti, Spring teknolojisiyle MVC olarak uygulanmıştır. Bu teknolojiyi bilmeyen okuyucular şunlardan birini yapabilir:
- sunucuyu nasıl başlatacağını ve sorgulayacağını açıklayan 9.2.1 numaralı paragrafı okuyabilir;
- [Spring MVC et Thymeleaf par l'exemple] belgesine, özellikle de kodda kullanılan başlıca anotasyonları tanıtan 4. bölüme göz atabilir;
9.2.1. IntelliJ IDEA Projesi
Web hizmeti / jSON şu mimariye sahiptir:
![]() |
Bu mimari, [1] adlı IntelliJ Idea projesi ile uygulanmaktadır:
![]() | ![]() |
Sunucu, [2-3] tarafından başlatılır. Ardından konsolda şu günlük kayıtları görüntülenir:
- 12. satır: hizmetin 8080 numaralı bağlantı noktasında kullanılabilir olduğunu gösterir;
- 10. satır: URL adlı web hizmetinin tek örneği / jSON, HTTP ve GET işlemleri aracılığıyla erişilebilir. Parametreleri şunlardır:
- [a,b]: rastgele sayı üretme aralığı;
- [minCount, maxCount]: count rastgele sayı üretilir; burada count, [minCount, maxCount] aralığındaki bir rastgele sayıdır;
- [minDelay, maxDelay]: hizmet, istenen sayıları geri göndermeden önce delay milisaniye bekler; burada delay, [minDelay, maxDelay] aralığında bir rastgele sayıdır;
Bir tarayıcıda şu URL değerini isteyelim:
![]() |
Talep edilen:
- [100, 200] aralığında rastgele sayılar;
- n aralığında, n'i içeren rastgele sayılar;
- [300, 400] aralığında x ile x milisaniyelik bir bekleme süresi;
Yanıtta:
- aleas: üretilen rastgele sayıların listesi;
- delay: sunucunun belirlediği milisaniye cinsinden bekleme süresi;
- hata: bir hata kodu - hata yoksa 0;
- message: bir hata mesajı - hata yoksa null;
9.2.2. Projenin Gradle bağımlılıkları
![]() |
[serveur] projesi, aşağıdaki [build.gradle] [1] dosyası ile yapılandırılmış bir Gradle projesidir:
// http://start.spring.io/ tarafından oluşturuldu (Mayıs 2016)
buildscript {
ext {
springBootVersion = '1.3.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'spring-boot'
jar {
baseName = 'serveur'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
}
- 1. satır: Bu yapılandırma dosyasının nasıl oluşturulduğunu belirten bir açıklama;
- 4. ve 10. satırlar: Spring ekosisteminin bir dalı olan [Spring Boot] çerçevesine bir bağımlılık. Bu [http://projects.spring.io/spring-boot/] çerçevesi, Spring için minimum bir yapılandırma sağlar. Projenin Classpath'inde bulunan kütüphanelere göre, [Spring Boot], proje için makul veya olası bir yapılandırma çıkarır. Dolayısıyla, Hibernate projenin Classpath'inde bulunuyorsa, [Spring Boot], kullanılan JPA uygulamasının Hibernate olacağını çıkarır ve Spring'i buna göre yapılandırır. Geliştiricinin bunu yapmasına artık gerek kalmaz. Geliştiricinin yapması gereken tek şey, [Spring Boot]'in varsayılan olarak yapmadığı yapılandırmaları veya [Spring Boot]'in varsayılan olarak yaptığı ancak daha ayrıntılı hale getirilmesi gereken yapılandırmaları gerçekleştirmektir. Her durumda, son sözü geliştiricinin yaptığı yapılandırma söyler;
- 14-15. satırlar: Bu Gradle dosyasının içeriğini kullanmak için gerekli iki Gradle eklentisi;
- satır 17-20: bu proje için oluşturulan arşivin özelliklerini tanımlar;
- 22-23. satırlar: Java 8 ile uyumluluk için;
- 25-27. satırlar: Bağımlılıklar, genel Maven deposunda veya makinedeki yerel depoda aranacaktır;
- satır 30: [spring-boot-starter-web] artefaktına bir bağımlılık tanımlar. Bu artefakt, bir Spring projesi olan MVC için gerekli tüm arşivleri beraberinde getirir. Bunlar arasında bir Tomcat sunucusunun arşivi de bulunur. Web uygulamasını dağıtmak için bu sunucu kullanılacaktır. Bağlantılı öğenin sürümünün belirtilmediğine dikkat edilmelidir. İthal edilen [spring-boot] projesinde belirtilen sürüm kullanılacaktır;
Projeyi güncellemek için [1-3] bağımlılıklarının indirilmesini zorlamak gerekir:
![]() | ![]() ![]() |
Şimdi, [4] dosyasının getirdiği bağımlılıkları inceleyelim:
![]() |
Bunların sayısı oldukça fazla. Spring Boot for Web, bir Spring web uygulamasının muhtemelen ihtiyaç duyacağı bağımlılıkları içermektedir. Bu, bazılarının gereksiz olabileceği anlamına gelir. Spring Boot, bir öğretici için idealdir:
- muhtemelen ihtiyaç duyacağımız bağımlılıkları sağlar;
- Spring MVC projesinin yapılandırmasını önemli ölçüde basitleştirdiğini göreceğiz;
- yerleşik bir Tomcat sunucusu [1] sağlar; bu sayede uygulamayı harici bir web sunucusuna dağıtmamıza gerek kalmaz;
- yukarıdaki tüm bağımlılıkları içeren çalıştırılabilir bir jar dosyası oluşturulmasına olanak tanır. Bu jar dosyası, yeniden yapılandırmaya gerek kalmadan bir platformdan diğerine taşınabilir.
Spring ekosisteminin web sitesinde Spring Boot’u kullanan çok sayıda örnek bulabilirsiniz [http://spring.io/guides]. Artık projenin bağımlılıklarını bildiğimize göre, koda geçebiliriz.
9.2.3. [métier] katmanı
![]() |
![]() |
[métier] katmanı, aşağıdaki [IMetier] arayüzüne sahip olacaktır:
package dvp.rxjava.server.metier;
public interface IMetier {
// [a,b] aralığındaki rastgele sayılar
// n sayıda sayı üretilir; burada n, [minCount, maxCount] aralığında rastgele bir sayıdır
// sayılar, milisaniye gecikme süresinden sonra üretilir,
// burada [delay], [minDelay, maxDelay] aralığında bir rastgele sayıdır
public AleasMetier getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay);
}
Bu arayüz, 8.4. paragrafta Swing ortamında incelenen arayüzle neredeyse aynıdır. 8. satırda, [getAleas] yöntemi aşağıdaki [AleasMetier] türünü döndürür:
package dvp.rxjava.server.metier;
import java.util.List;
public class AleasMetier {
// alanlar
private int delay;
private List<Integer> aleas;
// oluşturucular
public AleasMetier(){
}
public AleasMetier(int delay, List<Integer> aleas){
this.delay=delay;
this.aleas=aleas;
}
public AleasMetier(AleasMetier aleasMetier){
this.delay=aleasMetier.delay;
this.aleas=aleasMetier.aleas;
}
// alıcı ve ayarlayıcılar
...
}
[IMetier] arayüzünü uygulayan [Metier] sınıfının kodu şöyledir:
package dvp.rxjava.server.metier;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class Metier implements IMetier {
@Autowired
private ObjectMapper mapper;
@Override
public AleasMetier getAleas(int a, int b, int minCount, int maxCount, int minDelay, int maxDelay) {
// [a,b] aralığındaki rastgele sayılar
// n aralığında n rastgele sayı üretilir, burada n'nin kendisi de [minCount, maxCount] aralığında rastgele bir sayıdır
// sayılar, milisaniye gecikme süresinden sonra üretilir,
// burada [delay], [minDelay, maxDelay] aralığında bir rastgele sayıdır
// bazı kontroller
List<String> messages = new ArrayList<>();
int erreur = 0;
if (a < 0) {
messages.add("Le nombre a de l'intervalle [a,b] de génération doit être supérieur à 0");
erreur |= 2;
}
if (a >= b) {
messages.add("Dans l'intervalle [a,b] de génération, on doit avoir a< b");
erreur |= 4;
}
if (minCount < 0) {
messages.add("Le nombre min de l'intervalle [min,count] du nombre de valeurs générées doit être supérieur à 0");
erreur |= 16;
}
if (minCount > maxCount) {
messages.add("Dans l'intervalle [min,count] du nombre de valeurs générées, on doit avoir min<= max");
erreur |= 32;
}
if (minDelay < 0) {
messages.add("Le nombre min de l'intervalle [min,count] du délai d'attente doit être supérieur à 0");
erreur |= 64;
}
if (minCount > maxCount) {
messages.add("Dans l'intervalle [min,count] du délai d'attente, on doit avoir min<= max");
erreur |= 128;
}
if (maxDelay > 5000) {
messages.add("L'attente en millisecondes avant la génération des nombres doit être dans l'intervalle [0,5000]");
erreur |= 256;
}
// hatalar mı?
if (!messages.isEmpty()) {
throw new AleasException(String.join(" [---] ", messages), erreur);
}
// rastgele sayı üreteci
Random random = new Random();
// bekleme?
int delay = minDelay + random.nextInt(maxDelay - minDelay + 1);
if (delay > 0) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
String message = null;
try {
message = mapper.writeValueAsString(Arrays.asList(String.format("[%s : %s]", e.getClass().getName(), e.getMessage())));
} catch (JsonProcessingException e1) {
throw new AleasException(e1,512);
}
throw new AleasException(message, 1024);
}
}
// sonuç üretme
int count = minCount + random.nextInt(maxCount - minCount + 1);
List<Integer> nombres = new ArrayList<Integer>();
for (int i = 0; i < count; i++) {
nombres.add(a + random.nextInt(b - a + 1));
}
// sonuç geri dönüşü
return new AleasMetier(delay,nombres);
}
}
Sınıf hakkında yorum yapmayacağız: bu sınıf, 8.4. paragrafında Swing ortamında karşılaştığımız sınıfa benzemektedir. Sadece şu noktalara dikkat çekmek istiyoruz:
- 10. satır: Spring'in bu sınıfı tek bir örnek (singleton) olarak oluşturmasını ve referansını diğer Spring bileşenleri için kullanılabilir hale getirmesini sağlayan Spring [@Service] anotasyonu. Aynı etkiyi elde etmek için burada başka Spring anotasyonları da kullanılabilirdi;
- 13-14. satırlar: jSON adlı bir mapper enjekte ediliyor. Spring, bir nesne konteyneridir. Bu konteyner, web uygulamasının başlatılması sırasında örneklenir ve bir yapılandırma dosyasında tanımlanan nesneler, varsayılan olarak tek bir kopya halinde (singleton) örneklenir. Bir Spring singleton'ı, diğer Spring nesnelerine referanslar içerebilir. Burada da durum böyledir: [metier] singleton'ı (satır 10-11), [mapper] singleton'ına (satır 13-14) bir referans içerecektir. Buna bağımlılık enjeksiyonu denir. Bir singleton'ı başka bir singleton'a enjekte etmenin iki yolu vardır:
- türüne göre: enjekte edilecek singleton, bu türe sahip tek Spring nesnesi ise bu mümkündür. Burada 13-14. satırlardaki enjeksiyon için durum böyledir (ObjectMapper türü);
- adına göre: Birden fazla Spring nesnesi aynı türe sahipse bu yöntem kullanılır. Bu durumda, singleton'ın adını belirtmek için @Qualifier("nomDuSingleton") anotasyonu eklenmelidir;
[Metier] sınıfı, [AleaException] türünde istisnalar atar:
package android.exemples.server.metier;
public class AleaException extends RuntimeException {
// hata kodu
private int code;
// yapıcılar
public AleaException() {
}
public AleaException(String detailMessage, int code) {
super(detailMessage);
this.code = code;
}
public AleaException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
public AleaException(String detailMessage, Throwable throwable, int code) {
super(detailMessage, throwable);
this.code = code;
}
// alıcı ve ayarlayıcılar
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- 3. satır: [AleasException], [RuntimeException] sınıfını genişletir. Dolayısıyla bu, kontrol edilmeyen bir istisnadır (try / catch ile yönetilmesi zorunlu değildir);
- 6. satır: [RuntimeException] sınıfına bir hata kodu eklenir;
9.2.4. Web hizmeti / JSON
![]() |
![]() |
/ JSON web hizmeti, Spring MVC tarafından uygulanmaktadır. Spring MVC, MVC olarak adlandırılan mimari modelini (Model – Görünüm – Denetleyici) şu şekilde uygular:
![]() |
Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşir:
- istek - istenen URL'ler http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... biçimindedir. [Dispatcher Servlet], gelen URL'leri işleyen Spring sınıfıdır. Bu sınıf, URL'i onu işlemesi gereken eyleme "yönlendirir". Bu eylemler, [Contrôleurs] olarak adlandırılan belirli sınıfların yöntemleridir. Burada MVC'in C kısmı, [Dispatcher Servlet, Contrôleur, Action] dizesidir. Gelen URL'i işlemek için herhangi bir eylem yapılandırılmamışsa, [Dispatcher Servlet] servleti, istenen URL'in bulunamadığını bildirir (404 hatası NOT FOUND);
- işleme
- seçilen eylem, [Dispatcher Servlet] servletinin kendisine ilettiği parami parametrelerini kullanabilir. Bu parametreler çeşitli kaynaklardan gelebilir:
- [/param1/param2/...]'in URL'teki yolundan,
- URL'in ,'e gönderdiği [p1=v1&p2=v2]
- tarayıcı tarafından istekle birlikte gönderilen parametrelerden;
- kullanıcının isteğinin işlenmesi sırasında, eylem [metier] ve [2b] katmanlarına ihtiyaç duyabilir. Müşterinin isteği işlendikten sonra, bu işlem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
- istek düzgün bir şekilde işlenemediğinde bir hata sayfası
- aksi takdirde bir onay sayfası
- eylem, belirli bir görünümün ([3]) görüntülenmesini ister. Bu görünüm, görünüm modeli olarak adlandırılan verileri gösterecektir. Bu, MVC'teki M'dir. Eylem, bu M şablonunu ([2c]) oluşturacak ve bir V görünümünün ([3]) görüntülenmesini isteyecektir;
- yanıt - seçilen V görünümü, eylemin oluşturduğu M şablonunu kullanarak müşteriye göndermesi gereken HTML yanıtının dinamik kısımlarını başlatır ve ardından bu yanıtı gönderir.
Bir web hizmeti / JSON için, önceki mimari biraz değiştirilmiştir:
![]() |
- [4a]'te, bir Java sınıfı olan şablon, bir kütüphane tarafından JSON dizesine dönüştürülür;
- [4b]'te, bu JSON dizesi tarayıcıya gönderilir;
Uygulamamızın [web] katmanına geri dönelim:
![]() |
Uygulamamızda tek bir denetleyici vardır:
![]() |
/ JSON web hizmeti, istemcilerine aşağıdaki gibi bir [AleasResponse] türünde yanıt gönderecektir:
package dvp.rxjava.server.web;
import dvp.rxjava.server.metier.AleasMetier;
public class AleasResponse extends AleasMetier {
// hata kodu
private int erreur;
// hata mesajı
private String message;
// oluşturucular
public AleasResponse() {
}
public AleasResponse(int erreur, String message, AleasMetier aleasMetier) {
super(aleasMetier);
this.erreur = erreur;
this.message = message;
}
// getter ve setter'lar
public void setAleasMetier(AleasMetier aleasMetier) {
this.setDelay(aleasMetier.getDelay());
this.setAleas(aleasMetier.getAleas());
}
...
}
- 5. satır: [AleasResponse] sınıfı, [AleasMetier] sınıfını genişletir ve dolayısıyla tüm özniteliklerini (aleas, delay) devralır;
- 8. satır: bir hata kodu (hata yoksa 0);
- 10. satır: erreur!=0 ise bir hata mesajı, hata yoksa null;
[AleasController] denetleyicisi şöyledir:
package dvp.rxjava.server.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.rxjava.server.metier.AleasException;
import dvp.rxjava.server.metier.IMetier;
@Controller
public class AleasController {
// iş mantığı katmanı
@Autowired
private IMetier metier;
@Autowired
private ObjectMapper mapper;
// [a,b]'te rastgele sayılar
// n aralığında n sayı üretilir [minCount, maxCount]
// sayılar milisaniyelik bir gecikme süresinin ardından üretilir,
// burada [delay], [minDelay, maxDelay] aralığında bir rastgele sayıdır
@RequestMapping(value = "/{a}/{b}/{minCount}/{maxCount}/{minDelay}/{maxDelay}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public String getAleas(@PathVariable("a") int a, @PathVariable("b") int b, @PathVariable("minCount") int minCount,
@PathVariable("maxCount") int maxCount, @PathVariable("minDelay") int minDelay,
@PathVariable("maxDelay") int maxDelay) throws JsonProcessingException {
// yanıt hazırlanır
AleasResponse response = new AleasResponse();
// iş katmanı kullanılarak rastgele sayılar üretilir
try {
response.setAleasMetier(metier.getAleas(a, b, minCount, maxCount, minDelay, maxDelay));
} catch (AleasException e) {
// hata durumu (kod ve mesaj)
response.setErreur(e.getCode());
response.setMessage(e.getMessage());
}
// yanıt gönderiliyor jSON
return mapper.writeValueAsString(response);
}
}
- 16. satır: [@Controller] notu, [AleasController] sınıfını bir Spring singleton'ı yapar. Ayrıca, sınıfın web uygulamasındaki belirli URL'ler için istekleri işleyecek yöntemler içerdiğini belirtir. Burada, 29. satırda sadece bir tane bulunmaktadır;
- 20-21. satırlar: [@Autowired] anotasyonu, Spring'den alana [IMetier] türünde bir bileşen enjekte etmesini ister. Bu, önceki [Metier] sınıfı olacaktır. Bu sınıfa [@Service] anotasyonu eklediğimiz için, bir Spring bileşeni olarak yönetilmektedir;
- 22-23. satırlar: [@Autowired] anotasyonu, Spring'den alana [ObjectMapper] türünde bir bileşen enjekte etmesini ister. Bunu birazdan tanımlayacağız;
- 31. satır: [getAleas] yöntemi rastgele sayılar üretir. Adının önemi yoktur. Bu yöntem çalıştırıldığında, 31-33. satırlardaki parametreler Spring tarafından MVC olarak başlatılmıştır. Bunun nasıl olduğunu göreceğiz. Ayrıca, bu yöntemin çalıştırılmasının nedeni, web sunucusunun 29. satırdaki URL için HTTP GET isteği almış olmasıdır (method özniteliği);
- 30. satır: [@ResponseBody] açıklaması, yöntemin sonucunun olduğu gibi istemciye gönderilmesi gerektiğini belirtir. Burada, [AleasResponse] türünde olan jSON karakter dizisini istemciye göndereceğiz;
- 29. satır: İşlenen URL, /{a}/{b}/{minCount}/{maxCount}/{minDelay}/{maxDelay} şeklindedir; burada {x} bir değişkeni temsil eder. Bu farklı değişkenler, 32-33. satırlarda yöntemin parametrelerine atanır. Bu, @PathVariable("x") anotasyonu aracılığıyla yapılır. {x} değerlerinin bir URL'in bileşenleri olduğu ve dolayısıyla String türünde oldukları unutulmamalıdır. String'in yöntem parametrelerinin türüne dönüştürülmesi başarısız olabilir. Bu durumda Spring MVC bir istisna oluşturur. Özetleyelim: Bir tarayıcıdan URL /100/200/10/20/300/400 değerlerini talep edersem, 31. satırdaki getAleas yöntemi a=100 (31. satır), b=200 (satır 31), minCount=10 (satır 31), maxCount=20 (satır 32), minDelay=300 (satır 32), maxDelay=400 (satır 33) parametreleriyle çalıştırılacaktır;
- satır 39: [métier] katmanından bir rastgele sayı listesi istenir. [metier].getAleas yönteminin bir istisna oluşturabileceğini hatırlayalım;
- satır 42-43: hata durumu;
- 46. satır: [AleasResponse] türündeki yanıt, jSON dizesi biçiminde döndürülür;
9.2.5. Spring projesinin yapılandırılması
![]() |
Spring'i yapılandırmanın çeşitli yolları vardır:
- XML dosyalarıyla;
- Java kodu ile;
- ikisinin bir karışımıyla;
Web uygulamamızı Java kodu kullanarak yapılandırmayı tercih ediyoruz. Bu yapılandırmayı sağlayan, yukarıdaki [Config] sınıfıdır:
package dvp.rxjava.server.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ComponentScan(basePackages = { "dvp.rxjava.server.metier", "dvp.rxjava.server.web" })
@EnableWebMvc
public class Config {
// -------------------------------- katman yapılandırması [web]
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8080);
}
// eşleştirici jSON
@Bean
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- 15. satır: Spring'e, hangi paketlerde örneklenecek nesneleri bulacağını bildiriyoruz. Spring iki nesne bulacaktır:
- [@Service] ile anotlanmış [Metier] sınıfı;
- [@Controller] ile anotlanmış [AleasController] sınıfı;
- 16. satır: [@EnableWebMvc] anotasyonu, Spring MVC çerçeve için otomatik yapılandırmalar oluşturur;
- 19-20. satırlar: Spring bağlamının (Spring nesnelerinin konteyneri) enjeksiyonu. Bu enjeksiyon, 22-26. satırlardaki nesnenin buna ihtiyaç duyması nedeniyle gereklidir;
- Spring yapılandırma dosyası, [@Bean] ile işaretlenmiş yöntemler kullanılarak yeni Spring nesneleri tanımlayabilir. Yöntemin sonucu böylece bir Spring nesnesi haline gelir;
- satır 22-26: Spring çerçevesinin MVC servlet'inin tanımı; bu servlet, HTTP isteklerini doğru denetleyiciye ve doğru yönteme yönlendirir. [DispatcherServlet] bir Spring sınıfıdır;
- 28-31. satırlar: Bu servletin tüm URL isteklerini işlediği belirtilir;
- 33-36. satırlar: Proje arşivlerinde bulunan Tomcat sunucusunu etkinleştirecek olan bu bean'dir. Sunucu, 8080 numaralı bağlantı noktasında istekleri bekleyecektir;
- 39-42. satırlar: bir jSON eşleştiricisi. Spring nesneleri [Metier] ve [AleasController]'e enjekte edilen budur;
9.2.6. Web sunucusunun çalıştırılması
![]() |
Proje, aşağıdaki [Application] yürütülebilir sınıfından çalıştırılır:
package android.exemples.server.boot;
import android.exemples.server.config.Config;
import org.springframework.boot.SpringApplication;
public class Application {
public static void main(String[] args) {
// uygulama çalıştırma
SpringApplication.run(Config.class, args);
}
}
- 6. satır: [Application] sınıfı bir yürütülebilir sınıftır (7-10. satırlar);
- 9. satır: [SpringApplication.run] statik yöntemi, uygulamayı başlatacak olan [spring Boot] sınıfının (4. satır) bir yöntemidir. İlk parametresi, projeyi yapılandıran Java sınıfıdır. Burada, az önce tanımladığımız [Config] sınıfı yer almaktadır. İkinci parametre, [main] yöntemine (7. satır) aktarılan argüman dizisidir. Burada herhangi bir argüman bulunmayacaktır;
Asıl yürütme için okuyucunun 9.2.1 numaralı paragrafa geri dönmesi önerilir.
9.3. Android istemcisi
Not: Aşağıdaki Android projesi oldukça karmaşıktır. Bu proje, örneğin [Introduction à la programmation de tablettes Android avec Android Studio ]'te bulunabilecek düzeyde iyi bir Android bilgisi gerektirir.
Etkinlik
Görünümler
Katman
[DAO]
Kullanıcı
Sunucu
Müşterinin iki bileşeni olacaktır:
- bir [Présentation] katmanı (görünümler + etkinlik);
- daha önce incelediğimiz [web / JSON] hizmetine hitap eden bir [DAO] katmanı.
9.3.1. RxAndroid
Rastgele sayı sunucusuyla asenkron iletişim kurmak için Android istemcisi, RxAndroid kütüphanesini kullanacaktır. Bu kütüphane, RxJava'i Android ortamına genişletir. Swing uygulamasında olduğu gibi, RxAndroid tarafından sağlanan tek bir uzantıyı, yani [AndroidSchedulers.mainThread()] zamanlayıcısını kullanacağız. Bir Android grafik arayüzü, bir Swing arayüzüyle aynı kurallara tabidir:
- olaylar, olay döngüsü veya UI iş parçacığı olarak adlandırılan tek bir iş parçacığında işlenir;
- bir olay asenkron eylemleri tetiklediğinde, bu eylemlerin sonuçları, kullanıcı arayüzünü güncellemek için kullanılacaksa kullanıcı arayüzü iş parçacığından alınmalıdır;
Android istemcisi:
- rastgele sayı sunucusuna birkaç asenkron istek gönderecektir. Bu istekler, [Schedulers.io()] zamanlayıcısının iş parçacıklarıyla istemci tarafında yürütülecektir;
- bu asenkron istekler, tek bir gözlemlenebilir değere birleştirilecek (merge) gözlemlenebilir değerler döndürecektir;
- bu gözlemlenebilir, RxAndroid tarafından başlatılan [AndroidSchedulers.mainThread()] zamanlayıcısında istemci tarafında gözlemlenecektir;
9.3.2. IntelliJ IDEA Projesi
Android projesinin adı [client]'tir:
![]() | ![]() ![]() |
Proje, [2] komutuyla çalıştırılacaktır.
Not: Çalıştırma, kullanılan IntelliJ IDEA'nın yapılandırmasına büyük ölçüde bağlıdır. Yukarıdaki [2] komutunun, benim bilgisayarımdan başka bir bilgisayarda ilk denemede çalışmayacağı muhtemeldir. Bu projeyi çalıştırmak için IntelliJ IDEA'yı doğru şekilde yapılandırmak, yeni başlayanlar için zorlu bir görev olabilir. Dikkat edilmesi gereken bazı noktalar şunlardır:
- [3]'te proje yapısına erişin;
![]() | ![]() |
- [4-5]'te, JDK ve SDK Android sürümleri bilgisayarımda mevcut. JDK 1.8 sürümünün zorunlu olmadığını belirtmek gerekir. Android, lambda ifadeleri dahil olmak üzere Java 8'in bazı özelliklerini desteklemez. Bu nedenle, fonksiyonel arayüzleri örneklendirmek için isimsiz sınıflar kullanacağız. Bu durumda JDK 1.6 sürümü yeterlidir. Ancak, dağıtıldığı haliyle proje JDK 1.8 sürümüyle yapılandırılmıştır;
Android projesini yapılandıran [build.gradle] [6] dosyası şöyledir:
buildscript {
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
// Android eklentisinin güncel sürümüyle değiştirin
classpath 'com.android.tools.build:gradle:1.5.0'
}
}
apply plugin: 'com.android.application'
dependencies {
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.springframework.android:spring-android-rest-template:1.0.1.RELEASE'
compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.9'
compile 'io.reactivex:rxandroid:1.1.0'
}
repositories {
jcenter()
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "android.aleas"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/license.txt'
}
}
Mevcut SDK Android dosyalarına göre, 8., 24-25. ve 29. satırlardaki sürümlerin değiştirilmesi gerekebilir.
Yeni SDK Android sürümlerini yüklemek için, SDK Yöneticisini aşağıdaki şekilde kullanın:
![]() ![]() | ![]() |
Proje şu şekilde yapılandırılmıştır:
- SDK, API, 23, [2];
- SDK Build-tools 23.0.3 [3];
- SDK Tool 25.1.3 [4]
Son olarak, aşağıdaki 11. satırda yer alan [local.properties] ve [4] dosyalarında SDK Android'in yolunu kontrol edin:
## Bu dosya Android Studio tarafından otomatik olarak oluşturulmuştur.
# Bu dosyayı değiştirmeyin -- YOUR CHANGES WILL BE ERASED!
#
# Bu dosya, yerel yapılandırmanıza özgü bilgiler içerdiğinden *NOT* sürüm kontrol sistemlerine kaydedilmelidir,
# çünkü yerel yapılandırmanıza özgü bilgiler içerir.
#
# SDK dosyasının konumu. Bu dosya yalnızca Gradle tarafından kullanılır.
# Bir Sürüm Kontrol Sistemi kullanırken özelleştirme yapmak için lütfen
# başlık notunu okuyun.
#7 Nisan Perşembe 14:51:14 CEST 2016
sdk.dir=C\:\\Users\\st\\AppData\\Local\\Android\\sdk
9.3.3. IntelliJ IDEA projesinin çalıştırılması
Proje için uygun bir ortam oluşturulduktan sonra, proje aşağıdaki şekilde çalıştırılabilir:
![]() | ![]() | ![]() |
- [1]'te Genymotion Android emülatörü başlatılır;
- [2] komutunda, [app] yürütme yapılandırması çalıştırılır;
- [3]'te, bir çalıştırma yapılandırması oluşturulur;
![]() |
- [1, 3]'te, yapılandırmaya [app] adı verilmiştir;
- [2]'e dönüştürüldü; bu, [app] adlı modülün yürütülmesine karşılık gelir;
- [4]'te, yürütme sırasında IDE'in bize bir yürütme aygıtı önermesi istenir. Burada bu her zaman Genymotion emülatörü olacaktır;
- [5]'te, bu ayarın tüm çalıştırmalarında bu aygıtın korunması belirtilir;
Projenin Genymotion emülatöründe çalıştırılması, aşağıdaki başlangıç komutuyla başlar:

[1]'e ne yazmanız gerektiğini öğrenmek için bir komut penceresi açın ve aşağıdaki komutu girin:
C:\Program Files\Console2>ipconfig
Configuration IP de Windows
Carte Ethernet Ethernet :
Statut du média. . . . . . . . . . . . : Média déconnecté
Suffixe DNS propre à la connexion. . . : ad.univ-angers.fr
Carte réseau sans fil Connexion au réseau local* 3 :
Statut du média. . . . . . . . . . . . : Média déconnecté
Suffixe DNS propre à la connexion. . . :
Carte Ethernet VirtualBox Host-Only Network :
Suffixe DNS propre à la connexion. . . :
Adresse IPv6 de liaison locale. . . . .: fe80::8076:36e6:3b38:5e98%16
Adresse IPv4. . . . . . . . . . . . . .: 192.168.56.2
Masque de sous-réseau. . . . . . . . . : 255.255.255.0
Passerelle par défaut. . . . . . . . . :
Carte Ethernet Ethernet 2 :
Suffixe DNS propre à la connexion. . . :
Adresse IPv6 de liaison locale. . . . .: fe80::d0d9:e01f:ddde:1f4b%14
Adresse IPv4. . . . . . . . . . . . . .: 192.168.95.1
Masque de sous-réseau. . . . . . . . . : 255.255.255.0
Passerelle par défaut. . . . . . . . . :
Carte réseau sans fil Wi-Fi :
Suffixe DNS propre à la connexion. . . :
Adresse IPv6 de liaison locale. . . . .: fe80::54b3:afe5:e199:2206%10
Adresse IPv4. . . . . . . . . . . . . .: 192.168.0.13
Masque de sous-réseau. . . . . . . . . : 255.255.255.0
Passerelle par défaut. . . . . . . . . : fe80::523d:e5ff:fe0c:4ad9 192.168.0.1
[1]'e, makinenizdeki adreslerden birini (20, 28, 32. satırlar) yazın. Windows güvenlik duvarınız varsa, Android emülatörünün rastgele sayı sunucusuna erişebilmesi için muhtemelen bunu devre dışı bırakmanız gerekecektir.
Yukarıdaki bilgilerle asenkron isteklerin yürütülmesi, aşağıdaki sonuçları verir:

Her istek, aşağıdaki alanları içeren jSON yanıtını oluşturur:
- aleas: sunucu tarafından üretilen rastgele sayılar;
- idClient: isteğin numarası;
- on: istemci tarafında isteğin yürütme iş parçacığı;
- requestAt: isteğin saati;
- responseAt: yanıtın alındığı saat;
- delay: sunucunun yanıtını göndermeden önce beklediği süre;
- hata: bir hata kodu - hata yoksa 0;
- mesaj: bir hata mesajı - hata yoksa null;
- observedAt: yanıtın gözlemlendiği saat;
- observedOn: yanıtın gözlemlendiği iş parçacığı. Burada her zaman [main] olacaktır; bu, kullanıcı arayüzünün iş parçacığını belirtir;
İstekler asenkron olduğundan ve sunucuya uygulanan bekleme süreleri rastgele olduğundan, yanıtlar dağınık bir sırayla gelir.
9.3.4. Projenin Gradle bağımlılıkları
Proje, [app / build.gradle] dosyasına eklediğimiz bağımlılıklara ihtiyaç duyar:
![]() |
dependencies {
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.springframework.android:spring-android-rest-template:1.0.1.RELEASE'
compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.9'
compile 'io.reactivex:rxandroid:1.1.0'
}
- 2-3. satırlardaki bağımlılıklar, SDK 23 ile birlikte bir Android projesinin standart bağımlılıklarıdır;
- 5. satırdaki bağımlılık, [DAO] katmanının sunucu ile olan iletişimini yöneten Spring nesnesi [RestTemplate]’i getirir;
- 6. satırdaki bağımlılık, uygulama tarafından kullanılan JSON ve [Jackson] kütüphanelerini getirir;
- 7. satırdaki bağımlılık, Ui katmanının [DAO] katmanıyla iletişim kurmak için kullandığı RxAndroid kütüphanesini (ve bununla birlikte RxJava kütüphanesini) getirir;
9.3.5. Android uygulamasının manifest dosyası
![]() |
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.aleas">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="android.aleas.activity.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- 5. satır: İnternet erişimi izin verilmelidir;
9.3.6. [DAO] katmanı
![]() |
![]() | ![]() |
9.3.6.1. [DAO] katmanının [IDao] arayüzü
[DAO] katmanının arayüzü şu şekilde olacaktır:
package android.aleas.dao;
import android.aleas.fragments.Request;
import rx.Observable;
public interface IDao {
// [a,b] aralığında rastgele sayılar
// n sayıda sayı üretilir; burada n, [minCount, maxCount] aralığında rastgele bir sayıdır
// sayılar, milisaniye gecikme süresinden sonra üretilir,
// burada [delay], [minDelay, maxDelay] aralığında bir rastgele sayıdır
public Observable<AleasDaoResponse> getAleas(final Request request);
// web hizmetinin URL aralığı
public void setUrlServiceWebJson(String url);
// bağlantı isteğine sunucunun vereceği yanıt için maksimum bekleme süresi (ms)
// bir isteğe sunucunun yanıt vermesi için maksimum bekleme süresi (ms)
public void setClientTimeouts(int connectTimeout, int readTimeOut);
}
- 12. satır: [DAO] katmanının rastgele sayıları asenkron olarak sağlayan yöntemi;
- satır 15: [DAO] uygulamasına, rastgele sayı üretme hizmetinin URL'sini belirtmek için;
- satır 19: Sunucu yanıt vermediğinde çok uzun bekleme sürelerini önlemek amacıyla [DAO] uygulamasına maksimum bekleme sürelerini ayarlamak için;
[getAleas] yöntemi, tüm parametrelerini aşağıdaki [Request] nesnesinden alır:
package android.aleas.fragments;
public class Request {
// istek numarası
int id;
// kullanıcı girişleri
private int nbRequests;
private int a;
private int b;
private int minCount;
private int maxCount;
private int minDelay;
private int maxDelay;
// yapıcılar
public Request() {
}
public Request(int id, int nbRequests, int a, int b, int minCount, int maxCount, int minDelay, int maxDelay) {
this.id = id;
this.nbRequests = nbRequests;
this.a = a;
this.b = b;
this.minCount = minCount;
this.maxCount = maxCount;
this.minDelay = minDelay;
this.maxDelay = maxDelay;
}
// getter ve setter'lar
...
}
Burada, sorgulanacak sunucunun URL nesnesindeki parametrelerin çoğunu görebiliriz.
[getAleas] yöntemi, Observable<AleasDaoResponse> türünde bir değer döndürür; burada [AleasDaoResponse] sınıfı şu şekildedir:
package android.aleas.dao;
import java.util.List;
public class AleasDaoResponse {
// hata kodu
private int erreur;
// hata mesajı
private String message;
// sunucu bekleme süresi
private int delay;
// sunucu tarafından üretilen rastgele sayılar
private List<Integer> aleas;
// istemci durumu
private ClientState clientState;
// yapıcılar
public AleasDaoResponse() {
}
public AleasDaoResponse(int erreur, String message, int delay, List<Integer> aleas, ClientState clientState) {
this.erreur = erreur;
this.message = message;
this.delay = delay;
this.aleas = aleas;
this.clientState = clientState;
}
// alıcı ve ayarlayıcılar
...
}
[ClientState] türü şu şekildedir:
package android.aleas.dao;
import org.codehaus.jackson.map.annotate.JsonFilter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class ClientState {
// çalışma iş parçacığının adı
private String on;
// istek saati
private String requestAt;
// yanıt saati
private String responseAt;
// istemci kimliği
private int idClient;
// oluşturucu
public ClientState() {
on = Thread.currentThread().getName();
requestAt = getTimeStamp();
}
public ClientState(int idClient) {
this();
this.idClient = idClient;
}
// özel yöntemler
private String getTimeStamp() {
return new SimpleDateFormat("hh:mm:ss:SSS").format(Calendar.getInstance().getTime());
}
// alıcı ve ayarlayıcılar
...
}
- 11. satır: [DAO] katmanının yürütme iş parçacığı;
- 13. satır: İsteğin saati;
- 15. satır: yanıt saati;
- satır 17: istek numarası;
[on, requestAt, idClient] alanları, isteğin başlangıcında istemci tarafından başlatılır. [responseAt] alanı, istemci sunucudan yanıtı aldığında başlatılır.
9.3.6.2. [DAO] katmanının uygulanması
![]() |
[IDao] arayüzü, aşağıdaki [Dao] sınıfı ile uygulanır:
package android.aleas.dao;
import android.aleas.fragments.Request;
import android.util.Log;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import rx.Subscriber;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class Dao implements IDao {
// müşteri REST
private RestTemplate restTemplate;
// URL hizmet
private String urlServiceWebJson;
// eşleştirici jSON
private ObjectMapper mapper;
// üreticiler
public Dao() {
// eşleştirici jSON
mapper = new ObjectMapper();
}
@Override
public Observable<AleasDaoResponse> getAleas(final Request request) {
...
}
@Override
public void setUrlServiceWebJson(String urlServiceWebJson) {
// REST hizmetinin URL'i belirlenir
this.urlServiceWebJson = urlServiceWebJson;
}
@Override
public void setClientTimeouts(int connectTimeout, int readTimeOut) {
...
}
}
- 22. satır: Rastgele sayı sunucusuyla iletişimi sağlayacak [RestTemplate] nesnesi;
- 24. satır: Üretim hizmetine ait URL nesnesi – 41. satırdaki [setUrlServiceWebJson] yöntemi tarafından belirlenir;
- 27. satır: Rastgele sayı sunucusu tarafından gönderilen jSON dizesini deserialize etmek için kullanılacak jSON eşleştiricisi;
- satır 30-33: sınıfın oluşturucusu;
- 32. satır: 27. satırdaki jSON eşleştiricisi oluşturulur;
[setClientTimeouts] yöntemi şu şekildedir:
// REST istemcisi
private RestTemplate restTemplate;
...
@Override
public void setClientTimeouts(int connectTimeout, int readTimeOut) {
// REST istemcisinin istek zaman aşımı ayarlanır
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(readTimeOut);
factory.setConnectTimeout(connectTimeout);
restTemplate = new RestTemplate(factory);
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
}
- İstemci ile web sunucusu / JSON arasındaki iletişim, 2. satırdaki [RestTemplate] nesnesi tarafından sağlanır. Şu an için bunu başlatmadık. Bunu [setClientTimeouts] yöntemi gerçekleştirir;
- 8. satır: [HttpComponentsClientHttpRequestFactory] sınıfı, [spring-android-rest-template] bağımlılığı tarafından sağlanır. Bu sınıf, sunucudan gelecek yanıt için maksimum bekleme sürelerini belirlememizi sağlayacaktır (9-10. satırlar);
- 11. satır: Web hizmeti ile iletişimin temelini oluşturacak [RestTemplate] türündeki nesneyi oluşturuyoruz. Az önce oluşturulan [factory] nesnesini bu nesneye parametre olarak aktarıyoruz;
- 12. satır: İstemci-sunucu diyaloğu çeşitli şekillerde gerçekleşebilir. Veri alışverişi metin satırları üzerinden yapılır ve [RestTemplate] türündeki nesneye bu metin satırıyla ne yapması gerektiğini belirtmemiz gerekir. Bunun için, metin satırlarını işleyebilen dönüştürücüler ve sınıflar sağlarız. Dönüştürücünün seçimi genellikle metin satırına eşlik eden HTTP başlıklarına göre yapılır. Bu başlıklara göre, [RestTemplate] nesnesi, sahip olduğu dönüştürücüler arasından duruma en uygun olanı seçecektir. Burada tek bir dönüştürücümüz olacak: String --> String dönüştürücüsü. Bu sayede, sunucudan alınan String türü herhangi bir dönüşüme uğramayacaktır.
[getAleas] yöntemi en karmaşık yöntemdir:
@Override
public Observable<AleasDaoResponse> getAleas(final Request request) {
Log.d("rxjava", String.format("service [DAO] pour client n° %s%n", request.getId()));
// hizmetin çalıştırılması
return Observable.create(new Observable.OnSubscribe<AleasDaoResponse>() {
@Override
public void call(Subscriber<? super AleasDaoResponse> subscriber) {
try {
// URL hizmetinden: /{a}/{b}/{minCount}/{maxCount}/{minDelay}/{maxDelay}
String urlService = String.format("%s/%s/%s/%s/%s/%s/%s",
urlServiceWebJson, request.getA(), request.getB(), request.getMinCount(),
request.getMaxCount(), request.getMinDelay(), request.getMaxDelay());
// müşteri bilgileri
ClientState clientState = new ClientState(request.getId());
// senkron HTTP isteği
String response = executeRestService("get", urlService, null);
// sunucudan gelen jSON yanıtının serileştirilmesinin kaldırılması
AleasServerResponse aleasServerResponse = mapper.readValue(
response,
new TypeReference<AleasServerResponse>() {
});
// hata mı?
int erreur = aleasServerResponse.getErreur();
if (erreur != 0) {
// istisna iletiliyor
subscriber.onError(new AleasException(aleasServerResponse.getMessage(), erreur));
} else {
// alınma saati kaydediliyor
clientState.setResponseAt();
// sonuç aboneye iletiliyor
subscriber.onNext(
new AleasDaoResponse(aleasServerResponse.getErreur(), aleasServerResponse.getMessage(),
aleasServerResponse.getDelay(), aleasServerResponse.getAleas(), clientState));
}
} catch (Exception ex) {
// istisna aboneye iletiliyor
subscriber.onError(ex);
} finally {
// gözlemlenebilirin sonu bildirilir
// çalıştırma sırasında, [onError] yöntemi daha önce çağrılmışsa bu yöntemin hiçbir etkisi olmadığı fark edilir teoriye uygun olarak - bu nedenle bu komutu yalnızca try bloğuna yerleştirebiliriz
subscriber.onCompleted();
}
}
});
}
- 2. satır: [Observable<AleasResponse>] türünde bir sonuç üretmemiz gerektiğini unutmamalıyız;
- 3. satır: Android konsoluna bir günlük satırı yazılır;
- 5. satır: [RestTemplate] nesnesi, sunucu ile senkron bir iletişim sağlar. Bu, isteği gönderen yürütme iş parçacığının yanıt alınana kadar bloke olacağı anlamına gelir. Swing örneğinde, [Observable.create] yöntemi sayesinde senkron bir eylemi asenkron bir eyleme nasıl dönüştüreceğimizi görmüştük. Burada da aynı yolu izliyoruz;
- 7. satır: 5. satırdaki [Observable.OnSubscribe<AleasDaoResponse>] arayüzünün [call] yöntemi. Bir gözlemci gözlemlenene abone olduğunda çağrılan yöntem budur;
- 10-12. satırlar: rastgele sayı hizmetinin URL nesnesinin oluşturulması;
- 14. satır: [ClientState] nesnesinin başlatılması. Burada isteğin saati kaydedilir;
- satır 16: senkron HTTP isteği. jSON yanıtı alınır. [executeRestService] yöntemi üç parametre bekler:
- hizmeti sorgulamak için kullanılacak HTTP yöntemi;
- hizmetin URL'i;
- gönderilecek Object türündeki nesne; HTTP yöntemi POST değilse null;
- 18-21: Alınan jSON dizesinin [AleasServerResponse] türüne deserializasyonu. Bu tür şöyledir:
package android.aleas.dao;
import java.util.List;
public class AleasServerResponse {
// hata kodu
private int erreur;
// hata mesajı
private String message;
// sunucu bekleme süresi
private int delay;
// rastgele sayılar
private List<Integer> aleas;
// alıcı ve ayarlayıcılar
...
}
- 23. satır: sunucu tarafından gönderilen hata kodu alınır;
- 24-26. satırlar: Hata durumunda, aboneye bir istisna iletilir;
- 29. satır: aboneye gönderilecek yanıtın bir parçası olacak olan [clientState] güncellenir;
- 31-33. satırlar: aboneye yanıtın gönderilmesi. Yanıtın biçimi [AleasDaoResponse] şeklindedir;
- satır 35-37: tüm hata durumlarını ayırt etmeden ele alır. En olası hata, bir ağ hatasıdır;
- 41. satır: İletim sonu bildirimi;
9.3.7. Uygulamanın görünümleri
![]() |
![]() |
Uygulama aşağıdaki iki görünümü sunar:
İstek görünümü

Yanıt görünümü

9.3.7.1. [MyFragment] sınıfı
İki parça vardır:
- [RequestFragment] istek için;
- Yanıt için [ResponseFragment];
Her iki parça da aşağıdaki [MyFragment] sınıfını genişletir:
package android.aleas.fragments;
import android.aleas.activity.MainActivity;
import android.aleas.activity.Session;
import android.support.v4.app.Fragment;
public abstract class MyFragment extends Fragment {
// ------------- parçalar arasında ortak veriler
protected MainActivity activity;
protected Session session;
public abstract void onRefresh();
}
- 7. satır: [MyFragment] sınıfı, Android [Fragment] sınıfını genişletir;
- 10-11. satırlar: tüm parçalar için ortak veriler;
- 10. satır: Her fragman, uygulamanın tek aktivitesini tanır;
- 11. satır: Fragmanlar birbirleriyle iletişim kurmak için bir oturum kullanır;
- satır 13: Bir fragmanı görüntülemeden önce, fragmandan oturum içeriğiyle kendini yenilemesi istenir. Bu yöntem, alt sınıflar tarafından uygulandığı için soyut olarak tanımlanmıştır. Bu nedenle, sınıfın kendisi de soyut olarak tanımlanmıştır (satır 7);
[Session] sınıfı, uygulamanın farklı parçalarının paylaştığı verileri içerir. Kod şöyledir:
![]() |
package android.aleas.activity;
import android.aleas.fragments.Request;
import android.widget.ArrayAdapter;
public class Session {
// uygulama etkinliği
private MainActivity activity;
// istek sayısı
private int nbRequests;
// istek özellikleri
private int a;
private int b;
private int minCount;
private int maxCount;
private int minDelay;
private int maxDelay;
// URL web hizmeti / jSON
private String urlWebJson;
// işlem başladı
private boolean onAir;
// aynı, ancak zaman açısından biraz daha geç
private boolean operationStarted;
// kullanıcı tarafından örnek listesinden seçilen örneğin adı
private String exampleName;
// parça listesindeki numarası
private int examplePosition;
// istek görünümündeki örnek döndürücüsünün adaptörü
private ArrayAdapter<CharSequence> spinnerExemplesAdapter;
// yöntemleri
public void setInfos(int nbRequests, int a, int b, int minCount, int maxCount, int minDelay, int maxDelay, String urlWebJson, String exampleName, int examplePosition) {
this.nbRequests = nbRequests;
this.a = a;
this.b = b;
this.minCount = minCount;
this.maxCount = maxCount;
this.minDelay = minDelay;
this.maxDelay = maxDelay;
this.urlWebJson = urlWebJson;
this.exampleName = exampleName;
this.examplePosition = examplePosition;
}
public Request getRequest() {
return new Request(0, nbRequests, a, b, minCount, maxCount, minDelay, maxDelay);
}
// alıcı ve ayarlayıcılar
...
}
- satırdaki yöntem, sorgu görünümünde kullanıcı tarafından girilen tüm bilgileri kapsayan [Request] nesnesini oluşturmaya olanak tanır:
![]() |
package android.aleas.fragments;
public class Request {
// istek numarası
int id;
// kullanıcı girişleri
private int nbRequests;
private int a;
private int b;
private int minCount;
private int maxCount;
private int minDelay;
private int maxDelay;
// oluşturucular
public Request() {
}
public Request(int id, int nbRequests, int a, int b, int minCount, int maxCount, int minDelay, int maxDelay) {
this.id = id;
this.nbRequests = nbRequests;
this.a = a;
this.b = b;
this.minCount = minCount;
this.maxCount = maxCount;
this.minDelay = minDelay;
this.maxDelay = maxDelay;
}
// getter ve setter'lar
....
}
9.3.7.2. Sorgudaki [RequestFragment] parçası
Sorgunun bu parçası aşağıdaki bileşenleri içerir:

Uygulamanın, iki sekme içeren tek bir görünümü vardır:
- [1]: istek sekmesi;
- [2]: yanıt sekmesi;
[RequestFragment] parçasının bileşenleri şunlardır:
No | Tür | Ad | İşlev |
3 | EditText | edtNbRequests | rastgele sayı üretme hizmetine gönderilecek istek sayısı |
4 | EditText | edtA, edtB | sayı üretme aralığının sınırları [a,b]; |
5 | EditText | edtMinCount, edtMaxCount | hizmet, count sayıları üretir; burada count, [minCount, maxCount] aralığındaki bir rastgele sayıdır |
6 | EditText | edtMinDelay, edtMaxDelay | hizmet, delay milisaniye bekledikten sonra sayıları üretir; burada delay, [minDelay, maxDelay] aralığında rastgele bir sayıdır |
7 | EditText | edtUrlServiceRest | URL aralığı içinde rastgele sayı üretme hizmetinden; |
8 | Spinner | spinnerExemples | örnekler açılır listesi. Her örnek, [Observable] sınıfının belirli bir yöntemini göstermektedir; |
8 | Button | btnExecuter | sayı üretme hizmetine çağrıları başlatan düğme; |
Giriş hataları şu şekilde bildirilir:

1'den 6'ya kadar olan bileşenler, sırasıyla aşağıdaki adlara sahip [TextView] bileşenleridir: txtErrorRequests, txtErrorIntervalle, txtErrorCount, txtErrorDelay, txtMsgErreurUrlServiceWeb.
9.3.7.3. Yanıtın [ResponseFragment] parçası
Yanıt parçası aşağıdaki bileşenleri içerir:

no | Tür | Ad | Rol |
1 | TextView | infoReponses | Alınan yanıt sayısı |
2 | ListView | listReponses | sunucudan alınan jSON kanalları listesi |
3 | Düğme | btnAnnuler | sunucuya yapılan istekleri iptal etmek için |
9.3.7.4. Android etkinliği [MainActivity]
![]() |
![]() |
[MainActivity] sınıfı aşağıdaki [] görünümünü görüntüler:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="android.arduinos.ui.activity.MainActivity">
<!-- uygulama çubuğu -->
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">
<!-- araç çubuğu -->
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways">
<!-- bekleme görüntüsü -->
<ProgressBar
android:id="@+id/loadingPanel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
</android.support.v7.widget.Toolbar>
<!-- sekme konteyneri -->
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>
<!-- görünüm konteyneri -->
<android.aleas.activity.MyPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:layout_marginBottom="100dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
Bu görünümün bileşenleri şunlardır:
satırlar | Tür | Ad | Rol |
20-34 | Araç Çubuğu | araç çubuğu | uygulama araç çubuğu |
29-34 | ProgressBar | loadingPanel | kullanıcının isteği işlenirken görüntülenen bekleme görüntüsü |
37-40 | TabLayout | sekmeler | uygulamanın sekme çubuğu |
44-51 | MyPager | konteyner | uygulamanın çeşitli parçalarının görüntülendiği konteyner |
[MyPager] sınıfı şu şekildedir:
package android.aleas.activity;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class MyPager extends ViewPager {
// kaydırma kontrolü
private boolean isSwipeEnabled;
// oluşturucular
public MyPager(Context context) {
super(context);
}
public MyPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
// yöntemlerin yeniden tanımlanması
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// kaydırma izinli mi?
if (isSwipeEnabled) {
return super.onInterceptTouchEvent(event);
} else {
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// kaydırma izinli mi?
if (isSwipeEnabled) {
return super.onTouchEvent(event);
} else {
return false;
}
}
// setter
public void setSwipeEnabled(boolean isSwipeEnabled) {
this.isSwipeEnabled = isSwipeEnabled;
}
}
- [MyPager] sınıfı, standart Android sınıfı [ViewPager]'i genişletir. [MyPager] sınıfını, [ViewPager] sınıfı yerine kullanmamızın tek nedeni, kaydırma hareketini engellemek istememizdir: varsayılan olarak [ViewPager] sınıfında, bir sekmeden diğerine kaydırma hareketiyle (sola veya sağa kaydırma). Burada bu davranışı istemiyoruz;
- 11. satır: kaydırma hareketini kontrol edecek boole değeri (26. ve 36. satırlar);
- 44-46. satırlar: 11. satırdaki alanı başlatmaya yarayan yöntem;
Android etkinliği [MainActivity]'in iskeleti şöyledir:
package android.aleas.activity;
import android.aleas.R;
import android.aleas.dao.AleasDaoResponse;
import android.aleas.dao.Dao;
import android.aleas.dao.IDao;
import android.aleas.fragments.MyFragment;
import android.aleas.fragments.Request;
import android.aleas.fragments.RequestFragment;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ProgressBar;
import rx.Observable;
public class MainActivity extends AppCompatActivity implements IDao {
// katman [DAO]
private IDao dao;
// oturum
private Session session;
// oluşturucu
public MainActivity() {
// üst
super();
// oturum
session = new Session();
// DAO
dao = new Dao();
}
// alıcılar
public Session getSession() {
return session;
}
// uygulama IDao ----------------------------------------
@Override
public Observable<AleasDaoResponse> getAleas(Request request) {
return dao.getAleas(request);
}
@Override
public void setUrlServiceWebJson(String url) {
dao.setUrlServiceWebJson(url);
}
@Override
public void setClientTimeouts(int connectTimeout, int readTimeOut) {
dao.setClientTimeouts(connectTimeout, readTimeOut);
}
}
- 21. satır: [MainActivity] sınıfı, standart Android sınıfı [AppCompatActivity]'i genişletir. Dolayısıyla bu, standart bir Android aktivitesidir;
- 21. satır: [MainActivity] sınıfı, [IDao] arayüzünü uygular;
Uygulamanın mimarisine geri dönersek:
![]() |
Aktivitenin [DAO] katmanının arayüzünü uygulaması, görünümlerin [DAO] katmanından haberdar olmamasına olanak tanır: sunucuyla iletişim kurmak istediklerinde olay işleyicileri [activité] katmanına başvuracaktır.
- 24. satır: 35. satırdaki oluşturucu tarafından başlatılan [DAO] katmanına bir referans;
- 26. satır: 33. satırdaki oluşturucu tarafından başlatılan, parçalar arasında paylaşılan oturuma bir referans;
- satır 46-59: [IDao] arayüzünün uygulaması;
[MainActivity] sınıfı, kendisine atanan görünümün bileşenlerini şu şekilde başlatır:
// araç çubuğu
private Toolbar toolbar;
// parça yöneticisi
private MyPager mViewPager;
// sekme konteyneri
private TabLayout tabLayout;
// bekleme görüntüsü
private ProgressBar loadingPanel;
...
@Override
public void onCreate(Bundle savedInstanceState) {
// klasik
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// oturum
session.setActivity(this);
// katman zaman aşımı yapılandırması [DAO]
setClientTimeouts(Constants.CONNECT_TIMEOUT, Constants.READ_TIMEOUT);
// bileşenler
mViewPager = (MyPager) findViewById(R.id.container);
toolbar = (Toolbar) findViewById(R.id.toolbar);
loadingPanel = (ProgressBar) findViewById(R.id.loadingPanel);
tabLayout = (TabLayout) findViewById(R.id.tabs);
// araç çubuğu
setSupportActionBar(toolbar);
// başlangıçta sadece bir sekme var
TabLayout.Tab tab = tabLayout.newTab();
tab.setText("Request");
tabLayout.addTab(tab);
// olay yöneticisi
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
// bir sekme seçildi - parça konteyneri tarafından görüntülenen parça değiştiriliyor
int position = tab.getPosition();
if (position == 0) {
// istek sekmesi
showView(0);
} else {
// yanıt sekmesi - seçilen örneğe bağlıdır
showView(session.getExamplePosition());
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
// yanıt parçalarının oluşturulması
createResponseFragments();
// bekleme görüntüsü yönetimi
loadingPanel.setVisibility(View.INVISIBLE);
}
Bu kod, bir aktivitede oldukça yaygın bir koddur. Bazı noktaları açıklayalım:
- 19. satır, aşağıdaki [Constants] sınıfına atıfta bulunur:
package android.aleas.activity;
abstract public class Constants {
final static public int VUE_REQUEST = 0;
final static public int VUE_RESPONSE = 1;
final static public int CONNECT_TIMEOUT = 1000;
final static public int READ_TIMEOUT = 6000;
final static public int DELAY_MAX = 5000;
final static public String EXAMPLES_PACKAGE = "android.aleas.exemples";
}
- 31-33. satırlar: [Request] başlıklı ilk sekme oluşturulur. Bir noktada, bellekte şunlar olacaktır:
- [Request] parçası;
- [ExampleXXFragment] türünde n parça;
İlk sekme her zaman [Request] parçasını gösterecektir. İkinci sekme ise kullanıcının seçtiği örneğe karşılık gelen [ExampleXXFragment] parçasını gösterecektir. Dolayısıyla, ikinci sekmede gösterilen parça zamanla değişir;
- 37-48. satırlar: Kullanıcı sekmelerden birine tıkladığında yürütülen kod;
- 43. satır: 0 numaralı parça görüntülenir;
- 46. satır: şu anda kullanımda olan (görüntülenen) parça görüntülenir. Parçanın numarası oturumda bulunur;
- satır 62: [RequestFragment] görünümündeki (1. sekme) örnek seçici içinde bulunan tüm örneklerin parçaları oluşturulur;
- 65. satır: Bekleme görüntüsü şimdilik gizlenir;
[showView] yöntemini (43. ve 46. satırlar) ve [createResponseFragments] yöntemini anlamak için öncelikle bellek içi parça yöneticisini (MainActivity Java dosyasında bulunan sınıf) tanıtmamız gerekir:
// parça yöneticisi - getItem ve getCount yöntemlerini tanımlamalıdır
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// yönetilen parçalar
private MyFragment[] fragments;
// oluşturucu
public SectionsPagerAdapter(FragmentManager fm, MyFragment[] fragments) {
super(fm);
this.fragments = fragments;
}
// parça n° konumunu döndürmelidir
@Override
public MyFragment getItem(int position) {
// parça
return fragments[position];
}
// yönetilecek parça sayısını döndürür
@Override
public int getCount() {
// parça sayısı
return fragments.length;
}
}
}
- [SectionsPagerAdapter] sınıfı, Android [FragmentPagerAdapter] sınıfını genişletir. Bu sınıf, üst sınıfının iki yöntemini yeniden tanımlar:
- [getItem] yöntemi, 15. satır;
- [getCount] yöntemi, 22. satır;
- [SectionsPagerAdapter] sınıfı, uygulamanın tüm parçalarını içerir. Bunlar 5. satırda depolanır. Bunların, 9.3.7.1 numaralı paragrafta tanıtılan [MyFragment] türünde olduğuna dikkat edilmelidir;
- 8. satır: [SectionsPagerAdapter] sınıfı, oluşturulurken yönetmesi gereken parçaları alır;
- 14-18. satırlar: [getItem] yöntemi, [position] konumundaki parçayı döndürür;
- 21-25. satırlar: [getCount] yöntemi, toplam parça sayısını döndürür;
[createResponseFragments] yöntemi, uygulamanın ihtiyaç duyduğu tüm parçaları oluşturur:
private void createResponseFragments() {
// örnekler için döner menü
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.exemples, android.R.layout.simple_spinner_item);
// Seçenek listesi görüntülendiğinde kullanılacak düzeni belirtin
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// [Request] görünümünün adaptörü alabilmesi için adaptörü oturuma yerleştirin
session.setSpinnerExemplesAdapter(adapter);
...
}
- 3. satır: Örneklerin döner menüsü için bir adaptör oluşturulur; burada, örneklerin adlarını temsil eden String listesi yer alır. Bu adlar, [layout/exemples.xml] dosyasında bulunur:
![]() |
[exemples.xml] dosyası aşağıdaki kodu içerir:
<!-- örnekler -->
<resources>
<string-array name="exemples">
<item>Exemple-01</item>
<item>Exemple-02</item>
<item>Exemple-03</item>
<item>Exemple-04</item>
</string-array>
</resources>
- satır: Bu dosya, [createFromResource] yönteminin ikinci parametresidir. [R.array.exemples]'te, [exemples], yukarıdaki 3. satırda belirtilen tablonun adıdır, dosya adı değildir.
- 5. satır: Adaptöre bir layout (görüntüleme yöneticisi) atanır. Artık adaptör hem verilere hem de bunların görüntülenme biçimine sahiptir;
- 7. satır: Adaptörü oturuma geçiriyoruz. İhtiyacı olan [RequestFragment] parçası onu buradan alacaktır;
[createResponseFragments] yönteminin koduna devam edelim:
private void createResponseFragments() {
// Örnekler için döner menü
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.exemples, android.R.layout.simple_spinner_item);
// Seçenek listesi görüntülendiğinde kullanılacak düzeni belirtin
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// [Request] görünümünün onu alabilmesi için adaptörü oturuma ekliyoruz
session.setSpinnerExemplesAdapter(adapter);
// parça tablosunun oluşturulması (1 sorgu, n yanıt)
MyFragment[] tFragments = new MyFragment[adapter.getCount() + 1];
// isteğin parçası
tFragments[0] = new RequestFragment();
// yanıt parçaları
for (int i = 1; i < tFragments.length; i++) {
// kullanıcının seçtiği örneğe karşılık gelen, örneklenecek parçanın adı oluşturulur
// bu ad, paketi de içeren tam ad olmalıdır - burada doğrudan spinner'daki örnek numarasıyla ilişkilendirilir
String exampleClassName = String.format("%s.Example%02dFragment", Constants.EXAMPLES_PACKAGE, i);
// örneğe ilişkin parça örneklenir
MyFragment fragment;
try {
// sınıfın örneği oluşturulur
fragment = (MyFragment) Class.forName(exampleClassName).getConstructors()[0].newInstance(new Object[]{});
} catch (Exception e) {
e.printStackTrace();
return;
}
// parça oluşturuldu - diziye eklenir
tFragments[i] = fragment;
}
// parça yöneticisi, bu yeni parçalarla örneklenir
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager(), tFragments);
// ViewPager'i bölüm adaptörüyle kurun.
mViewPager.setAdapter(mSectionsPagerAdapter);
// Sayfalar arasında gezinme - bu komut önemlidir
// Burada, görüntülenen görünümün her iki yanında da [tFragments.length] başlatılmış görünümlerinin tutulması gerektiği belirtilmektedir
// bu, burada uygulamanın kullandığı tüm parçaların bellekte bulunması ve başlatılmış olması anlamına gelir
// Bunu yapmazsak, varsayılan olarak [OffscreenPageLimit] değeri 1 olur
// böylece görüntülenen parça 3 numaralıysa, yalnızca 2 ve 4 numaralı parçalar başlatılacaktır
// Bu, bu iki parçanın [onCreateView] yönteminin çağrılmasıyla gerçekleşir — bu, bu yöntemde şunların sağlanması gerektiği anlamına gelir:
// parçanın en son kullanıldığı zamanki görsel görünümünü yeniden oluşturmak gerekir; ayrıca, bu yöntemde
// iki kez çalıştırılmaya dayanamayacak kod bulunmamalıdır — bu, devasa bir karmaşaya yol açar ve yönetimi zordur
// burada bu zorlukları önlemeyi tercih ettik – günlüklerde, uygulamanın başlatılması sırasında tüm parçacıkların oluşturulduğu görülüyor
// ve bunların [onCreateView] yöntemleri çalıştırılıyor — daha sonra bir daha asla çalıştırılmıyor —
mViewPager.setOffscreenPageLimit(tFragments.length);
// parçalar arasında kaydırma engelleniyor
mViewPager.setSwipeEnabled(false);
}
- 9. satır: Uygulamanın tüm parçalarını içerecek dizinin oluşturulması;
- 11. satır: İlk fragman, isteğin fragmanıdır;
- satır 13-28: örnek sayısı kadar fragman oluşturulacak. Bu fragmanların tümü, [ResponseFragment] yanıt fragmanını genişletir ve yalnızca örneğe özgü olanı uygular: gözlemlenen değerlerin oluşturulması. Bu değerler aslında örnekten örneğe farklılık gösterir;
- 16. satır: Bir örneğin parçası standart bir ada sahiptir: ExampleXXFragment; burada XX, örnekler listesindeki konumuna 1 eklenmiş halidir. XX, aynı zamanda parça yöneticisindeki örnek parçasının numarasıdır;
- 21. satır: döner menüdeki i numaralı örnek parçasının örneklenmesi:
- Class.forName(exampleName): parçayı belleğe yükler;
- Class.forName(exampleName).getConstructors()[0]: Sınıfın ilk oluşturucusunun referansını alır. ExampleXXFragment sınıfının tek bir yapıcı işlevi vardır. Dolayısıyla, bu yapıcı işlevine ait bir referans alınacaktır;
- Class.forName(exampleName).getConstructors()[0].newInstance(new Object[]{}) ifadesi, önceki adımdaki oluşturucuyu kullanarak ExampleXXFragment türünde bir nesne oluşturur. new Object[]{}, bu oluşturucuya geçirilen parametreleri temsil eder. ExampleXXFragment sınıfının oluşturucusu parametre beklemediğinden, boş bir nesne dizisi aktarılır;
- 27. satır: bu parça, parça dizisine eklenir;
- satır 30: [SectionsPagerAdapter] parça yöneticisinin oluşturucusunun, parametrelerinde yönetmesi gereken parça dizisini beklediğini görmüştük. Şimdi bu dizi ona aktarılır;
- 22. satır: [MainActivity] etkinliğine bağlı görünümün [mViewPager] fragman konteyneri burada fragman yöneticisiyle ilişkilendirilir: [mViewPager] fragman konteyneri, fragman yöneticisinin fragmanlarını görüntüler;
- 43. satır: Açıklamaları okuyalım – bu talimat, o anda görüntülenen parça ne olursa olsun, tüm parçaların kodun yerleştirdiği durumda kalması gerektiğini ifade eder. Dolayısıyla, o parçaya geri döndüğümüzde, onu bıraktığımız durumda buluruz;
- satır 45: [mViewPager] fragman konteyneri, kaydırma hareketini engellemeye olanak tanıyan [MyPager] türündedir;
[MainActivity.showView] yöntemi şu şekildedir:
// [position] numaralı görünüm görüntülenir
private void showView(int position) {
// parça görüntülenmeden önce yenilenir
mSectionsPagerAdapter.getItem(position).onRefresh();
// istenen görünüm görüntülenir - doğrudan o görünüme geçilir (ikinci parametre false olarak ayarlanır)
// bu parametre olmadan, ara görünümler hızlıca görüntülenerek varsayılan olarak istenen görünüme gidilir - istenmeyen davranış
mViewPager.setCurrentItem(position, false);
}
- 3. satır: konum numaralı parçayı görüntülemek istiyoruz;
- 4. satır: Bu fragman, fragman yöneticisinden istenir ve ardından yenilenir. Zira, en son görüntülendiğinden bu yana oturum değişmiş olabilir. Fragman, güncellenmesi gerekip gerekmediğini görmek için oturumu kontrol etmelidir;
- 7. satır: Parça, [ViewPager] tarafından görüntülenir. Bu parça, parça yöneticisiyle ilişkilendirildiğinden, 4. satırda yenilenen [position] numaralı parça görüntülenecektir;
Son olarak, iki bekleme yönetimi yöntemini ele alalım:
public void beginWaiting() {
// bekleme görüntüsü yönetimi
loadingPanel.setVisibility(View.VISIBLE);
}
public void cancelWaiting() {
// bekleme görüntüsü yönetimi
loadingPanel.setVisibility(View.INVISIBLE);
// çalıştırma sonu
session.setOnAir(false);
session.setOperationStarted(false);
}
9.3.7.5. [RequestFragment] parçası
[RequestFragment] sınıfı şu şekildedir:
package android.aleas.fragments;
import android.aleas.R;
import android.aleas.activity.Constants;
import android.aleas.activity.MainActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import java.net.URI;
import java.net.URISyntaxException;
public class RequestFragment extends MyFragment {
// URL web hizmeti
private EditText edtUrlServiceRest;
private TextView txtMsgErreurUrlServiceWeb;
// istek sayısı
private EditText edtNbRequests;
private TextView txtErrorRequests;
// oluşturma aralığı
private EditText edtA;
private EditText edtB;
private TextView txtErrorIntervalle;
// gecikme
private EditText edtMinDelay;
private EditText edtMaxDelay;
private TextView txtErrorDelay;
// oluşturulan değer sayısı
private EditText edtMinCount;
private EditText edtMaxCount;
private TextView txtErrorCount;
// düğme
private Button btnExecuter;
// yanıt listesi
private ListView listReponses;
private TextView infoReponses;
// örnekler için döner menü
private Spinner spinnerExemples;
// girişler
private int nbRequests;
private int a;
private int b;
private String urlServiceWebJson;
private int minDelay;
private int maxDelay;
private int minCount;
private int maxCount;
// oluşturucu
public RequestFragment() {
super();
Log.d("rxjava", "RequestFragment constructor");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("rxjava", "RequestFragment onCreateView");
// etkinlik ve oturumu alıyoruz
activity = (MainActivity) getActivity();
session = activity.getSession();
// tanımından yola çıkarak parça görünümünü oluşturma XML
View view = inflater.inflate(R.layout.request, container, false);
// bileşenler
edtUrlServiceRest = (EditText) view.findViewById(R.id.editTextUrlServiceWeb);
txtMsgErreurUrlServiceWeb = (TextView) view.findViewById(R.id.textViewErreurUrl);
edtNbRequests = (EditText) view.findViewById(R.id.edt_nbrequests);
txtErrorRequests = (TextView) view.findViewById(R.id.txt_error_nbrequests);
edtA = (EditText) view.findViewById(R.id.edt_a);
edtB = (EditText) view.findViewById(R.id.edt_b);
txtErrorIntervalle = (TextView) view.findViewById(R.id.txt_errorIntervalle);
edtMinDelay = (EditText) view.findViewById(R.id.edt_minDelay);
edtMaxDelay = (EditText) view.findViewById(R.id.edt_maxDelay);
txtErrorDelay = (TextView) view.findViewById(R.id.txt_error_delay);
edtMinCount = (EditText) view.findViewById(R.id.edt_minCount);
edtMaxCount = (EditText) view.findViewById(R.id.edt_maxCount);
txtErrorCount = (TextView) view.findViewById(R.id.txt_error_count);
btnExecuter = (Button) view.findViewById(R.id.btn_Executer);
listReponses = (ListView) view.findViewById(R.id.lst_reponses);
infoReponses = (TextView) view.findViewById(R.id.txt_Reponses);
spinnerExemples = (Spinner) view.findViewById(R.id.spinnerExemples);
// düğme [Exécuter]
btnExecuter.setVisibility(View.VISIBLE);
btnExecuter.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
doExecuter();
}
});
// başlangıçta hata mesajı yok
txtErrorRequests.setVisibility(View.INVISIBLE);
txtErrorIntervalle.setVisibility(View.INVISIBLE);
txtMsgErreurUrlServiceWeb.setVisibility(View.INVISIBLE);
txtErrorCount.setVisibility(View.INVISIBLE);
txtErrorDelay.setVisibility(View.INVISIBLE);
// örnekler için dönen simge
spinnerExemples.setAdapter(session.getSpinnerExemplesAdapter());
// sonuç
return view;
}
...
}
- 16. satır: [RequestFragment] sınıfı, [MyFragment] sınıfını genişletir (bkz. paragraf 9.3.7.1);
- satır 18-42: parçanın görsel bileşenleri (bkz. paragraf 9.3.7.2);
- satır 45-52: kullanıcı tarafından forma girilen veriler;
- [MainActivity] etkinliği, uygulamanın tüm parçalarını oluştururken, oluşturucu (satır 55-58) ve [onCreateView] yöntemi yürütülür. Bu, tek seferlik bir işlemdir;
- satır 61: [onCreateView] yönteminin kodu standarttır. Satır 102'de, örneklerdeki spinner adaptörünün oturumda tutulduğuna dikkat edilmelidir. Ayrıca 91. satırda, [Exécuter] düğmesine tıklamanın [doExecuter] yöntemi tarafından işlendiğine dikkat edilmelidir;
- 64-65. satırlar: [activity] ve [session] alanları, üst sınıf olan [MyFragment]'e aittir;
[doExecuter] yöntemi şu şekildedir:
// girdiler
private int nbRequests;
private int a;
private int b;
private String urlServiceWebJson;
private int minDelay;
private int maxDelay;
private int minCount;
private int maxCount;
...
private void doExecuter() {
// girdiler geçerli mi?
if (isPageValid()) {
// bilgiler oturuma kaydediliyor
session.setInfos(nbRequests, a, b, minCount, maxCount, minDelay, maxDelay, urlServiceWebJson, spinnerExemples.getSelectedItem().toString(), spinnerExemples.getSelectedItemPosition() + 1);
// web hizmetinin URL'i kaydediliyor
activity.setUrlServiceWebJson(session.getUrlWebJson());
Log.d("rxjava", String.format("RequestFragment doExecuter, session=%s, session.position=%s%n", session, session.getExamplePosition()));
// işlem devam ediyor
session.setOnAir(true);
// ancak henüz başlamadı
session.setOperationStarted(false);
// yanıt parçası görüntüleniyor
activity.selectTab(Constants.VUE_RESPONSE);
// bekleme başlatılıyor
beginWaiting();
}
}
- 15. satır: [ispageValid] yöntemine değinmeyeceğiz. Bu yöntem, girdilerin geçerliliğini kontrol eder ve yalnızca tümü geçerliyse true değerini döndürür. Bu durumda, girdiler 2-9. satırlardaki alanları başlatmak için kullanılır;
- 17. satır: Çeşitli girdiler oturuma aktarılır:
- [spinnerExemples.getSelectedItem().toString()], kullanıcı tarafından seçilen örneğin adıdır ve [session.exampleName]'te saklanır;
- [spinnerExemples.getSelectedItemPosition() + 1], örneğe ilişkin ve parça yöneticisi tarafından kaydedilmiş olan parçanın numarasıdır. Bu numara, [session.examplePosition]'te saklanır;
- 19. satır: Web hizmetinin URL / jSON satırı, etkinliğe iletilir; etkinlik de bunu [DAO] katmanına iletir;
- 21-24. satırlar: Bir işlemin başlayacağı görülür;
- 26. satır: Yanıt sekmesi görüntülenecek. Neler olacağını anlamak için [MainActivity.selectTab] kodunu hatırlamak gerekir:
// sekme seçimi
public void selectTab(int position) {
// en fazla 2 sekme var
// başlangıçta sadece bir tane var, o da isteğin sekmesi
// istenen sekme 1 numara ise ve henüz mevcut değilse, o zaman oluşturulması gerekir
if (position == 1 && tabLayout.getTabCount() == 1) {
// 1 sekme daha eklenir
TabLayout.Tab tab = tabLayout.newTab();
tab.setText("Response");
tabLayout.addTab(tab);
}
// sekme program tarafından seçilir; bu, [onTabSelected] olayını tetikler
// bu da bu sekmeye doğru görünümü atayacaktır
tabLayout.getTabAt(position).select();
}
- başlangıçta, etkinlik yalnızca istek sekmesini (sekme no. 0) oluşturmuştu;
- 6-11. satırlar: Henüz oluşturulmamışsa, yanıt sekmesi (sekme no. 1) oluşturulur;
- 14. satır: position numaralı sekme (0 veya 1) seçilir. Bu, [onTabSelected] olayını Android uygulamasının olay döngüsü kuyruğuna yerleştirir;
[MainActivity] içindeki [onTabSelected] olay işleyicisi şöyledir:
@Override
public void onTabSelected(TabLayout.Tab tab) {
// bir sekme seçildi - parça konteyneri tarafından görüntülenen parça değiştirilir
int position = tab.getPosition();
if (position == 0) {
// istek sekmesi
showView(0);
} else {
// yanıt sekmesi - seçilen örneğe bağlıdır
showView(session.getExamplePosition());
}
}
[Response] sekmesinde ise 9. satır yürütülür. [session.getExamplePosition()] numaralı parça görüntülenecektir. Örneğin, [exemple-03] örneğinde, [session.examplePosition]'e kaydedilen numara 3'tür. Dolayısıyla 10. satır, 3 numaralı parçayı görüntüler. Etkinlik tarafından başlangıçta oluşturulan parça tablosu [RequestFragment, Exemple01Fragment, Exemple02Fragment, Exemple03Fragment,..]'tir. Dolayısıyla, görüntülenecek olan parça [Exemple03Fragment]'tir. Bu, aşağıdaki kodla gerçekleştirilir:
// [position] numaralı görünümün görüntülenmesi
private void showView(int position) {
// görüntülenmeden önce parça yenilenir
mSectionsPagerAdapter.getItem(position).onRefresh();
// istenen görünüm görüntülenir - doğrudan o görünüme geçilir (ikinci parametre false olarak ayarlanır)
// bu parametre olmadan, varsayılan olarak istenen görünüme gidilir ve ara görünümler hızlıca görüntülenir - istenmeyen davranış
mViewPager.setCurrentItem(position, false);
}
Görüldüğü gibi, parça görüntülenmeden önce (7. satır) yenilenecektir (4. satır).
9.3.7.6. [ResponseFragment] parçası
[ResponseFragment] sınıfı, sunucudan gelen yanıtları görüntüler. Kodu şöyledir:
package android.aleas.fragments;
import android.aleas.R;
import android.aleas.activity.MainActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import org.codehaus.jackson.map.ObjectMapper;
import rx.Subscription;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public abstract class ResponseFragment extends MyFragment {
// yanıtların listesi
private ListView listReponses;
private TextView infoReponses;
// düğme
private Button btnAnnuler;
// jSON eşleştiricisi
private ObjectMapper mapper;
protected ResponseFragment() {
super();
Log.d("rxjava", String.format("ResponseFragment (%s) constructor", this));
mapper = new ObjectMapper();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// etkinlik ve oturum alınır
activity = (MainActivity) getActivity();
session = activity.getSession();
Log.d("rxjava", String.format("ResponseFragment (%s) onCreateView%n", this));
// tanımından yola çıkarak parça görünümünü oluşturur XML
View view = inflater.inflate(R.layout.response, container, false);
// bileşenler
listReponses = (ListView) view.findViewById(R.id.lst_reponses);
infoReponses = (TextView) view.findViewById(R.id.txt_Reponses);
btnAnnuler = (Button) view.findViewById(R.id.btn_Annuler);
// düğme [Annuler]
btnAnnuler.setVisibility(View.INVISIBLE);
btnAnnuler.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
doAnnuler();
}
});
// sonuç
return view;
}
...
// parçanın her görüntülenmesinden önce (açık kodla) çalıştırılacak yöntem
public void onRefresh() {
...
}
}
- 21. satır: [ResponseFragment] sınıfı, [MyFragment] sınıfını genişletir;
- 23-27. satırlar: parça bileşenleri;
- satır 32-36: oluşturucu, etkinlik tarafından örnek parçalarının ilk oluşturulduğu sırada yalnızca bir kez çalıştırılır. Nitekim, tüm örnek parçaları [ResponseFragment] parçasını genişletir. Bu parçaların örneklendirilmesi sırasında, üst sınıfları olan [ResponseFragment] sınıfının oluşturucusu çağrılır;
- satır 35: bir istisna yığınının jSON dizesini görüntülemek için kullanılan, satır 30'daki jSON eşleştiricisini başlatır;
- 38-59. satırlar: [onCreateView] yöntemi, örnek parçalarının etkinlik tarafından ilk oluşturulduğu sırada yalnızca bir kez yürütülür. Burada bir Android uygulamasında sıkça görülen klasik kod bulunur;
- 52-56. satırlar: [Annuler] düğmesine tıklandığında yürütülen yöntem, [doAnnuler] yöntemidir;
- 62-64. satırlar: [onRefresh] yöntemi, [Response] sekmesi her görüntülendiğinde çalıştırılır;
Önemli yöntemlere yerleştirilen çeşitli günlükler sayesinde, uygulamanın başlatılması sırasında neler olduğunu görebiliriz:
- 1. satır: [RequestFragment] parçasının oluşturulması;
- 2-9. satırlar: Uygulamadaki 4 örneğin parçalarının oluşturulması;
- 10. satır: [RequestFragment] fragmanının başlatılması;
- satır 11-14: uygulamanın 4 örneğine ait parçaların başlatılması;
Bundan sonra, bu yöntemlere yapılan çağrılar bir daha görülmez.
[ResponseFragment.onRefresh] yöntemi şu şekildedir:
// fragmanın her görüntülenmesinden önce (açık kodla) çalıştırılacak yöntem
public void onRefresh() {
Log.d("rxjava", String.format("ResponseFragment (%s) onRefresh for %s, sessionIsOnAir=%s session.isOperationStarted=%s%n", this, activity == null ? null : activity.getSession().getExampleName(), session.isOnAir(), session.isOperationStarted()));
// çalışıyor mu?
if (session.isOnAir() && !session.isOperationStarted()) {
// istek yürütülüyor
session.setOperationStarted(true);
doExecuter();
}
}
- 5. satır: [RequestFragment] parçacığının bir istek (session.isOnAir) yapıp yapmadığına ve bu isteğin başlatılıp başlatılmadığına (isOperationStarted) bakılır. Eğer [RequestFragment] parçası bir istek göndermişse ve bu istek henüz yürütülmüyorsa, işlem başlatılır (7-8. satırlar);
- işlem başlatıldıktan sonra, bu işlem asenkron olduğu için kullanıcı iki sekme arasında geçiş yapabilir. Eğer kullanıcı tekrar [Response] sekmesine dönerse ve bir işlem devam ediyorsa, 7-8. satırlar yürütülmez;
- satırdaki [doExecuter] yöntemi, kullanıcı tarafından talep edilen işlemi yürütür:
private void doExecuter() {
Log.d("rxjava", String.format("ResponseFragment (%s) doExecuter for %s%n", this, session.getExampleName()));
// bekleme başlangıcı
beginWaiting();
// yürütme hazırlığı
subscriptions.clear();
reponses.clear();
nbInfos = 0;
// seçilen örneğin gözlemlenebilirleri oluşturuluyor ve çalıştırılıyor
createAndExecuteObservables();
}
// alt sınıflar tarafından uygulanan yöntem
protected abstract void createAndExecuteObservables();
- 10. satır: gözlemlenebilirleri oluşturur, yürütür ve gözlemler. Bunlar her örnek için farklıdır. Bu nedenle, [createAndExecuteObservables] yöntemi soyuttur (satır 14). Bu yöntem, [ResponseFragment] sınıfını genişleten [ExampleXXFragment] parçaları tarafından uygulanacaktır;
- 6. satır: abonelik listesi boşaltılır;
- 7. satır: yanıtları gösteren liste boşaltılır;
- 8. satır: alınan yanıtların sayısı sayılır;
[ExampleXXFragment] alt sınıfları, gözlemledikleri öğeleri görüntüleme görevini aşağıdaki [showAlea] yöntemine devreder:
protected void showAlea(String data) {
// ek bilgi
nbInfos++;
infoReponses.setText(String.format("Liste des réponses (%s)", nbInfos));
// 1 yanıt daha
reponses.add(0, data);
Log.d("rxjava", data);
// UI'in güncellenmesi
listReponses.setAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, reponses));
}
- 1. satır: Gözlemlenen öğenin bir dize biçiminde geldiği görülür. Bu, aslında gözlemlenen öğenin jSON dizesidir. Bu, gözlemlenen öğenin tam Java türü ne olursa olsun, onu görüntülemek için tek bir yönteme sahip olmamızı sağlar;
- 6. satır: Gözlemlenen [data] öğesi, yanıt listesinin en başına eklenir. Böylece kullanıcı, listenin başında en yeni yanıtları görür;
Bekleme, aşağıdaki [beginWaiting] ve [cancelWaiting] yöntemleriyle yönetilir:
private void beginWaiting() {
// kum saati devreye giriyor
activity.beginWaiting();
// [Annuler] düğmesi görüntüleniyor
btnAnnuler.setVisibility(View.VISIBLE);
}
protected void cancelWaiting() {
// bekleme sona erdi
activity.cancelWaiting();
// [Annuler] düğmesi gizlenir
btnAnnuler.setVisibility(View.INVISIBLE);
}
Bu yöntemler, aktivitedeki aynı isimli yöntemleri çağırır ve ardından sadece [Annuler] düğmesini gösterir veya gizler.
[Annuler] düğmesine tıklama, aşağıdaki kodla yönetilir:
protected void doAnnuler() {
// tüm abonelikler iptal edildi
for (Subscription s : subscriptions) {
if (!s.isUnsubscribed()) {
s.unsubscribe();
}
}
// bekleme sona erdi
cancelWaiting();
}
- 3-7. satırlar: tüm abonelikler tek tek iptal edilir;
9.3.8. Gözlemlenebilir örnekler
9.3.8.1. Exemple-01
[ExampleXXFragment] sınıflarının işlevi, gözlemlenebilirleri oluşturmak, yürütmek ve gözlemlemektir. Gözlemlenen değerlerin görüntülenmesi, üst sınıf olan [ResponseFragment] tarafından gerçekleştirilir.
[Example01Fragment] sınıfı şu şekildedir:
![]() |
package android.aleas.exemples;
import android.aleas.dao.AleasDaoResponse;
import android.aleas.fragments.AleasUiResponse;
import android.aleas.fragments.Request;
import android.aleas.fragments.ResponseFragment;
import android.util.Log;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import java.io.IOException;
public class Example01Fragment extends ResponseFragment {
// eşleştiriciler jSON
private ObjectMapper mapperAleasUiResponse;
// oluşturucu
public Example01Fragment() {
super();
Log.d("rxjava", "Example01Fragment constructor");
// filtreler jSON
mapperAleasUiResponse = new ObjectMapper();
}
@Override
public void createAndExecuteObservables() {
Log.d("rxjava", "Example01Fragment createAndExecuteObservables");
// rastgele sayılar isteniyor
Observable<AleasDaoResponse> observable = Observable.empty();
for (int i = 0; i < session.getNbRequests(); i++) {
// i numaralı gözlemlenebilir yapılandırma
// sunucuya yapılacak istek
Request request = session.getRequest();
request.setId(i);
// hesaplama iş parçacığı üzerinde yürütülen gözlemlenebilir
observable = observable.mergeWith(session.getActivity().getAleas(request).subscribeOn(Schedulers.io()));
}
// olay döngüsünün iş parçacığı üzerinde gözlem;
observable = observable.observeOn(AndroidSchedulers.mainThread());
// tüm bu gözlemlenebilirler çalıştırılıyor
subscriptions.add(observable.subscribe(new Action1<AleasDaoResponse>() {
@Override
public void call(AleasDaoResponse aleasDaoResponse) {
showAlea(getDataFrom(aleasDaoResponse));
}
}, new Action1<Throwable>() {
...
}, new Action0() {
...
}
private String getDataFrom(AleasDaoResponse aleasDaoResponse) {
// görüntülenecek bilgiler çıkarılır
String data;
try {
data = mapperAleasUiResponse.writeValueAsString(new AleasUiResponse(aleasDaoResponse));
} catch (IOException e) {
data = String.format("[%s,%s]", e.getClass().getName(), e.getMessage());
}
return data;
}
}
- 36. satır: oluşturulacak tek gözlemlenebilir;
- satır 37-44: 36. satırdaki gözlemlenebilir öğede birleştirilen (satır 43) çeşitli gözlemlenebilir öğelerin oluşturulması ve yapılandırılması;
- 43. satır: gözlemlenebilir, [Schedulers.io()] zamanlayıcısının bir iş parçacığında yürütülür. Sunucuya yapılan HTTP çağrısı bu iş parçacığında yürütülecektir;
- satır 46: nihai gözlemlenebilir, olay döngüsünün iş parçacığında gözlemlenir;
- satır 48-57: gözlemlenebilirlerin, dolayısıyla rastgele sayı sunucusuna yapılan isteklerin yürütülmesi. Android henüz Java 8 ve lambda ifadelerini desteklemiyor. Bu nedenle burada, RxJava'in işlevsel arayüzlerini örneklendirmek için isimsiz sınıflar kullanıyoruz;
- 49-52. satırlar: Gözlemci, gözlemlenebilir nesneden [AleasDaoResponse] türünde yeni bir öğe aldığında yürütülen eylem (bkz. paragraf 9.3.6.1);
- satır 51: üst sınıfın [showAlea] yönteminin çağrılması. Bu yöntemin bir karakter dizisi beklediği hatırlanmalıdır. Bu karakter dizisi, 59-68. satırlardaki [getDataFrom] yöntemi tarafından sağlanır;
- 63. satır: jSON dizesi, aşağıdaki [AleasUiResponse] türüne dönüştürülür:
package android.aleas.fragments;
import android.aleas.dao.AleasDaoResponse;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class AleasUiResponse {
// yanıt [DAO]
private AleasDaoResponse aleasDaoResponse;
// gözlem iş parçacığı
private String observedOn;
// gözlem saati
private String observedAt;
// yapıcılar
public AleasUiResponse() {
observedOn = Thread.currentThread().getName();
observedAt = new SimpleDateFormat("hh:mm:ss:SSS").format(Calendar.getInstance().getTime());
}
public AleasUiResponse(AleasDaoResponse aleasDaoResponse, String on, String at) {
this.aleasDaoResponse = aleasDaoResponse;
this.observedOn = on;
this.observedAt = at;
}
public AleasUiResponse(AleasDaoResponse aleasDaoResponse) {
this();
this.aleasDaoResponse = aleasDaoResponse;
}
// alıcı ve ayarlayıcılar
...
}
- [DAO] katmanının yanıtına (satır 11), iki bilgi eklenir:
- 13. satır: gözlem dizisi;
- 15. satır: gözlem saati;
Abone koduna geri dönelim:
@Override
public void createAndExecuteObservables() {
...
// tüm bu gözlemlenebilirleri çalıştırıyoruz
subscriptions.add(observable.subscribe(new Action1<AleasDaoResponse>() {
@Override
public void call(AleasDaoResponse aleasDaoResponse) {
showAlea(getDataFrom(aleasDaoResponse));
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable th) {
// istisna görüntülenir
showAlea(getMessagesFromThrowable(th));
// bir istisna alındıktan sonra, gözlemlenebilir ne onNext ne de onCompleted alır
// aboneliği manuel olarak iptal etmek zorunda kalınır
doAnnuler();
}
}, new Action0() {
@Override
public void call() {
// bekleme sona erdi
cancelWaiting();
}
}));
}
- 11-18. satırlar: gözlemcinin bir istisna aldığı durum;
- 14. satır: İstisnayı görüntülemek için yine üst sınıftaki [showAlea] yöntemi kullanılır. [getMessagesFromThrowable] yöntemi, bir istisnadan bir karakter dizisi üreten [ResponseFragment] üst sınıfına ait bir yöntemdir:
// istisna mesajları
protected String getMessagesFromThrowable(Throwable ex) {
// istisna yığınındaki hata mesajlarından bir liste oluşturuluyor
List<String> messages = new ArrayList<String>();
Throwable th = ex;
while (th != null) {
messages.add(String.format("[%s, %s]", th.getClass().getName(), th.getMessage()));
th = th.getCause();
}
try {
return mapper.writeValueAsString(messages);
} catch (IOException e) {
return e.getMessage();
}
}
- 11. satır: Hata mesajları listesindeki (4. satır) jSON karakter dizisi döndürülür;
Gözlemlenebilir nesneye abonelik koduna geri dönelim:
- 19-25. satırlar: Gözlemci, yayın sonu bildirimini aldığında yürütülen kod. Ardından bekleme işlemi iptal edilir (23. satır) ve bu da grafik arayüzü günceller;
Örnek 01'in yürütme sonucu, aşağıdakine benzer bir sonuç verir:

Listenin her bir öğesi, gözlemlenen bir değerin jSON dizesidir. jSON dizesinin alanları şunlardır:
- aleas: sunucu tarafından sağlanan rastgele sayı listesi;
- idClient: istek numarası (yanıtların karışık sırayla geldiği görülebilir);
- on: bu değeri gönderen gözlemlenebilirin yürütme iş parçacığı;
- requestAt: istemcinin isteğinin saati;
- responseAt: sunucunun yanıt saatidir;
- delay: sunucu tarafından gözlemlenen bekleme süresi;
- hata: sunucu tarafından döndürülen hata kodu (0=hata yok);
- message: sunucu tarafından gönderilen hata mesajı (null=hata yok);
- observedAt: gözlemlenen değerin gözlem zamanı;
- observedOn: gözlemlenen değerin gözlem iş parçacığı;
9.3.8.2. Exemple-02
[Example02Fragment] sınıfı şu şekildedir:
package android.aleas.exemples;
import android.aleas.dao.AleasDaoResponse;
import android.aleas.fragments.AleasUiResponse;
import android.aleas.fragments.Request;
import android.aleas.fragments.ResponseFragment;
import android.util.Log;
import org.codehaus.jackson.map.ObjectMapper;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import java.io.IOException;
public class Example02Fragment extends ResponseFragment {
// jSON eşleştiricileri
private ObjectMapper mapperAleasUiResponse;
// oluşturucu
public Example02Fragment() {
super();
Log.d("rxjava", "Example02Fragment constructor");
// filtre jSON
mapperAleasUiResponse = new ObjectMapper();
}
public void createAndExecuteObservables() {
Log.d("rxjava", "Example02Fragment createAndExecuteObservables");
// rastgele sayılar isteniyor
Observable<AleasDaoResponse> observable = Observable.empty();
for (int i = 0; i < session.getNbRequests(); i++) {
// isteğin hazırlanması
Request request = session.getRequest();
request.setId(i);
// sadece müşteri numarası çift olan gözlemlenebilirler tutulur
observable = observable
.mergeWith(session.getActivity().getAleas(request).filter(new Func1<AleasDaoResponse, Boolean>() {
@Override
public Boolean call(AleasDaoResponse aleasDaoResponse) {
return aleasDaoResponse.getClientState().getIdClient() % 2 == 0;
}
})
// G/Ç iş parçacığında yürütme
.subscribeOn(Schedulers.io()));
}
// olay döngüsü iş parçacığında gözlem
observable = observable.observeOn(AndroidSchedulers.mainThread());
// bu gözlemlenebilirler çalıştırılır
subscriptions.add(observable.subscribe(new Action1<AleasDaoResponse>() {
@Override
public void call(AleasDaoResponse aleasDaoResponse) {
showAlea(getDataFrom(aleasDaoResponse));
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable th) {
showAlea(getMessagesFromThrowable(th));
doAnnuler();
}
}, new Action0() {
@Override
public void call() {
// bekleme sona erdi
cancelWaiting();
}
}));
}
private String getDataFrom(AleasDaoResponse aleasDaoResponse) {
// görüntülenecek bilgi çıkarılır
String data;
try {
data = mapperAleasUiResponse.writeValueAsString(new AleasUiResponse(aleasDaoResponse));
} catch (IOException e) {
data = String.format("[%s,%s]", e.getClass().getName(), e.getMessage());
}
return data;
}
}
Bu örnek, bir önceki örneğe (satır 38) benzerdir. Ancak, bir önceki örnekte elde edilen gözlemlenebilir değerlerden, [filter] yöntemi (satır 41) sayesinde yalnızca müşteri numarası çift olan gözlemlenebilir değerler tutulur (satır 42-46).
Elde edilen sonuçlar şöyledir (10 istek için):

9.3.8.3. Exemple-03
[Example03Fragment] sınıfı şu şekildedir:
package android.aleas.exemples;
import android.aleas.dao.AleasDaoResponse;
import android.aleas.fragments.Request;
import android.aleas.fragments.ResponseFragment;
import android.util.Log;
import org.codehaus.jackson.map.ObjectMapper;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import java.io.IOException;
import java.util.List;
public class Example03Fragment extends ResponseFragment {
// jSON eşleştiricileri
private ObjectMapper mapper;
// oluşturucu
public Example03Fragment() {
super();
Log.d("rxjava", "Example03Fragment constructor");
// filtre jSON
mapper = new ObjectMapper();
}
public void createAndExecuteObservables() {
Log.d("rxjava", "Example03Fragment createAndExecuteObservables");
// rastgele sayılar talep edilir
Observable<List<Integer>> observable = Observable.empty();
for (int i = 0; i < session.getNbRequests(); i++) {
// isteğin hazırlanması
Request request = session.getRequest();
request.setId(i);
// gözlemlenebilir yapılandırma
observable = observable.mergeWith(session.getActivity().getAleas(request).filter(new Func1<AleasDaoResponse, Boolean>() {
@Override
public Boolean call(AleasDaoResponse aleasDaoResponse) {
return aleasDaoResponse.getClientState().getIdClient() % 2 == 0;
}
}).map(new Func1<AleasDaoResponse, List<Integer>>() {
@Override
public List<Integer> call(AleasDaoResponse aleasDaoResponse) {
return aleasDaoResponse.getAleas();
}
})
// G/Ç iş parçacığında yürütme
.subscribeOn(Schedulers.io()));
}
// olay döngüsü iş parçacığında gözlem
observable = observable.observeOn(AndroidSchedulers.mainThread());
// bu gözlemlenebilirleri çalıştırıyoruz
subscriptions.add(observable
.subscribe(new Action1<List<Integer>>() {
@Override
public void call(List<Integer> aleas) {
showAlea(getDataFrom(aleas));
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable th) {
showAlea(getMessagesFromThrowable(th));
doAnnuler();
}
},
new Action0() {
@Override
public void call() {
// bekleme sonu
cancelWaiting();
}
}
));
}
private String getDataFrom(List<Integer> aleas) {
// görüntülenecek bilgi çıkarılır
String data;
try {
data = mapper.writeValueAsString(aleas);
} catch (IOException e) {
data = String.format("[%s,%s]", e.getClass().getName(), e.getMessage());
}
return data;
}
}
Bu örnek, Örnek-02'ye benzerdir:
- 40. satır: Örnek-02'dekiyle aynı gözlemlenebilirler tanımlanır;
- 45. satır: önceki gözlemlenebilirler tarafından üretilen değerlerin her biri, [map] yöntemi ile, sunucu tarafından üretilen rastgele sayıların listesi olan List<Integer> türüne dönüştürülür;
- 58. satır: Artık gözlemlenen değer List<Integer> türündedir;
10 istek için elde edilen sonuç şöyledir:

9.3.8.4. Exemple-04
[Example04Fragment] sınıfı şu şekildedir:
package android.aleas.exemples;
import android.aleas.dao.AleasDaoResponse;
import android.aleas.fragments.Request;
import android.aleas.fragments.ResponseFragment;
import android.util.Log;
import org.codehaus.jackson.map.ObjectMapper;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
public class Example04Fragment extends ResponseFragment {
// jSON eşleştiricileri
private ObjectMapper mapper;
// oluşturucu
public Example04Fragment() {
super();
Log.d("rxjava", "Example04Fragment constructor");
// filtre jSON
mapper = new ObjectMapper();
}
public void createAndExecuteObservables() {
Log.d("rxjava", "Example03Fragment createAndExecuteObservables");
// rastgele sayılar talep edilir
Observable<Integer> observable = Observable.empty();
for (int i = 0; i < session.getNbRequests(); i++) {
// isteğin hazırlanması
Request request = session.getRequest();
request.setId(i);
// gözlemlenebilirlerin yapılandırılması
observable = observable.mergeWith(session.getActivity().getAleas(request).filter(new Func1<AleasDaoResponse, Boolean>() {
@Override
public Boolean call(AleasDaoResponse aleasDaoResponse) {
return aleasDaoResponse.getClientState().getIdClient() % 2 == 0;
}
}).flatMap(new Func1<AleasDaoResponse, Observable<Integer>>() {
@Override
public Observable<Integer> call(AleasDaoResponse aleasDaoResponse) {
return Observable.from(aleasDaoResponse.getAleas());
}
})
// G/Ç iş parçacığında yürütme
.subscribeOn(Schedulers.io()));
}
// olay döngüsünün iş parçacığında gözlem
observable = observable.observeOn(AndroidSchedulers.mainThread());
// bu gözlemlenebilirler çalıştırılıyor
subscriptions.add(observable
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer alea) {
showAlea(String.valueOf(alea));
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable th) {
showAlea(getMessagesFromThrowable(th));
doAnnuler();
}
},
new Action0() {
@Override
public void call() {
// bekleme sonu
cancelWaiting();
}
}
));
}
}
Bu örnek, Örnek-03'e benzerdir; ancak 42. satırda [map] yöntemi yerine [flatMap] yöntemi kullanılmaktadır.
- 55. satır: Artık gözlemlenen değerin türünün Integer olduğu dikkatinizi çekecektir;
10 sorgu için şu sonuçlar elde edilir:

Bu sefer, sorgu sayısından daha fazla gözlem değeri elde edilmiştir.
9.3.8.5. Exemple-05
Şimdi, uygulamaya yeni bir gözlemlenebilir örnek eklemek için izlenecek prosedürü açıklayacağız.
7.6.4. paragrafındaki [Exemple22h] örneğini yeniden oluşturmak istediğimizi varsayalım:
package dvp.rxjava.observables.exemples;
import dvp.rxjava.observables.utils.Process;
import dvp.rxjava.observables.utils.ProcessUtils;
import rx.Observable;
import rx.observables.GroupedObservable;
public class Exemple22h {
public static void main(String[] args) throws InterruptedException {
// işlem
Observable<GroupedObservable<Boolean, Integer>> obs = Observable.range(1, 10).groupBy(i -> i % 2 == 0);
Process<Integer> process = new Process<>("process", obs.concatMap(g -> g.asObservable()));
// abonelikler
ProcessUtils.subscribe(1, process);
}
}
- [Observable.range(1, 10)] gözlemlenebilirinin değerleri önce [groupBy] yöntemi (11. satır) ile çift ve tek sayılar olarak gruplandırılır, ardından [concatMap] yöntemi (12. satır) ile tek bir gözlemlenebilirde birleştirilir;
adım 1
[exemples.xml] dosyasında yeni bir örnek oluşturulur:
![]() |
<!-- örnekler -->
<resources>
<string-array name="exemples">
<item>Exemple-01</item>
<item>Exemple-02</item>
<item>Exemple-03</item>
<item>Exemple-04</item>
<item>Exemple-05</item>
</string-array>
</resources>
Yukarıda, 8. satır eklenmiştir. Örneğe verilen isim herhangi bir isim olabilir.
2. adım
[Example04Fragment] sınıfını [Example05Fragment] olarak kopyalıyoruz. Burada sınıf adı zorunludur.
3. adım
[Example05Fragment] kodunu şu şekilde değiştirin:
package android.aleas.exemples;
import android.aleas.dao.AleasDaoResponse;
import android.aleas.fragments.Request;
import android.aleas.fragments.ResponseFragment;
import android.util.Log;
import org.codehaus.jackson.map.ObjectMapper;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.observables.GroupedObservable;
import rx.schedulers.Schedulers;
public class Example05Fragment extends ResponseFragment {
// eşleştiriciler jSON
private ObjectMapper mapper;
// oluşturucu
public Example05Fragment() {
super();
Log.d("rxjava", "Example05Fragment constructor");
// filtre jSON
mapper = new ObjectMapper();
}
public void createAndExecuteObservables() {
Log.d("rxjava", "Example05Fragment createAndExecuteObservables");
// işlevsel arayüzlerin örnekleri
// filtre
Func1<AleasDaoResponse, Boolean> filter = new Func1<AleasDaoResponse, Boolean>() {
@Override
public Boolean call(AleasDaoResponse aleasDaoResponse) {
return aleasDaoResponse.getClientState().getIdClient() % 2 == 0;
}
};
// flatMap
Func1<AleasDaoResponse, Observable<Integer>> flatMap = new Func1<AleasDaoResponse, Observable<Integer>>() {
@Override
public Observable<Integer> call(AleasDaoResponse aleasDaoResponse) {
return Observable.from(aleasDaoResponse.getAleas());
}
};
// groupBy
Func1<Integer, Boolean> groupBy = new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer integer) {
return integer % 2 == 0;
}
};
// concatMap
Func1<GroupedObservable<Boolean, Integer>, Observable<Integer>> concatMap = new Func1<GroupedObservable<Boolean, Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> call(GroupedObservable<Boolean, Integer> integerIntegerGroupedObservable) {
return integerIntegerGroupedObservable.asObservable();
}
};
// rastgele sayılar isteniyor
Observable<Integer> observable = Observable.empty();
for (int i = 0; i < session.getNbRequests(); i++) {
// isteğin hazırlanması
Request request = session.getRequest();
request.setId(i);
// gözlemlenebilir yapılandırma
observable = observable.mergeWith(session.getActivity().getAleas(request).filter(filter).flatMap(flatMap))
.groupBy(groupBy).concatMap(concatMap)
// G/Ç iş parçacığında yürütme
.subscribeOn(Schedulers.io());
}
// olay döngüsünün iş parçacığında gözlem
observable = observable.observeOn(AndroidSchedulers.mainThread());
// bu gözlemlenebilirler çalıştırılıyor
subscriptions.add(observable
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer alea) {
showAlea(String.valueOf(alea));
}
},
new Action1<Throwable>() {
@Override
public void call(Throwable th) {
showAlea(getMessagesFromThrowable(th));
doAnnuler();
}
},
new Action0() {
@Override
public void call() {
// bekleme sona erdi
cancelWaiting();
}
}
));
}
}
- 67. satır: Örnek 04'teki gözlemlenebilir değeri temsil eder: bir tamsayı akışı;
- 68. satır: Bu tamsayı akışını, tanımlayacağımız bir Boole kriterine göre gruplandırıyoruz. Böylece, GroupedObservable<Boolean, Integer> türünde elemanlar üreten Observable<GroupedObservable<Boolean, Integer>> türünde bir gözlemlenebilir elde edeceğiz;
- 68. satır: [concatMap] yöntemi, GroupedObservable<Boolean, Integer> türündeki öğelerden Integer türünde öğeler üretecektir;
- satır 32-59: 67-69. satırlardaki gözlemlenebilirin oluşturulmasını daha okunaklı hale getirmek için, çeşitli [filter, flatMap, groupBy, concatMap] operatörlerinin ihtiyaç duyduğu işlevsel arayüz örneklerini ayırdık;
- satır 47-52: [groupBy] yöntemi, Func1<T,K> türünde bir parametre bekler; burada T, gruplandırılan öğelerin türüdür ve K, gruplandırma kriterinin türüdür. T öğesinden yola çıkarak, Func1<T,K> örneği, öğenin K gruplama anahtarını üretmekle görevlidir;
- satır 48-51: Integer türündeki elemanlar pariteye göre gruplandırılacaktır. Func1<Integer,Boolean> örneği, öğenin hangi gruba yerleştirileceğine bağlı olarak true veya false anahtarını üretir. Çıktıda iki grup elde edilir: true anahtarına sahip çift elemanlar grubu ve false anahtarına sahip tek elemanlar grubu;
- 53-59. satırlar: [concatMap] yöntemi, Func1<T,Observable<R>> türünde bir parametre bekler ve R türünde elemanlardan oluşan bir gözlemlenebilir üretir. Burada T türü, [groupBy] operatörü tarafından üretilen tür olacaktır; bu durumda GroupedObservable<Boolean, Integer> türüdür;
- 57. satır: [GroupedObservable<Boolean, Integer>] türündeki öğeden bir Observable<Integer> türü üretilir. [groupBy] operatörü iki grup oluşturduğundan, [concatMap] operatörü de [Observable<Integer>] türünde iki gözlemlenebilir oluşturacaktır. [flatMap] gibi, bunları tek bir gözlemlenebilir nesneye düzleştirecektir. Ancak [flatMap]'ten farklı olarak, düzleştirilmiş gözlemlenebilir nesnelerin elemanlarını karıştırmaz. Dolayısıyla, iki ayrı grup gözlemlenmelidir: çift rastgele sayılar ve diğer tek sayılar.
4. adım
Uygulamayı çalıştırıyoruz:

ve şu sonuçları elde ederiz:

- [1]'te çift rastgele sayılar, [2]'te ise tek rastgele sayılar;
9.3.8.6. Devam etmek için
Okuyucu şimdi kendi örneklerini oluşturmaya ve rastgele sayı sunucusuna yapılan istekleri yapılandıran formdaki girişler için çeşitli değerleri denemeye davet ediliyor.
9.3.9. Sonuç
Android ortamında aşağıdaki mimariyi oluşturduk:
Android istemcisi:
![]() |
[DAO] katmanı, Android tablet tarafından görüntülenen rastgele sayıları üreten sunucuyla iletişim kurar. Bu sunucu, aşağıdaki gibi iki katmanlı bir mimariye sahiptir:
![]() |
[DAO] katmanı, rastgele sayı sunucusuna n adet HTTP isteği gönderdi ve [swing] katmanı, bunları görüntülemek için bu isteklerin sonuçlarını asenkron olarak bekledi. Bu n adet HTTP isteği, aynı tür yanıtları veren aynı sunucuya yapılmıştı. Bu sayede yanıtları (mergeWith) tek bir gözlemlenebilir öğede birleştirebildik.
Gerçekte ise Android uygulamaları farklı sunuculara bağlanır ve bu sunuculardan gelen yanıtları muhtemelen birleştirmeyiz. Bu sunuculara yapılan HTTP istekleri birbirinden bağımsız olarak yönetilecek ve sonuçları ayrı yöntemlerle izlenecektir.

















































