Friday, April 30, 2010

Internet Isletim Sistemi

Ilginc gelismeler oluyor. HP, Palm sirketini satin aldi; bu alim servis tarafinda guclu olan bir sirketin kullaniciya (consumer) donuk bir telefon sirketini satin almasi anlamina geliyor ayni zamanda. Bu son haber, "etrafi citle cevrilmis bahce (walled garden)" kavramina dogru bir gidisati da ispatliyor sanki. Google, Android musteri (client) sistemlerin kendi App Engine (bulut) servis tarafi sistemlerinden olusan bir "bahceyi" tesvik ederken, Apple iPhone/iPad ve su anda yatirimini yaptigi kendi servis tarafi hizmetleri ile birlesmesini arzulayacak gibi. HP / Palm bu yonde bir gelisme olarak algilandi. O'Reilly yayinciligin duayeni Tim O'Reilly'ye gore, isler bir Internet Isletim Sistemi'ne dogru gidiyor.

http://oreil.ly/cyFhMZ
http://oreil.ly/amhozs

Bunlara paralel olarak son kullaniciya donuk program gelistirenler "hangi tarafa kayalim?" sorusu ile karsi karsiya. Ya da kayarsak, "o platformu nasil kodlayalim?"

Bizim durusumuz telefon uzerinde gitgide HTML/CSS/Javascript bazli teknolojilere dogru. Servis tarafinda Google servislerine yakin duruyoruz, cunku App Engine, ozellikle Python uzerinden, esnek servisler sunuyor. Telefon tarafinda Objective-C Cocoa islerine girmeyecegimizi iyi biliyoruz, ve Apple'in App Store'unun bazen 5 haftayi bulan kontrol sistemi bir acik yazilimci icin uygun degil. Kurumsal kodlayicilar icin de Android / GAE cekici olacaktir.

Bulut'a dogru gidisi cok iyi anlatan su yazilar guzel.

http://bit.ly/aTkdpM
http://bit.ly/bxOePm

Kapismanin alanini tarif eden "mobil platform artik yeni masaustu" sozu uygun herhalde. Bir zamanlar masaustu icin Windows, Mac arasindaki savasin degisik bir versiyonu oynaniyor, ve su anda masaustu (ve dizustu) hizla kar elde edilemeyecek, ucuzluk (commodity) klasmanina dogru kayiyor, ve savas kullanicilari kendi ekosistemine kapatarak, o ekosistem icindeki para akisi uzerinden kar etmeye calismak gibi gozukuyor. Apple iTunes ve App Store ile insanlari kolayca sarki, uygulama satin almaya alistirdi. Apple 100+ milyon insanin kredi kart bilgisini elinde tutuyor. Bu servisi fiziksel seyleri satmaya yoneltebilecegi konusulmakta... Google reklam, arama servislerini Android uzerinden diger servislere genisletebilme potansiyelini barindiriyor. HP, son haberlere gore, eldeki varini yogunu cok buyuk risklere girerek telefon / bulut alanina yatirmaya karar verdi. Microsoft surpriz olabilir, ama buyuk bir ihtimalle pek anlamadigi bu alanda var olamayacak. Ilginc gelismeler. Su yazi da faydali:

http://bit.ly/9ZJ8Fq

Wednesday, April 28, 2010

Tor

Tor programi, o programi isleten digerlerinin bilgisayarlarini Internet uzerinden birlestirererek "guvenli" bir alternatif iletisim agi olusturur. Isyerinizde belli bir site blok edilmis ise, Tor ile bir "sanal aga" baglanip disari "cikarak" o istediginiz siteye baglanabilirsiniz. Tabii sizin diger bilgisayarlari kullandigi gibi, baskalari da sizin bilgisayarinizi diger noktalara erismek icin kullaniyor olabilir - performans acisindan bunu da belirtelim. Yani surekli Tor isletmek bilgisayar performansinizda negatif bir etkiye sebep olabilir.

Ubuntu uzerinde Tor kurmak icin

sudo apt-get install tor
sudo apt-get install privoxy

komutlari yeterli.

Not: Ubuntu 9.10 Tor kurulumu icin surayi takip etmek lazim cunku bu durumda normal apt-get islemiyor: /etc/apt/sources.list dosyasi icinde
deb     http://deb.torproject.org/torproject.org karmic main
satirlari en sona eklenir ve
gpg --keyserver keys.gnupg.net --recv 886DDD89
gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | sudo apt-key add -
isletilir, sonra
apt-get update
apt-get install tor tor-geoipdb
Privoxy icin sudo nano /etc/privoxy/config ile dosyayi edit edin, ve icerik olarak

forward-socks4a / localhost:9050 .

ekleyin. Baslatmak icin

sudo /etc/init.d/tor start
sudo /etc/init.d/privoxy start

Sonra Firefox 3.0 uzerinde TorButton adli eklentiyi kurun.

YouTube video'larini ve flash icerik gorebilmek icin: Bu eklenti kurulduktan sonra Firefox sag alt kosede Tor statusunu gosteren bir isaret cikacak. Bu isaret uzerinde mouse sag dugme click yaparak "preferences" secenegine girin. Security Preferences tab'inden "disable plugins during Tor usage" secenegini de-aktive edin.

Simdi mouse sol click ile Tor Disabled yazisi uzerine tiklayin, bu yazi "Tor Enabled" gibi yesil bir yaziya donusecek. Bu kadar. Artik istediginiz siteye baglanabilirsiniz.

Kaynaklar

https://help.ubuntu.com/community/Tor?action=show&redirect=TOR

http://support.mozilla.com/tiki-view_forum_thread.php?locale=fi&comments_parentId=82766&forumId=1

Friday, April 16, 2010

HTML -> Ajax -> AppEngine

Akilli telefonlar icin mobil kod gelistirilmesinde bir alternatif daha sekillenmeye basliyor. iPhone dunyasinda AppStore uygulama deposuna giren programlarin bazen cok uzun suren kabul surecinden gecmesi, programcilari alternatif yollar aramaya itti. Diger yandan ayni kodla birden fazla marka telefon uzerinde program isletebilmek ozelligi aranir oldu, ve isletilen programin surum yapilir yapilmaz devreye sokulabilmesi istegi, aynen masaustu ortaminda oldugu gibi tekrar Web'den servis edilen HTML secenegini gundeme getirdi. Bu sekilde, hicbir kabul surecinden gecmeden, bir URL uzerinden uygulama HTML olarak servis edilebilmis olacakti. Ayrica HTML'in bir sonraki surumu HTML5 mobil icin faydali ozellikler sunuyor; lokasyon verisini saglayabilme, Net'e baglantisiz (offline) calisabilme, tarayici uzerinde lokal veri depolayabilme gibi servisleri var. Hem Android, hem iPhone baglaminda, HTML secenegiyle gorsel kodlama icin yeni bir dil (API) ogrenmeye gerek kalmayacak, piyasada yaygin bir HTML / CSS / Javascript tecrubesi mevcut.

Bu durum telefon uzerinde Java kodlamasi acisindan biraz kotu haber olabilir: Su anda Android dahil olmak uzere mobil kodlama icin HTML5 + Servis Kodlarinin Java icermesi gerekmiyor. Illa Android Market'ten indirilebilecek bir uygulama istense dahi, bu tek class'lik bir Java kodu olabilir; program baslar baslamaz WebView.loadUrl cagrisini yapar ve aradan cekilir.

Biz de bu secenegi takip edecegiz; teknoloji yelpazesindeki son durum, telefon uzerinde HTML5, CSS / Javascript kodlarinin Ajax uzerinden Google App Engine Python servis kodlarina baglanip bilgi alip verdigi bir ortam olacak.

Basit bir ornek ile baslayalim: Bir test.html sayfasi, yuklenir yuklenmez bir GAE servisine baglanip, HTTP GET uzerinden bilgi alacak, ve bunu Javascript uzerinden Document objesiyle HTML icine yazacak. Javascript ile Ajax cagrisi yapmanin en basit yolu bir Javascript kutuphanesi kullanmak. Prototype kutuphanesi ile bu is cok basit. prototype.js dosyasini indirip /files/ altina koyuyoruz.
<html>
<head>
</head>
<body>
<script type="text/javascript" src="/files/prototype.js"></script>
<script type="text/javascript">
new Ajax.Request( 'http://localhost:8080/test', {
method: 'get',
parameters: { 'test': 'value1'},
onSuccess: function(response){
document.getElementById("test").innerHTML = response.responseText
},
onFailure: function(){
alert('ERROR');
}
});
</script>
<h3>
<div id="test">
</div>
</h3>

</body>

</html>
Bu servisin baglandigi Python kodlarina gelelim:
# main.py
#
import cgi
import logging
import wsgiref.handlers
from google.appengine.ext import webapp
from testpage import *

logging.getLogger().setLevel(logging.DEBUG)

application = webapp.WSGIApplication([
('/test', Test),
], debug=True)

def main():
wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
main()

# testpage.py
#
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

class Test(webapp.RequestHandler):

def get(self):
tt = self.request.get('test')
self.response.out.write(str(tt)+",")
self.response.out.write("item1,")
self.response.out.write("item2,")
self.response.out.write("item3")
Uygulamayi ayarlamak icin app.yaml dosyasi soyle olacak:
application: testapp
version: 1
runtime: python
api_version: 1

handlers:
- url: /files
static_dir: files
- url: .*
script: main.py
Simdi GAE gelistirme servisini dev_appserver.py ile baslatalim, ve http://localhost:8080/files/test.html adresini ziyaret edelim. HTML icinde Ajax ile GET cagrisi yaparken URL uzerinden 'test' adli parametreyle servise 'value1' degeri gonderdik, bu deger ve onun yaninda diger bazi degerler geri gonderilecek. Tum bu degerlerin ekranda basildigini gorecegiz; Javascript bunu bos biraktigimiz 'div' icinde innerHTML'e degerleri gecerek yapacak.

Not: HTML icinde gorulen http://localhost:8080/test referansi anlatim kolayligi icin kullanildi, aslinda tum URL yerine sadece /test kullanmak ta yeterli. Hatta gelistirme, sonuc ortami arasinda rahat gidip gelebilmek icin boyle yapmak daha mantikli.

Saturday, April 10, 2010

Android Üzerinde Veri Tabani Kullanımı - sqllite

Mobil uygulamalarımızda hafızaya sığmayacak kadar büyük, anahtar bazlı hızlı erişim, çetrefil sorgulama gerektiren veri depolaması için ilişkisel bir taban kullanmak gerekebilir. Her Android sürümüne dahil edilen Sqlite veri tabanı bu ihtiyacı rahatça karşılar. Sqlite ufak boyutlu tabanlar kategorisinde oldukça popüler bir ürün. Firefox, Skype, Python, Mac OS-X kullanıyorsanız, zaten Sqlite kullanıyorsunuz demektir [1]; bu ürünlerin hepsinin içinde Sqlite var, onu kendi iç veri yapılarını depolamak, ona hızla erişmek için kullanıyorlar.

Başlamadan önce önemli bir nokta: Sqlite'ın Android içindeki kullanımı JDBC üzerinden değil. Bu erisim mobil ortam içinde JDBC ile yapılabilir, fakat biz bunu tavsiye etmeyeceğiz; tabana erişimin Google'ın kendi API'ı üzerinden yapılmasını tavsiye edeceğiz. Bu Google tarafından açılmış bir "yol", ve bu yolun güç kullanımı (power consumption), performans gibi konular açısından en optimal yol olması gerekiyor. Gücü, işlemci hızı sınırlı bir platformda kodlama yaptığımızı hatırlarsak, o zaman bütün çözümler buna göre şekillenmeli. Test etme bölümüne gelince ve geliştirme ortamımız içinden Android DB API çağrılarını taklit etmek gerekli olunca, bunun arka plan kodlarını JDBC ile kodlayacağız, bunlar nasıl olsa dışarıda işleyen kodlar.

Dosyalar ve Erişim

Android geliştirme ortamından, emulatöründen blogumuzdaki su yazıda [2] bahsetmiştik. Sqlite veri tabanı kendi dosyalarını telefon üzerindeki mevcut dosya sistemine yazıyor. Bu dosyalara emulatörü başlattıktan sonra [ANDROID_SDK]/tools altından "adb shell" komutunu vererek göz atabiliriz [3]. IDE makinamıza tek bir telefon bağlı ise (emulatör de bir telefon olarak addediliyor) o zaman "adb shell" bizi direk emulatöre götürecek. Bu ortam aynen bir Unix shell ortamıdır, dosyaları, dizin yapısı var. Sqlite dosyaları /data/data altında bulacağız; "cd /data/data/[UYGULAMAMIZIN PAKETI]/databases altında uygulamamızın veri tabanını görebiliriz. Bu tabana daha yakından göz atmak için ise telefon shell'i üzerinde "sqlite3 /data/data/.../[TABAN ISMI] komutu yeterli. Bu ikinci ortam herhangi bir SQL shell'ine benziyor. SELECT, INSERT gibi sql komutları burada işletilebiliyor.

Eğer veriyi telefondan geliştirme makınamıza çıkartmak istiyorsak, bu makina shell'i üzerinden "adb pull" komutu kullanılabilir, diğer yöne gitmek için "adb push" gerekiyor.

Veri Tabanı Yaratmak - Genel Kullanım

Bir tabanının şemasını yaratmak ve kullanmaya başlamak için yöntemlerden biri Android uygulama kodları içinde SQLiteOpenHelper class'ından miras alan bir yardımcı class yaratmak ve bu objeyi bir Activity içinden işleme sokmak. Bu noktadan sonra helper objesi bizim veri tabanına olan bağlantımızdır, bu referans üzerinden istediğimiz DB çağrılarını yapabiliriz.

public class DataSQLHelper extends SQLiteOpenHelper {

 private static final String DATABASE_NAME = "[DB ISMI]";
 private static final int DATABASE_VERSION = [DB VERSIYON];
 ..
 public DataSQLHelper(Context context) {
   super(context, DATABASE_NAME, null, DATABASE_VERSION);
 }
 @Override
 public void onCreate(SQLiteDatabase db) {
   String sql = "create table ... ";
   db.execSQL(sql);
   ..
 }
 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   if (oldVersion >= newVersion)
     return;
   String sql = null;
   if (oldVersion == 1)
     sql = "alter table ...";
   if (sql != null)
     db.execSQL(sql);
 }
}

public class Demo extends Activity {

 DataSQLHelper db;

 @Override
 public void onCreate(Bundle savedInstanceState) {
   db = new DataSQLHelper(this);
    ..
   }

 @Override
 public void onDestroy() {
   super.onDestroy();
   db.close();
 }
 ..
}

DataSQLHelper.onCreate metodunda CREATE TABLE komutları kodlanacak. Bu komutlar Android tarafından eğer veri tabanı mevcut değilse, işletilir. Activity DataSQLHelper objesini yarattığı zaman, o obje üzerinde tanımlanan int DATABASE_VERSION sayısını bir kenara kaydediyor. Eğer bu rakamı sonraki sürümlerde kod içinde değiştirirseniz, Android tarafından çağrılacak onUpgrade() altında koyacağınız ALTER TABLE komutları devreye sokulabiliyor, böylece database şemanızı bir versiyondan sonrakine otomatik olarak geçirtebilirsiniz. onUprade metoduna (mevcut) eski versiyon ve en son versiyon geçilecektir (Android altyapısı tarafından), ve bu rakamlar uzerinde if..else irdelemeleri kullanarak o geçişe uygun şema değişimi yapmak mümkün olacaktır. Eğer '1' versiyonundan '2' versiyonuna geçiyorsam, şu ALTER komutlarını işlet, yoksa şunları işlet, vs. şeklinde.

Üstteki örnek kodlar [4] her işletildiğinde, otomatik olarak yaratılacak tabloya bir satır veri yazılacak ve aynı anda ekranda önceden yazılmış veriler ekranda gösterilecek. Versiyon değişimini test etmek için versiyonu 1 rakamindan 2'ye geçirelim, ve programı derleyip tekrar emulatöre gonderelim. Mevcut tabloya yeni bir kolon eklendiğini göreceğiz.

Sqlite 3 icin geçerli veri tipleri şurada [5] bulunabilir.

API

Veri tabanina erişimin Google'ın kendi arayüzleri üzerinden olduğunu söylemiştik. Arayüzlerden en çok kullanılacak olanlar SQLiteDatabase ve Cursor objeleri olacak. Bir INSERT, UPDATE komutu işletmek için SQLiteDatabase üzerindeki execSQL(..) çağrısı kullanilabilir, bu çağrıya String olarak bir DDL komutu, yani INSERT, UPDATE gibi arka plandaki Sqlite tabanı için anlamlı komutlar verilebilir. Ayrıca execSQL'e verilen SQL komutu içinde sabit veri gömmek yerine aynen JDBC'de oldugu gibi değer olarak soru işareti '?' karakteri kullanılabiliyor, ve bu soru işaretlerinin içinin nasıl doldurulacağı execSQL'a geçilebilecek ikinci bir Object[] parametresi ile tanımlanabiliyor. Soru işaretlerinin sırası, değerlerin Object[] içindeki yerlerine birebir uymalı.

Veri okuma amaçlı olarak rawQuery çağrısı yapılabilir, aynı şekilde SQL komutu ve parametreleri (gerekiyorsa, yoksa null) geçilerek, ve SELECT komutu içeren bu cağrıdan, okuma amaçlı olarak bir Cursor objesi geri döndürülür. Cursor,

while (cursor.isLast()) {
cursor.moveToNext();
String val1 = cursor.getString(0);
int val2 = cursor.getInt(1);
 ..
}

şeklinde gezilebilir. getString, getInt çağrılarına geçilen sıfır temelli indeks değeri, SELECT komutunda listelenen kolon isimlerinin sırasına tekabul ediyor olmalı. Daha fazla detay icin [6] dökumanına danışılabilir.

Veri Tabanı Yaratmak - Statik ve Büyük Çapta Veri

Biraz önceki kullanım, ufak çapta veri tanımlaması, depolaması icin uygun. Fakat çok büyük çapta veriyi, uygulama başlamadan hızlı bir şekilde hazır etmek istiyorsak, o zaman bir Sqlite tabanını olduğu gibi telefona göndermenin yollarını aramamız lazim. Muhakkak helper onCreate() içinde DDL komutlarını uyguladıktan sonra, Internet'teki, ya da res/raw dizinindeki [7] APK içinde paketlenmiş bir düz dosyayı açarak, satır satır okuyup tabana INSERT ile yazabiliriz. Fakat bu tür işlemler kapasitesi sınırlı mobil ortamda çok uzun zaman alacaktır. Tipik kullanıcı uygulamayı ilk başlattığında saniyeler sonra onun hazır olmasını bekler, dakikalar sürecek bir hazırlık evresi uygun olmaz.

Sqlite için database bir dosyadan ibarettir, ve bu dosyayı gerekli yere (/data/data altındaki dizine yani) kopyalamak o tabanı hazır hale getirmek demektir, bu basit bir dosya kopyalama operasyonu olacaktır. Bu çözümle ilerlersek, iki önemli nokta ortaya çıkar: 1) Res/raw altına koyup telefona gönderebileceğimiz dosya 1.2 MB uzerinde olamıyor 2) sürekli db dosyası kopyalamamak için (işlem her ne kadar hızlı olsa da) üstteki versiyon yöntemine benzer hızlı bir kontrol yöntemi gerekli.
O zaman: Sqlite veri tabanını önceden (telefon dısında) pişirmek için geliştirme ortamımızda bir main() içinden tetikleyerek (bunun nasıl olacağını test etme bölümünde göreceğiz -hatta burada pür JDBC bile kullanılabilir-) yaratabildiğimizi farzedelim. Bu dosyayı APK içinde göndermenin tek you onu res/raw altına koymaktır. Eğer dosya çok büyük ise, onu Unix split ile parçalara bölüp, kopyalama işlemi tarafından onu tekrar birleştirmemiz gerekli. Bölme komutu:

split [DOSYA] -b 1M [yenidb-]

Boylece [DOSYA] 1 megabaytlık parçalara bölünecek ve yenidb-a, yenidb-b, .. gibi yeni dosyalar ortaya çıkacak. Bu dosyaları geliştirme dizinimizde res/raw altına koyalım. Java kod bağlamında DB'yi hazırlamak için yapılacak ilk çağrı önce 1) /data/data/[UYGULAMA]/databases altında belli bir isimde ve belli bir büyüklükteki bir dosyanın olup olmadığını kontrol edecek, 2) yoksa, res/raw altındaki parçaları birleştirerek yeni taban dosyasını yaratacak. Bu yöntem, biraz çetrefil gibi kulağa gelse de, hiç şüphesiz INSERT ile taban yaratmaktan kat kat daha hızlı olacaktır. Önemli nokta: InputStream üzerinden parçalı bir dosyayı tek bir dosyaymış gibi okumak icin InputStreamChain [8] yöntemini kullanmak gerekli.

Tüm bu teknikleri kullanan bizim kodumuz [9] dosyasındaki DatabaseHelper.importDB içinde bulunabilir. Büyük veri transferi için kullanılan DatabaseHelper class'ının ilk örnekte olduğu gibi SQLiteOpenHelper'dan miras almadığına dikkat edelim: Bu yapılmadı çünkü gerekli değildi, statik veri durumunda versiyon kontrolunu taban dosyası büyüklüğü (size) üzerinden biz kendimiz yapıyoruz. DDL zaten gerekli değil, çünkü taban dosyasını olduğu gibi kopyalıyoruz.

Birim Testleri

Taban büyük ya da küçük olsun, eger geliştirme makinamızda Android DB çağrılarını test etmek istiyorsak bu çağrıları taklitlememiz (mock) lazım. Eğer telefon üzerinde JDBC kullanıyor olsaydık, taklitlemek kolay olurdu, test kodları telefon yerine dışarıdakı bir tabanı açardı (test kodlari bu tür bir bağlantıyı işlem kodlarına verirdi) ve malum Connection, Statement, ResultSet kullanımları olduğu gibi işlerdi. Fakat Android SQL durumunda özel API'leri taklitlemek gerekiyor. Buradaki okurlar için iyi haber şu: Biz kendi projemiz icin bu taklitlemeyi gerçekleştirdik, ve buradan paylaşıyoruz. Şu [9] zip dosyası içinde SQLiteDatabase, SQLException, ve Cursor kodları arka planda JDBC üzerinden bir Sqlite tabanına bağlanıp iş yapacak şekilde tekrar yazıldı, böylece işlem mantığının dünyadan haberi olmayacak, onlar kendini mobil platformda zannedecek ama aslında taklit kodları işletiyor olacaklar. Sqlite JDBC jar kodları şuradan [10] indirilebilir.

Tekniğin işlemesi için derlemek sürecinde, kod bazında taklit kodları işlem kodlarından ayırmak, bu kodları src/ dizininden bağımsız, onunla aynı seviyede "test" adlı bir başka dizine koymamız gerekti. Böylece emulatöre (ya da telefona) kod gönderirken test dizini normal derleme komutları tarafından derlenmeyeceği için (Ant ve Eclipse sadece src dizinine göre hazırlanmış), taklit kodları emulatöre gitmemiş olacak. Geliştirme, test ortamında ise taklit kodların cağrılması için src ve test dizinlerini bizim eklerimizle aynı anda derleriz, ve istediğimiz çağrımlar Android kodları yerine bizim kodlara gider. Aynı zip dosyasindaki [9] build-add.xml icinde Ant ortamında bunun tekniği mevcut: test-compile hedefi görüldüğü gibi iki kaynağı eşit seviyeden alarak derliyor ve sonuç class dosyalarını bin/ altına koyuyor. Eclipse gibi IDE kullanıcıları benzer derleme eklerini yapmanın yolunu bulacaktır. Test kodları ise şuna benzeyecek:

public TestEdilenClass {
 DatabaseHelper helper;
 public TestEdilenClass(DatabaseHelper helper) {
   this.helper = helper;
 }
 public void method1() {
   ..
   helper.execSQL(...);
   ..
 }
 ...
}

public class TestAlg {

 @Test
 public void test() throws Exception  {
   DatabaseHelper helper = new DatabaseHelper();
   SQLiteDatabase db = new SQLiteDatabase(); // bu taklit class aslında
   helper.db = db;
   TestEdilenClass alg = new TestEdilenClass(helper);
   alg.method1();
 }
}

Görüldüğü gibi normal bir class (taklit edilmemiş) olan DatabaseHelper'ı yaratıp onun uzerinde taklit edilmiş olan SQLiteDatabase objesini set ediyoruz. Böylece DatabaseHelper Android ortamında normal veri tabanı çağrıları yaptığını zannederken aslında bizim taklit kodları çağırıyor, çünkü Helper class execSQL gibi çağrıları db referansı üzerinden yapıyor. Bu kadar.
Not: JDBC kodlarının semantiği (cağrılış şekli) ve Android SQL kodlarının farkları sebebiyle cursor üzerinde moveToNext() öncesi sadece bir kere isLast() çağrılması gerekiyor. Eğer işlem mantığımızda isLast birkaç kere çağrılacaksa, bu birim testlerimizi bozabilir, bu değeri bir boolean icinde depolayıp cağrımı teke indirmemiz lazım. Bu pek önemli bir nokta değil aslında, çünkü tipik kullanım bunu gerektirmeyecektir.

Bu yazi ilk kez Java Dergisi 2008 Mayis ayi'nda mobil bolumunde yayinlanmisti.

Kaynaklar

[1] Well-Known Users of SQLite, http://www.sqlite.org/famous.html
[2] Android Gelistirme Ortami | Sayilar ve Kuramlar Blog, http://sayilarvekuramlar.blogspot.com/2009/12/android-kurulumu.html
[3] Android Debug Bridge | Android Developers, http://developer.android.com/guide/developing/tools/adb.html
[5] Datatypes In SQLite Version 3, http://www.sqlite.org/datatype3.html
[6] Data Storage | Android Developers, http://developer.android.com/guide/topics/data/data-storage.html
[7] Android ve Statik Dosyalari Okumak | Sayilar ve Kuramlar Blog, http://sayilarvekuramlar.blogspot.com/2009/12/android-ve-statik-dosyalari-okumak.html
[8] InputStreamChain | Paranoid Engineering Blog, http://paranoid-engineering.blogspot.com/2008/11/inputstreamchain.html
[10] SqliteJDBC, http://www.zentus.com/sqlitejdbc/
[11] Catalina Creek Blog, Placing a prepopulated sqlite database in an Android app, http://www.catalinacreek.com/blog/Placing_a_prepopulated_sqlite_database_in_an_Android_app
[12] Android Examples - SQL Demo, http://marakana.com/forums/android/android_examples/55.html

Sunday, April 4, 2010

Android -> AppEngine Baglantisi, Tekil Kullanicilar

Bir Android mobil uygulamasinin servis tarafi bir kodlar ile entegre edilmesi gerekebilir. Ne amacla? Belki agir hesap gerektiren kodlar var ve bu yuku servis tarafina aktarmak istiyoruz. Ya da, mobil programin kullanicilarinin birbiri ile mesajlasmasi gerekiyor, o zaman servis makinasi bu mesajlasmayi idare edecek bir trafik polisligi gorevini yapacak.

Entegrasyonun sekline gelince; Bu entegrasyon API bazli olabilir (client Java kodlari gerekli noktalarda servise API cagrisi yapar, bilgiyi kendi lokal ekranini degistirmek icin kullanir), ya da uygulama bir noktadan sonra kontrolu tamamen Web sayfa bazli servis tarafina aktarabilir. Bu yazida isleyecegimiz ikinci turden bir baglanti olacak. Bizim projemizin ihtiyaclari her telefonun bir kullanici gibi gozuktugu ve bu kullanicinin digerleri ile Web sayfalari uzerinden digerleri ile mesajlasabildigi bir sistemdi. Servis tarafinda Python Google App Engine (GAE) kullanildi.

Bu durumda, ilk once kimlik kavramini halletmek gerekiyor. Kullanicinin kimligini servis tarafina tanitmak icin ve kullanicinin Google'da hesabi oldugu durumda, kullanici / sifre kontrolunun Android'den Google'a cagri ile yapilip, gerekli bilgilerin servis tarafina aktarildigi ve GAE'nin otomatik olarak bu kullaniciya kendi API'si ile rahatca erisebilmesi sayesinde programa entegre edildigi bir cozum dusunulebilirdi. Fakat bu cozum 1) sizin programinizi kullanacak herkesin google hesabi olmasini gerektirdigi 2) kullanicinin sizin Android programiniza guvenmeyip kullanici / sifre bilgilerini vermekten kacinabilecegi olasiligindan hareketle takip edilmedi. Bunun yerine zaten kullaniciya ozel, onu tekil olarak kimlikleyebilecek baska bir cozumu sectik. O sey aslinda telefonun ta kendisidir. Bir telefon kullaniciya ozel olduguna gore telefon = uygulama = kimlik gibi bir irdeleme yapilabilirdi. Yani telefonda kurulan sizin Android programiniz tek kullaniciya gore yazilabilirdi.

Bunu halletmenin en basit yolu, ismi iyi bilinen (well-known) tek bir dosya icinde yoksa kimlik yaratmak, varsa o kimligi Web tarafi ile her iletisimde kimlik bilgisi olarak kullanmakti.

Kimlik degeri Google Bigtable tabaninda her kayit icin uretilen kimlik degerinden ibaret olabilir. Bu kimligin uretiminin servis tarafinda yapilmasi lazim dogal olarak, yoksa kimlik cakismalari olabilir.

Bizim cozumde API usulu verilip alinan tek bilgi kimlik degeri. Bunun icin cok basit bir cagri mekanizmasi kullandik; duz HTTP GET ile http://[BIZIM URL]/yenikullanici?param1=... gibi ozel bir url ziyaret ediliyor, bu ziyaret HTTP ile Android icinden yapiliyor, ve bu ziyaret karsiliginda servis tarafi hemen bir kullanici yaratip, onun kimligini response uzerinde print ile cevaba basiyor. Bu cevap baglanan tarafta okunup, kimlik olarak hemen telefonda iyi bilinen dosyaya yazilacak ve o dosya, o kimlik telefon icin hic degismeyecek sekilde set edilmis olacak. Servis tarafi:
class NewUser(webapp.RequestHandler):

def get(self):
param1 = self.request.get('param1')
..
user = AppUser(param1=param1, ...)
user.put()
self.response.out.write(str(user.key()))
Android tarafinda ise genel baglanma, sonucu okuma kodu soyle:
  public String visitUrl(String url) {
HttpClient httpclient = new DefaultHttpClient();

// Prepare a request object
HttpGet httpget = new HttpGet(url);

// Execute the request
HttpResponse response;

try {
response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
String result= convertStreamToString(instream);
instream.close();
return result;
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
Kimligin dosyada olup olmadiginin kontrolu ve okunup, kullanilmasi ise soyle. Bir Activity icinde
      WebView webview = new WebView(this);    
setContentView(webview);
...
File f = new File("/data/data/[UYGULAMA PAKET ISMI]/files/[KIMLIK DOSYASI]");
if (!f.exists()) {
String url = "http://[UYGULAMA URL]/yenikullanici?param1" + ...;
String id = visitUrl(url);
FileOutputStream out = openFileOutput("[KIMLIK DOSYASI]", Context.MODE_PRIVATE);
out.write(serialize(id));
out.close();
}
FileInputStream in = openFileInput("[KIMLIK DOSYASI]");
byte [] bid = new byte[in.available()];
in.read(bid);
in.close();
String id = (String)deserialize(bid);
webview.loadUrl("http://[UYGULAMA URL/giris?kimlik="+id);

} catch (Exception e) {
e.printStackTrace();
}
}
public byte[] serialize(Object obj) throws java.io.IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream() ;
ObjectOutputStream out = new ObjectOutputStream(bos) ;
out.writeObject(obj);
out.close();
byte[] buf = bos.toByteArray();
return buf;
}

public Object deserialize(byte [] bytes) throws Exception {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
Object out = in.readObject();
in.close();
return out;
}
Goruldugu gibi kimlik dosyasi araniyor (dikkat: Files objesi ile /data/data/[DOSYA]/files kontrolu yapilmali, ama openFileOutput(.., Context.MODE_PRIVATE) ve openFileInput cagrilari sadece [DOSYA] ismini kullanmali) ve dosya yoksa, visitUrl ile kimlik ureten kodlara baglaniliyor, deger okunuyor, dosyaya yaziliyor, ve bundan sonra kontrolun tamamen Web'e aktarilacagi /giris noktasina kimlik bilgisi kimlik=... ile aktariliyor.

Simdi, bir puruz daha kaldi. Kendisi de basli basina bir tarayici olan Webview objesine giris URL'i veriyoruz, ve aradan cekiliyoruz, bundan sonra link, dugme tiklamalari, vs. hep Web sayfalarinda olacak... Yani oyle oldugunu farz ediyorduk. Ilginc bir sekilde, ilk sayfa sonrasi link tiklamalarinin bizi apayri bir program olan Android Chrome tarayicisini goturdugunu gorduk. Bunu engellemek, her tiklamanin yine Webview icinde kalmasini saglamak icin su eki yapmak lazim. WebView objesini yarattiktan sonra Url takibi fonksiyonunu tekrar tanimlamak ve yine Webview'in kendi ic loadUrl cagrisina yonlendirmek gerekiyor.
     WebView webview = new WebView(this);    
webview.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
view.loadUrl(url);
return true;
}
});
setContentView(webview);
Niye her tiklamanin Webview icinde kalmasini istedik? Pek cok sebebi var. Bir kere servis tarafina /giris?kimlik=.. ile giris yaptiktan sonra geriye cookie icinde kimligi tekrar donduruyorduk (oturum yaratmak icin), ve ikinci tiklama apayri bir program baslatinca cookie kayboluyordu. Ayrica, Webview gorsel duzenlemesi Chrome duzenlemesinden farkli olabiliyor; Chrome ile sayfalarimizi habire zoom ettirmemiz gerekiyordu, vs.

Bu kadar.