22 Mayıs 2013 Çarşamba

Basit bir Android Twitter İstemcisi

Merhaba,

Bugün Android'te twitter arama api*'sini kullanarak basit bir uygulama yazacağız. Yazacağımız uygulama başlangıç seviyesindekiler için çok uygun olmayabilir fakat yine baştan sona bu makale takip edilirse meraklılar istedikleri sonuca ulaşabilirler. Uygulama geliştirmeye geçmeden önce yapacağımız uygulamanın neye benzediği hakkında bir fikir edinmeniz açısından tamamlanmış uygulamanın ekran birkaç ekran çıktısını paylaşmakta fayda görüyorum.

 Yan tarafta uygulama açıldığında karşınıza çıkan ekran görünmektedir. Üst tarafta bir arama alanı ve buton, ve mevcut butona bastığınızda ilgili aramanızdan sonuçlar gelmektedir.


Bu resimde ise uygulama içerisinde 'mehmet' araması için gelen sonuçlar bir listede gösterilmektedir.

















Uygulama arayüzü basit ve sadedir. Makalede stillere ve görsel arayüz detaylarına fazla değinilmeyecektir. Dilerseniz daha sonra bu uygulamayı bir prototip gibi kullanıp kendi uygulamanızda farklı stiller uygulamak şeklinde kullanabilirsiniz.

Uygulamanın kaynak kodlarını  buradan indirebilirsiniz

Bu makalede hangi Android özelliklerini nedenleriyle birlikte öğreneceğinizi belirtmekte fayda görüyorum

Kullanılan Özellikler

Fragment: Klasik Activity tabanlı ve fragment kullanılmadan uygulama geliştirme tekniği artık geride kaldığı ve fragmentlerin uygulama geliştirme aşamasında daha modüler çalışmanıza olanak tanıdığı için fragment kullanmayı uygun gördüm. Aslında yazacağımız uygulama Fragment'lere ihtiyaç duyan bir uygulama değil, sadece nasıl kullanıldığını göstermek açısından fragment kullandım. Bilmeyenler için şunu belirtelim: Fragment kavramı Android API seviyesi 11'de geldi, fakat Android Support Library sayesinde daha düşük seviyedeki API'ler de destekleniyor.Eğer Eclipse ve ADT'nin(Android Developer tools) son versiyonunu kullanıyorsanız oluşturacağınız projede Support Library otomatik olarak gelecektir. Kullanacağımız fragmentler de Support Library fragmentleri olacaktır. Fragment, bir Activity'yi oluşturan parçalar olarak tanımlanabilir, ancak Activity'den bağımsız olarak.

ListView Kullanımı, Performans ve Yavaş yükleme: ListView, Android'te en çok kullanılan widget'lardan biridir ve eğer doğru kullanılmazsa birçok performans problemine sebep olur. Bir listview'in en basit haliyle kullanımını değil, onu nasıl en performanslı biçimde kullanacağımızı öğreneceğiz. Yavaş yükleme ise yine resimlerin liste hareket ettirildiğinde takılmaması için gerekli performans optimizasyonu da yapılmıştır.

Asynctask Kullanımı: Mobil dünyasında multithreading kavramı tabiri caizse olmazsa olmazdır. Bir uygulamanın kullanıcı arayüzü işlemleri main thread'te gerçekleştirilir. Ağ işlemleri, diskten dosya yükleme gibi işlemler ağır işlemlerdir ve eğer bunlar da main thread'te gerçekleştirilmeye çalışılırsa uygulamanın cevap verme süresi son kullanıcılar açısından felaket olur. Çokça karşılaşılan ANR(Activity not responding, uygulama cevap vermiyor) hatasının sebebi ağır işlerin main thread'te gerçekleştirilmeye çalışılmasıdır. Aslında ağ işlemlerini API seviyesi 11 ve üzeri cihazlarda gerçekleştiremiyorsunuz, tabi ki aksini belirtmedikçe (Thread policy seviyesini aşağıya çekip bunu yapabilirsiniz). Tüm bu saydıklarımızı baz alarak Android'in multithreading işlemleri için yazdığı AsyncTask sınıfını kullanacağız.

Twitter APIsi ve JSON parse etme: Bu uygulamada basit de olsa twitter API'sini kullancağız ve Android'te nasıl JSON parse edildiğini öğreneceğiz.


Bilmeniz Gerekenler

Bu makalede anlatılan uygulamayı anlayarak geliştirmek istiyorsanız şunları bilmelisiniz.

  • Nesne Tabanlı Programlama
  • Java Programlama Dili
  • Temel Seviyede Android Uygulama Geliştirme 
Eğer bunları bilmiyorsanız bu makaleden yeterince faydalanmayabilirsiniz.

Başlayalım

Yeni Android Uygulama Projesi Oluşturma

Uygulamamızı Eclipse ortamında geliştireceğiz, Yeni uygulama oluşturmak için şu adımları takip etmemiz gerekir:

1-Eclipse üzerinden File-New-Android Application Project denilir ve bir proje ismi girilir. package name, minimum sdk, target sdk alanları seçilir ve next'e tıklanır


 2-Projenin Workspace'i seçilir, Mevcut workspace'iniz varsa orda kalmanızı tavsiye ederim. Seçili ve seçili olmayan kutucuklar varsayılan olarak kalabilirler



3-Eğer uygulama için bir ikon seçmek istiyorsanız bu adımda seçebilirsiniz. çeşitli ekran derinliklerine göre farklı çözünürlükteki ikonları ekleme şansınız var.




4-Bir sonraki pencerede varsayılan olarak blank activity'yi seçili olarak bırakabilirsiniz




5-Son aşamada Activity ve activity için kullanacağınız xml layout'un ismini belirleyebilirsiniz. Ben olduğu gibi bıraktım. Siz istediğinizi girebilirsiniz.





Evet, projemizi oluşturduktan sonra projemizi tasarlamaya başlayabiliriz.

Uygulama Tasarımı

Mobil uygulama geliştirmede tasarım hayati bir öneme sahiptir. Platform bileşenlerini düzgün yerleştirmek, aralarındaki ilişkileri düzenlemek için mutlaka düzgün bir nesne tasarımı yapılmalıdır. Bizim uygulmada kullanacağımız sınıf diyagramı aşağıdaki gibi olacaktır

Sınıf Diyagramı


Diyagramda belirtilen tüm sınıflar açıklanacaktır. Fakat başlangıçta XML layout'ları düzgün olarak tanımlanmalıdır.

Uygulamamız tek bir Activity'den oluşmaktadır ve bu activity de biri üstte olmak üzere (topfragment) biri de altta ve listeyi içeren (mainfragment) olmak üzere iki fragment vardır.

Aşağıda topfragmentlayout.xml dosyası verilmiştir

 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent" >  
   
   <EditText  
     android:id="@+id/search_input"  
     android:layout_width="match_parent"  
     android:layout_height="wrap_content"  
     android:layout_alignParentLeft="true"  
     android:layout_alignParentTop="true"  
     android:layout_marginLeft="20dp"  
     android:layout_marginTop="15dp"  
     android:ems="10"  
     android:singleLine="true"  
     android:hint="@string/search_hint" >  
   
     <requestFocus />  
   </EditText>  
   
   <Button  
     android:id="@+id/search_button"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_alignBaseline="@+id/search_input"  
     android:layout_alignBottom="@+id/search_input"  
     android:layout_alignParentRight="true"  
     android:text="@string/search_text" />  
   
 </RelativeLayout>  


Top fragment üstte kullanacağımız bir kutucuk(edittext) bir de buton'dan oluşan basit bir yapıdır. Bu fragment'in layout'unu değerlendirmek için bir Fragment sınıfı yazmamız gerekmektedir. Daha sonra bu fragment sınıfını Ana activity layout'u için kullanabilelim.

TopFragment.java kodu aşağıda verilmiştir

 public class TopFragment extends Fragment implements View.OnClickListener{  
 public static final String twitterSearchUrl="http://search.twitter.com/search.json?q=";  
 EditText searchText;  
      @Override  
      public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
           View topView=inflater.inflate(R.layout.topfragmentlayout, container, false);  
        searchText=(EditText)topView.findViewById(R.id.search_input);  
           Button button=(Button)topView.findViewById(R.id.search_button);  
           button.setOnClickListener(this);  
           return topView;  
      }  
      @Override  
      public void onClick(View v) {  
           MainActivity activity=(MainActivity)getActivity();  
           activity.onSearchButtonClicked(searchText.getText().toString());  
           //hide keyboard  
           InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(  
                   Context.INPUT_METHOD_SERVICE);  
                imm.hideSoftInputFromWindow(searchText.getWindowToken(), 0);  
             
      }  
   
 }  


Yukarıdaki Fragment kodunu inceleyelim
 public static final String twitterSearchUrl="http://search.twitter.com/search.json?q=";   

satırı kullanacağımız Twitter API URL'ini belirtiyor, daha sonra aradığımız sözcüğü bu url'in sonuna ekleme yaparak kullanacağız.
 @Override   
    public View onCreateView(LayoutInflater inflater, ViewGroup container,   
         Bundle savedInstanceState) {   


onCreateView metodu da diğer Android bileşenlerinde olduğu gibi bir callback metodudur. Callback metodları daha alt düzeyden koda daha üst düzeyde ulaşmayı sağlayan metodlardır. Daha fazla merak ediyorsanız buraya bakabilirsiniz. Bu metod içerisinde xml layout'unu inflater ile inflate edebilirsiniz.

Not: Inflate etmek demek xml kaynağını parçalayıp kullanılabilir java koduna dönüştürmek demek, Türkçe'de şişirmek gibi anlamları var.

Layout'unuzu inflate ettikten sonra içerisinde tanımlı buton, kutocuk gibi View'lara erişebilirsiniz.

Fragment'teki View.OnClickListener arayüzü fragment'teki buton tıklandığında aksiyon alınması için eklenmiştir. Siz isterseniz OnClickListener kodunu içerde de yazabilirsiniz. Ancak daha esnek olmak istiyorsanız içeride değil dışarıda kullanmanızı tavsiye ederim.

Ara butonuna tıklandığında tıklandığında Activity'miz üzerinden diğer fragment olan MainFragment'e bir yükleme işleminin yapılması gerekmektedir. İki fragment birbiriyle konuşamadığına göre bunu bir şekilde activity üzerinden yapmamız gerekir.

Bir bilgi olarak içinde bulunduğumuz fragment'in kullanıldığı activity'yi çekebiliyoruz (getActivity() metoduyla). O zaman bir çözüm olarak MainFragment ve MainActivity'nin ikisinin birlikte kullanacağı bir Interface (arayüz) tanımlamak bir çözüm olabilir. Bu sayede TopFragment'te buton tıklandığında Activity'nin uygun Interface metodu tetiklenir ve bu da MainFragment'te gerekli tetiklemeyi yapar.

Kullanacağımız Interface şudur:
 public interface SearchClickListener  
 {  
   public void onSearchButtonClicked(String searchText);  
 }  

MainFragment için kullanılan Xml layout'u aşağıda verilmiştir.
 <ListView xmlns:android="http://schemas.android.com/apk/res/android"  
   android:id="@android:id/list"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent" >  
 </ListView>  

Gördüğünüz gibi fragment'imiz tek bir ListView'den oluşuyor. Uygulamamız için her bir liste satırı için ortak bir layout tanımlamamız gerekmektedir.

Bu layout kodu aşağıda verilmiştir:
tweetlistrow.xml
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent" >  
   <ImageView  
     android:id="@+id/profileImage"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_alignParentLeft="true"  
     android:layout_alignParentTop="true"  
     android:layout_marginLeft="14dp"  
     android:layout_marginTop="14dp"  
     android:src="@drawable/ic_launcher" />  
   <TextView  
     android:id="@+id/tweetdatetext"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_alignBaseline="@+id/userInfoText"  
     android:layout_alignBottom="@+id/userInfoText"  
     android:layout_alignParentRight="true"  
     android:layout_marginRight="30dp"  
     android:text="Small Text"  
     android:textAppearance="?android:attr/textAppearanceSmall" />  
   <TextView  
     android:id="@+id/userInfoText"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_alignTop="@+id/profileImage"  
     android:layout_marginLeft="19dp"  
     android:layout_toRightOf="@+id/profileImage"  
     android:text="Medium Text"  
     android:textAppearance="?android:attr/textAppearanceMedium" />  
   <TextView  
     android:id="@+id/tweetcontenttext"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_alignLeft="@+id/userInfoText"  
     android:layout_below="@+id/userInfoText"  
     android:text="TextView" />  
 </RelativeLayout>  

Bu layout'u daha sonra listemizdeki satırları oluştururken kullanacağız. Profil fotoğrafı için bir ImageView, Kullanıcı bilgisi, tweet zamanı ve içerik için ise birer TextView kullanılmıştır.

Şimdi de activity'mizde kullanacağımız layout kodunu yazma vakti geldi.

activity_main.xml
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   xmlns:tools="http://schemas.android.com/tools"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   android:paddingBottom="@dimen/activity_vertical_margin"  
   android:paddingLeft="@dimen/activity_horizontal_margin"  
   android:paddingRight="@dimen/activity_horizontal_margin"  
   android:paddingTop="@dimen/activity_vertical_margin"  
   tools:context=".MainActivity" >  
   <fragment  
     android:id="@+id/topFragment"  
     android:name="com.example.listviewsample.TopFragment"  
     android:layout_width="fill_parent"  
     android:layout_height="wrap_content"  
     android:layout_alignParentLeft="true"  
     android:layout_alignParentTop="true"  
     tools:layout="@layout/topfragmentlayout" />  
   <fragment  
     android:id="@+id/mainFragment"  
     android:name="com.example.listviewsample.MainFragment"  
     android:layout_width="fill_parent"  
     android:layout_height="fill_parent"  
     android:layout_below="@+id/topFragment"  
     android:layout_centerHorizontal="true"  
     tools:layout="@android:layout/list_content" />  
 </RelativeLayout>  


Gördüğünüz gibi, MainActivity için kullanacağımız layout iki adet fragmentten oluşuyor, MainActivity kodunda bu layout'u activity'mize contentView olarak ekleyeceğiz.

MainActivity kodu aşağıda verilmiştir.
MainActivity.java
 public class MainActivity extends FragmentActivity implements SearchClickListener{  
      @Override  
      protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_main);  
      }  
      @Override  
      public void onSearchButtonClicked(String searchText) {  
           MainFragment mainFragment=(MainFragment) getSupportFragmentManager().findFragmentById(R.id.mainFragment);  
           mainFragment.onSearchButtonClicked(searchText);  
      }  
 }  

Gördüğünüz gibi activity kodumuz fragmentler sayesinde oldukça sade bir hal aldı. Yazdığımız fragment'leri layout içerisinde tanımladık ve o layout kullanılmaya başladığı andan fragment'in onCreateView metodu çalışmaya başladı. Burada dikkat edilmesi gereken bir şey var. Activity'miz mutlaka FragmentActivity'den türemeli (support library kullandığımız için), aksi halde activity'lerde fragment kullanılamayacaktır.


           MainFragment mainFragment=(MainFragment) getSupportFragmentManager().findFragmentById(R.id.mainFragment);  


bu satır ise fragmenti alıyor. Daha önce de bahsedildiği gibi Mainfragment'le ortak kullanılmak üzere bir SearchClickListener arayüzü oluşturmuştuk.

MainFragment kodumuz aşağıda verilmiştir.

MainFragment.java
 public class MainFragment extends ListFragment implements SearchClickListener{  
      ListView list;  
   ProgressDialog dialog;  
      @Override  
      public View onCreateView(LayoutInflater inflater, ViewGroup container,  
                Bundle savedInstanceState) {  
           // TODO Auto-generated method stub  
        list=(ListView)inflater.inflate(R.layout.mainfragmentlayout, container, false);  
           return list;  
      }  
      @Override  
      public void onSearchButtonClicked(String searchText) {  
     showDialog();  
           new GetTweetsTask().execute(searchText);  
      }  
      private void showDialog()  
      {  
           if(dialog==null)  
           {  
                dialog=new ProgressDialog(getActivity());  
                dialog.setTitle("Yukleniyor");  
           }  
           dialog.show();  
      }  
      private void closeDialog()  
      {  
           if(dialog!=null && dialog.isShowing())  
           {  
                dialog.dismiss();  
           }  
      }  
      private class GetTweetsTask extends AsyncTask<String, Void, ArrayList<Twit>>  
      {  
           @Override  
           protected ArrayList<Twit> doInBackground(String... params) {  
                String url=TopFragment.twitterSearchUrl+params[0];  
                TwitsParser parser=new TwitsParser(url);  
                return parser.parse();  
           }  
           @Override  
           protected void onPostExecute(ArrayList<Twit> result) {  
             list.setAdapter(new MainListAdapter(result));  
             ((MainListAdapter)list.getAdapter()).notifyDataSetChanged();  
             closeDialog();  
           }  
           @Override  
           protected void onProgressUpdate(Void... values) {  
                // TODO Auto-generated method stub  
                super.onProgressUpdate(values);  
           }  
      }  
 }  


Android görsel bileşenlerimizi neredeyse bitirdik, hatta MainFragment'tle birlikte ufaktan uygulamanın lojiğine biraz girmiş de olduk.

Dikkatinizi çektiyse MainFragment ListFragment'ten türüyor. ListFragment Fragment'in bir alt sınıfıdır ve içerik olarak bir ListView alır. ListActivity'ye aşinaysanız ListFragment size yabancı gelmeyecektir.


Bu sınıfta dikkatimizi çeken onSearchButtonClicked içerisinde başlatılan GetTweetsTask sınıfı.  Asynctask'tan daha önce bahsetmiştik, Bu generic sınıf main thread ile arka plandaki threadlerin birbirlerini kilitlemeden, senkronizasyon problemlerine neden olmadan bir arada çalışmasını sağlar. Üç adet parametre alır: Params, Progress ve Result. Daha sonra bu parametreler ilgili sınıfın metodlarında parametre olarak kullanılır. AsyncTask sınıfı ondan yeni bir sınıf türetip kendi ihtiyacınıza göre kullanmanız için tasarlanmıştır, başka bir şekilde kullanmanız tavsiye edilmez. AsyncTask'la ilgili belirtmek gereken bir şey ise sadece doInBackground metodunun arka plandaki thread'te çalışması ve arka plandaki thread'ten main thread'e müdahale edemeyeceğiniz. Sözgelimi bir butonun ismini doInBackground metodu içinde değiştiremezsiniz.


Peki GetTweetsTask ne iş yapıyor? belli bir arama sözcüğüne göre Twitter API'ye gidip oradaki twitleri almamızı sağlıyor. Bu işlemin doInBackground'ta yapılması gayet normal ve hatta gerekli. Çünkü ağ işlemi ve kullanıcıyı ilgilendirmiyor. Kullanıcıyı ilgilendiren o arada ne olduğunun kendisine bildirilmesi. Bu da task başlatılmadan önce showDialog() metoduyla sağlanmış oluyor. doInBackground'da parse işlemi yapıldıktan sonra onPostExecute(Result) metodu çalışıyor ve bu sayede main thread'te güncelleme yapılabilir oluyor.  Burada yapılan güncelleme ListView güncellemesi. Ancak buna geçmeden önce twitlerimizi nasıl parse ettiğimize bakalım. Bu işi yapması için bir TwitsParser sınıfı yazdım.

TwitsParser.java
 public class TwitsParser {  
      private String parseUrl;  
      public TwitsParser(String url)  
      {  
           parseUrl=url;  
      }  
      public ArrayList<Twit> parse(){  
           JSONArray tweetArray=null;  
           StringBuilder tweetFeedBuilder = new StringBuilder();  
     HttpClient tweetClient=new DefaultHttpClient();  
     try  
     {  
          HttpGet get=new HttpGet(parseUrl);  
          HttpResponse response=tweetClient.execute(get);  
          //check the status  
          StatusLine status=response.getStatusLine();  
          if(status.getStatusCode()==200){  
               HttpEntity entity=response.getEntity();  
               InputStream content=entity.getContent();  
               InputStreamReader isr=new InputStreamReader(content);  
               BufferedReader tweetReader=new BufferedReader(isr);  
               String line;  
               while((line=tweetReader.readLine())!=null){  
                    tweetFeedBuilder.append(line);  
               }  
               JSONObject resultObject=new JSONObject(tweetFeedBuilder.toString());  
               tweetArray=resultObject.getJSONArray("results");  
               return getTweetsFromArray(tweetArray);  
          }else  
          {//error  
          }  
     }catch(Exception e){}  
           return getTweetsFromArray(tweetArray);  
      }  
      private ArrayList<Twit> getTweetsFromArray(JSONArray tweetsArray){  
           ArrayList<Twit> tweets=new ArrayList<Twit>(tweetsArray.length());  
           for (int t=0; t<tweetsArray.length(); t++) {  
                try {  
                     JSONObject tweetObject = tweetsArray.getJSONObject(t);  
                     Twit twit=new Twit();  
                     twit.setUserInfo(tweetObject.getString("from_user"));  
                     twit.setProfileImageUrl(tweetObject.getString("profile_image_url"));  
                     twit.setDate(tweetObject.getString("created_at"));  
                     twit.setTweetContent(tweetObject.getString("text"));  
                     tweets.add(twit);  
                } catch (JSONException e) {  
                     // TODO Auto-generated catch block  
                     e.printStackTrace();  
                }  
           }  
           return tweets;  
      }  

Bu parser sınıfının üstünde fazla durmayacağım. Ama basitçe şunu söylemek gerekiyor. Bu sınıf kendisine verilen url'i Twitter API'sine göndererek gelen JSON'ı parçalayıp java nesnelerine dönüştüren bir sınıf. Bu sınıf twit listemizi almak için kullanıldı.

Twitleri aldıktan sonra yapmamız gereken o twitleri listeye yüklemek. ListView'in her bir satırı için belirlediğimiz xml layout'unu hatırlamanızı tavsiye ederim burda.

ListView ve Çalışma Yapısı

ListView, TextView gibi düz bir yapıda olmayıp bir listeyi içeren bir widget olduğundan çalışma şekli de farklıdır ve ayrı bir şekilde incelenmelidir. Bir ListView'e doğrudan item eklenip çıkarılmaz. Bir AdapterView yardımıyla her bir satır için ayrı ayrı muamele edilebilir bir hale getirilir ve AdapterView yardımıyla liste doldurulur. Burada bizim kullandığımız kendi tanımladığımız MainListAdapter. İncelememize oradan devam edebiliriz.


MainListAdapter.java
 public class MainListAdapter extends BaseAdapter{  
      private ArrayList<Twit> twits;  
      HashMap<Integer, ImageView> imageViewCollections;//to get images by tags  
      public MainListAdapter(ArrayList<Twit> data)  
      {  
           twits=data;  
           imageViewCollections=new HashMap<Integer, ImageView>();  
           new ImageLoaderTask().execute(twits);  
      }  
      @Override  
      public int getCount() {  
           // TODO Auto-generated method stub  
           return twits.size();  
      }  
      @Override  
      public Object getItem(int position) {  
           // TODO Auto-generated method stub  
           return twits.get(position);  
      }  
      @Override  
      public long getItemId(int position) {  
           // TODO Auto-generated method stub  
           return position;  
      }  
      @Override  
      public View getView(int position, View convertView, ViewGroup parent) {  
           // TODO Auto-generated method stub  
           TwitViewHolder viewholder =new TwitViewHolder();  
           if(convertView==null)  
           {  
                LayoutInflater inflater=  
                     (LayoutInflater)parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
                convertView=inflater.inflate(R.layout.tweetlistrow, parent, false);  
                viewholder.userInfoText=(TextView)convertView.findViewById(R.id.userInfoText);  
                viewholder.contentText=(TextView)convertView.findViewById(R.id.tweetcontenttext);  
                viewholder.profileImage=(ImageView)convertView.findViewById(R.id.profileImage);  
                viewholder.dateText=(TextView)convertView.findViewById(R.id.tweetdatetext);  
                imageViewCollections.put(position, viewholder.profileImage);  
                convertView.setTag(viewholder);  
           }else{  
                viewholder=(TwitViewHolder) convertView.getTag();  
                imageViewCollections.put(position, viewholder.profileImage);  
           }  
           //set view properties  
           viewholder.userInfoText.setText(twits.get(position).getUserInfo());  
           viewholder.dateText.setText(twits.get(position).getFormattedDate());  
           viewholder.contentText.setText(twits.get(position).getTweetContent());  
           return convertView;  
      }  
      private void setBitmap(Bitmap bitmap, int position)  
      {  
       ImageView imageViewRef=imageViewCollections.get(position);  
       if(imageViewRef!=null)  
    imageViewRef.setImageBitmap(bitmap);  
      }  
      private class ImageLoaderTask extends AsyncTask<ArrayList<Twit>, BitmapPositionPair, Boolean>  
      {  
           @Override  
           protected Boolean doInBackground(ArrayList<Twit>... params) {  
                ArrayList<Twit> twits=params[0];  
                for(int i=0;i<twits.size();i++){  
                     Bitmap bitmap=ImageHelper.getBitmapFromURL(twits.get(i).getProfileImageUrl());  
                     BitmapPositionPair bpp=new BitmapPositionPair();  
                     bpp.setBitmap(bitmap);  
                     bpp.setPosition(i);  
                     publishProgress(bpp);  
                }  
                return null;  
           }  
           @Override  
           protected void onPostExecute(Boolean result) {  
                // TODO Auto-generated method stub  
                super.onPostExecute(result);  
           }  
           @Override  
           protected void onProgressUpdate(BitmapPositionPair... values) {  
                setBitmap(values[0].getBitmap(), values[0].getPosition());  
           }  
      }  
      class BitmapPositionPair  
      {  
           private Bitmap bitmap;  
           private int position;  
           public Bitmap getBitmap() {  
                return bitmap;  
           }  
           public void setBitmap(Bitmap bitmap) {  
                this.bitmap = bitmap;  
           }  
           public int getPosition() {  
                return position;  
           }  
           public void setPosition(int position) {  
                this.position = position;  
           }  
      }  
      static class TwitViewHolder   
      {  
           ImageView profileImage;  
           TextView userInfoText;  
           TextView dateText;  
           TextView contentText;  
      }  
 }  


Bu sınıfı da yazdıktan sonra uygulamamızı bitirme noktasına geliyoruz nerdeyse. Fakat burada incelememiz gereken nazik durumlar var. Adapter arayüzünün bize sağladıklarından iyi faydalanamazsak uygulamalarımızdaki ListView'in doğasına aykırı olarak akmaması veya çok zor akması ve takılması gibi durumlarla karşılaşabiliriz.

Bunun için önünüze iki tane durum koymak istiyorum, siz de nasıl yapacağınızı düşünün bu arada. Diyelim ki elimizde 200 satırlık bir liste verisi var ve bunların içerdiği kullanıcı arayüzü bileşenlerinden her biri her bir liste satırı yaratıldığında tekrar tekrar yaratılıyor. Her liste satırında 4 tane bileşen(View) olduğunu düşündüğünüzden yukardan aşağıya her scroll'da 4*200=800 tane yaratım oluyor. Bu bir Android cihazın hafızası açısından felaket bir durumdur. Buradaki optimizasyonu birazdan yapacağız, ama önce önünüze diğer durumu koymak istiyorum.


Diyelim ki (bizim yaptığımız gibi) ağ üzerinden resim çekeceksiniz (hem de liste halinde). Bu resimleri bir listeye main thread içerisinde yüklemeye kalkarsanız listeyi scroll edebilir misiniz? Ben cevap vereyim: Bekleyen resimler yüklenmeden scroll yapamazsınız, bu ise ListView için, uygulama için ve hatta insanlık için büyük bir felakettir. Bunun da çözümünü bulacağız.


 public View getView(int position, View convertView, ViewGroup parent) 


Adapter içindeki bu metoda dikkatinizi çekmek istiyorum. Listenin satırlarının oluşumunu bu metod kontrol ediyor (Diğer metodları inceleyebilirsiniz). burada position satırın pozisyonu yani indeksi, convertView eğer varsa geri dönüştürülen bir View'i ve parent ise bu view'in ekleneceği içeren view i belirtiyor.

İlk durumdaki o performanssız duruma hiç convertView'i kullanmasaydık erişebilirdik. getView metodunda her bir view yaratıldığında ilgili pozisyondaki view bir recycler'a(geri dönüştürücüye) atılır ve daha sonra aynı pozisyondaki view'i elde etmek için o objeyi tekrar yaratmak yerine recycler'dan geri alınır. Ancak bu durum bile bizim için performanslı bir durum sayılamaz. Çünkü yine 400 item için maksimum bir 400 tane item yaratılacak. Burada avantaj daha önce scroll olan pozisyonlara geri dönüldüğünde ordaki view'ların recycler'dan geri dönmesi.

ViewHolder Pattern

Bizim durumumuzda her satır için üç view'lik bir konteynır'ımız var. Buna bir satırın view tipi denilebilir. Statik bir sınıf oluşturup özelliklerinden arındırılmış widget'larımızı koyarsak tekrar tekrar bu sınıfı kullanabiliriz. Bu bir liste için çok iyi bir optimizasyondur.
Bu sayede her bir view tipiniz için memory'de bir classlık yer allocate edilmesini sağlarsınız.


Resim Optimizasyonu ve Yavaş yükleme

Ağdan yüklenen resimlerin main thread'ten yapılmaması gerektiğini söylemiştik. Bu yüzden diğer içeriğin aksine resimleri ayrı bir thread içerisinde (Asynctask-ImageLoaderTask) içerisinde gerçekleştiriyoruz. Bu sayede lazy loading denen durumu elde ediyoruz.


Bu makaleye başlarken bu kadar süreceğini tahmin etmediğim için son konuları biraz atlamış oldum. Uygulamanın kaynak kodlarını  buradan indirebilirsiniz. Eclipse ortamına proje olarak import edip hemen kullanabilirsiniz. Lütfen sorularınız, düzeltmek istediğiniz bir şey varsa yorum yapmaktan çekinmeyiniz.

Mutlu programlamalar!




29 Ocak 2012 Pazar

Android Platform Bileşenleri

Merhaba arkadaşlar,

Bugünkü yazımda Android programlamaya giriş seviyesinde kısa teorik bilgiler vermek istiyorum. Android programlamaya başlamadan önce platformla tanışmak programlama açısından size epey avantaj sağlayacaktır.

Android platformunun Open Handset Alliance grubu tarafından desteklenen ve geliştirilen açık kaynaklı bir platform olduğunu belirtmekte fayda var.

Şimdi öncelikle Android platformunun bileşenlerinden bahsetmek gerekiyor. Android denince sadece mobil cihazlar için geliştirilmiş bir işletim sistemi mi yoksa işletim sisteminden öte bir programlama arayüzü sağlayan bütün bir platform mu olduğuna bakacağız. Android platform'u ve bileşenlerini tanıtmak açısından şurdan arakladığım oldukça açıklayıcı diyagramı paylaşmak istiyorum





Yukarıdaki resimden görüldüğü gibi Android platformu işletim sistemi çekirdeğinden(kernel), uygulama katmanı ve uygulamalara kadar uzanan bütün bir platformdur. Bu bileşenlerden her birini kısaca ve örneklerle açıklayalım

Linux Kernel

Android platformunun en alt katmanıdır. Android kernel olarak Linux 2.6 nın kernel ini kullanmaktadır. Bu katmanda çekirdek sistem kütüphaneleri, memory yönetimi, process yönetimi, network stack, sürücü model gibi işletim sisteminin çok temel servisleri bulunmaktadır. Bu katmanda donanımla haberleşmek amacıyla cihaz sürücüleri bulunmaktadır. Örnek verecek olursak bu katmanda yer alan camera driver kamera ile bir üst katman arasında arayüz görevi görmektedir. Android açık bir platform olduğu için bir android cihazına sürücüsünü yazdığınız sürece fazladan bir donanım bağlayabilir ve platform içine kolaylıkla gömebilirsiniz. Kernel katmanı daha çok c programlama dili kullanılarak yazılmıştır.


Libraries

Bu katmanda üst katmana sunulmak üzere çeşitli c-c++kütüphaneleri bulunmaktadır. Katmanın esas amacı uygulama katmanı tarafından kullanılacak işlevleri uygulama programcıları açısından kolaylaştırmaktır. Ayrıca grafik kütüphaneleri gibi performans kritik kütüphanelerin kaynak kodu buradadır. Bu katman C ve C++ dilleriyle yazıldığı için performans açısından oldukça elverişlidir. Bu katmanda ayrıca Android Runtime, runtime için çekirdek kütüphaneler ve Dalvik Virtual Machine bulunmaktadır. Bunların her birine detaylı olarak değinmeyeceğim ancak kısaca anlatmakta fayda görüyorum. Android runtime ve ilgili çekirdek kütüphaneler Android platformunun çalışması için gerekli olan bileşenleri içerir. Dalvik Virtual Machine optimize edilmiş Java Virtual Machine'dır. Dalvik Virtual Machine class dosyalarını Dalvik executable (dex) dosyalarına çevirir. (2.2 Froyo ile birlikte JIT complier sayesinde bytecode'lardan native makine koduna çeviri yapabilmektedir) Dalvik Virtual Machine java kodlarını dex'e çevirirken JVM'e oranla daha performanslı kod üretebilmektedir ve mobil cihazlar için çok uygundur. Ayrıca native program yazıp derlemek isteyenler bu katmanı kullanabilirler.

Application Framework

Uygulama geliştiricilerin esas olarak ilgilenmesi ve bilgi sahibi olması gereken katman application framework katmanı. Türkçe'de bazen "uygulama çatısı" olarak da kullanılıyor fakat şahsen bu şekilde kullanmak çok yapay ve zorlama duruyor. Bu yüzden yazımda oturmuş bazı şeyler için türkçe karşılık kullanmaktan çekineceğim. Neyse konumuza dönersek; uygulama katmanında Android platformunun uygulama geliştiricileri açısından kolayca kullanılmak üzere bileşenler üzerine Java dilinde yazılmış bir framework olduğunu söyleyebiliriz. Burada bileşenden kastettiğim platform değil uygulama bileşenleridir. Peki uygulama bileşenleri nedir? Kullanıcı arayüzüne sahip bir programı düşünün, bir programda kullanıcıyla etkileşim kurmak üzere butonlar, metin alanları vs gibi grafiksel bileşenler, onların tetiklenmesi için gerekli özellikler olması gerektiğini bilirsiniz ve uygulama bileşeni olarak da kastedilen tam da budur. Android platformunun uygulamalar için geliştirdiği mimariyi bilmek ve ona göre düzgün programlar yazabilmek için uygulama katmanı iyice anlaşılmalıdır. Örneğin android uygulamalarının temel bileşenlerinden biri Activity'dir ve Activity Manager bu bileşeni yönetmek, bu bileşenle diğer bileşenler arasındaki kontrolü sağlayabilmek için vardır. Bunun gibi telefon özelliği taşıyan bir mobil cihazda Application Framework'ta bulunan Telephony Manager telefon işlevlerinin sağlanması için yazılmış bir framework'tur. Kısacası yazacağınız uygulamalarda size en yakın dost, en iyi aracı Application Framework'taki bileşenler olacaktır. Bu bileşenler ve yazacağınız uygulamaların ikisinin de Java dilinde yazılmış olması sebebiyle kolaylıkla istediğiniz bileşeni veya bileşenleri kullanabileceksiniz

Applications

Burada artık diğer katmanların en üzerinde çalışan uygulamalar vardır. Bir rehber uygulaması, sizin yazacağınız bir Hello World uygulaması bu katman sınırları içerisindedir ve siz eğer bir uygulama programcısı olacaksanız bu katmandaki bileşenleri artırmak sizin görevinizdir.

Bitirmeden önce bir kaç not eklemek gerekirse; Android'te uygulamaları genellikle Java dilinde yazarsınız. Bunun için JDK ile birlikte kullanmanız gereken Android SDK'dır. Eğer çok performans kritik bir işlem yapıyorsanız veya düşük düzeye ulaşması gereken bir işlem yaparsanız C, C++ dillerinde native kod yazabilirsiniz. Bunun için Android Native Development Kit (NDK) kullanmalısınız. Ayrıca yukarıda belirttiğim katmanlar içerisindeki bileşenleri en alt katmandan başlayarak genişletebilirsiniz. Android programlama geliştirmek için Java dilini en az temel düzeyde bilmelisiniz. Eğer C# gibi nesne yönelimli bir programlama dili biliyorsanız java öğrenmeniz çok kolay olacaktır fakat Java'yı bilmeniz gerektiği gerçeğini değiştirmez bu.

Bu yazıda Android platformunun kurulumu, gerekli araçlar ile ilgili bilgi vermek niyetinde değilim. Platformun kurulumuyla ilgili yazılmış çok fazla yazı var. http://developer.android.com/sdk/installing.html adresinden sdk yüklemeniz için adımlar var. Bundan sonraki yazılarımda platformun kurulmuş olduğunu varsayarak yazacağım. Lütfen yazıyla ilgili yorumlarınızı paylaşmaktan çekinmeyin. Yazıda hata, eksiklik ve fazlalık gibi durumlar varsa lütfen belirtiniz. Sağlıcakla kalın

18 Kasım 2011 Cuma

Merhaba

Merhaba, bu blogta Android programlama konusunda çeşitli konuları yazmaya çalışacağım, umarım faydalı olur