9 Ekim 2013 Çarşamba

Android'de Bir Uygulama Nasıl Patlatılmaz?

Android'de uygulama gelşitiriyoruz. Aklımıza gelen bütün exception'ları handle ediyoruz ama elbet bir yerde bir exception gözümüzden kaçabiliyor. Uygulama burada patladığı zaman yüzümüz kızarıyor. Artık bundan çok sıkıldığınızı biliyorum. :)

Öyleyse neden bu yakalayamadığımız exception'ları uygulamayı patlatmadan bir yere kanalize edip uygulamayı ve kullanıcıyı istediğimiz yere yönlendirmeyelim? Bu sorunun cevabını Java'nın durdurak bilmeyen nimetleri içinde bulabilmemiz mümkün.

Java bu noktada bize UncaughtExceptionHandler sınıfını sunuyor ve bununla (adı üstünde) yakalanamayan exception'ları handle etmemizi sağlıyor. Hatta bu exception'ların stack trace'ini bir String objesinde saklayabileceğinizi söylesem?

Gözlerinizin yuvalarından çıkmasına gerek yok, böyle bir şeyin olmaması Java'nın ayıbı olurdu zaten. :) Bu özelliklerle dilerseniz yayındaki bir uygulamanın sadece patlamasını önlemekle kalmaz, dilerseniz stack trace'i kendinize mail olarak atabilir, uygulamanın neresinin patladığını izleyebilirsiniz.

Sistem elbetteki bizim hayal ettiğimiz şekilde işlemiyor. Bir uygulama patladıysa onun geri dönüşü olamaz, mevcut uygulama kapatılması gerekir ki, bu en sağlıklı yöntemdir ama kullanıcı dostu değildir. Biz bu yazımızda bunu kullanıcı dostu bir hale getireceğiz.

Bunun için eğlenceli bir uygulama yapalım. İki tane activity olsun, ilk activity'e Patlat butonu koyalım ve uygulama patlasın. Fakat yazdığımız sınıflarla bu patlama kullanıcıya belli edilmesin ve ikinci activity'e geçsin. Uygulamamıza yakalanamayan exception'ı handle edecek class'ımızı ekleyelim. Adı CustomExceptionHandler.java olsun.

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;

import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.util.Log;

public class CustomExceptionHandler implements UncaughtExceptionHandler {

 private final Context context;
 
 public CustomExceptionHandler(Context context) {
  this.context = context;
 }
 
 @Override
 public void uncaughtException(Thread t, Throwable e) {
  final Writer result = new StringWriter();
     final PrintWriter printWriter = new PrintWriter(result);
     e.printStackTrace(printWriter);
     String stacktrace = result.toString();
     printWriter.close();
     
     Log.d("Stack Trace", stacktrace);
     
     Intent intent = new Intent(context, ErrorActivity.class);
     context.startActivity(intent);
     
     Process.killProcess(Process.myPid());
  System.exit(10);
 }

}

UncaughtExceptionHandler sınıfından implement edilmiş sınıfımızda, uygulama patladığı zaman uncaoughException metodu çalışacak. Burada istediğimiz şeyi yapabiliriz. Kodda görüldüğü üzere ben stack trace'i LogCat'e yazdırıyorum ve ikinci activity'miz olan ErrorActivity'ye geçiyorum. Daha sonrasında patlayan uygulamayı kill ederek tamamen kapatıyorum. Eğer uygulamada önemli static değerleriniz varsa bunlara kill etmeden önce erişebilirsiniz.

Şimdi sırasıyla ilk activity'mizin XML'ini daha sonra ikinci activity'mizin XML'inin tasarımına geçelim. İlk activity'miz activity_main.xml



    

İkinci activity'miz activity_error.xml




    

        

        

Uygulamamız açıldığında ilk activity_main XML'iyle tasarlanmış MainActivity açılacak. Burada bir patlatma butonu bulunuyor. Bu butona basıldığında NullPointerException ile patlayacak bir takım kodlar çalışacak. Patlama işlemi gerçekleşince CustomExceptionHandler sınıfımız bu exception'ı yakalayacak ve konsola yazdıracak, ardından activity_error XML'iyle tasarlanmış ErrorActivity'miz açılacak.

Şimdi bu iki activity'ni Java tarafındaki sınıflarının içini dolduralım. İlk olarak MainActivity'.java

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
 }

 public void onPatlatClick(View v) {
  String funye = null;
  if(funye.equals("BOM")) {
   
  }
 }
}

Burada dikkat etmeniz gereken husus onCreate metodunda çağırdığım setDefaultUncaughtException'dır. Bu kod, uygulamanın başında çağrılmıştır ve yakalanamayan exception'ları yakalamak için CustomExceptionHandler sınıfımızı aktif hale getirmiştir. Bu koddan itibaren olabilecek bütün yakalanamayan exception'lar bu sınıfımıza düşecektir. Öte yandan. onPatlatClick metodumuzda Patlat butonuna basıldığında olacak işlemlerin implementasyonu yapılmıştır. Uygulama tam burada patlamaktadır ve NullPointerException fırlatır.

İkinci activity'miz ErrorActivity.java da aşağıdaki gibidir.

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class ErrorActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_error);
 }
 
 public void onAnaSayfaClick(View v) {
  Intent intent = new Intent(ErrorActivity.this, MainActivity.class);
  startActivity(intent);
  finish();
 }
}

Bu activity uygulama patladıktan sonra açılır. Activity'de ilk activity'mize geri dönüş yapabileceğimiz bir buton bulunur ve bu butona basıldığında bu activity sonlandırılır ve uygulama ilk açıldığı haline döner.

Böylece uygulamayı kullanıcıya 'Durduruldu' mesajı vermeden patlatır ve eski haline geri dönmesini sağlayabiliriz. Bu özellikle uygulamaya hiç bir şey olmamış gibi öncekini öldürüp farklı bir kanaldan yeni bir yaşam döngüsüne sokabilir ve kullanıcıya bunu farkettirmezsiniz. Dahası dilerseniz uygulamanın patladığı yerin stack trace'ini kendinize mail atabilir ve anında müdahale edebilirsiniz.

8 Ekim 2013 Salı

Android ile QR Kod ve Barkod Okuma

Mobil cihazların hayatımıza girmesiyle QR Kod ve Barkod okumak iş hayatında gereksinimler sıralamasında üst sıralara tırmanmaya devam ediyor. Bugün Play Store, Apple Store ve Windows Store'a baktığımız zaman birçok QR ve Barkod okuyabilen uygulamalara erişebilmemiz mümkün. Fakat bunlar sadece genel kullanımlarda işe yaramakta.

Kurumsal şirketler kendi mobil uygulamalarını yaptığı zaman özellikle stok takibi için barkod ve QR kod okumayı en hızlı ve pratik çözüm olarak görüyorlar. Böyle durumlarda QR kod ve barkod okuma spesifik istekler arasına giriyor ve internetten uygulama bulmak yerine biz yazılımcılara büyük iş düşüyor. Kurumsal şirketlerin mobil uygulamalarında kendi istekleri doğrultusunda çalışacak bir barkod okuyucu geliştirmek durumunda kalıyoruz.

QR kod ve barkod okuyabilmek için birçok implementasyon yapmamız gerekiyor. Öncelikle zbar, armeabi, armeabi-v7a, x86 gibi bir çok kütüphaneyi uygulamamıza entegre etmemiz gerekiyor. Bu kütüphanelerin uygulamanın libs klasöründe durması yeterli olacaktır. Blog sonunda örnek bir uygulamayı sizinle paylaşacağım.

Öncelikle AndroidManifest.xml dosyamızda kameranın açılması için gerekli izinleri vermemiz gerekiyor. Aşağıdaki permission'ı kopyalayıp yapıştıralım.



Şimdi gerekli implementasyonlara başlayabiliriz. Barkod okumak için kameranın açılacağı activity'nin xml'ini oluşturalım.




  

  

  


FrameLayout'ta kameramız açılacak. TextView'da da okuma başarılı olduğu takdirde okuduğu şeyi yazacak. Örneğin bir URL, bir yazı veya bir e-mail adresi, farketmez.

Şimdi kameramıza gerekli ayarları verip, FrameLayout'umuza yerleştirmek için CameraPreview.java dosyamızı oluşturuyoruz.

import java.io.IOException;

import android.content.Context;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private PreviewCallback previewCallback;
    private AutoFocusCallback autoFocusCallback;

    public CameraPreview(Context context, Camera camera,
                         PreviewCallback previewCb,
                         AutoFocusCallback autoFocusCb) {
        super(context);
        mCamera = camera;
        previewCallback = previewCb;
        autoFocusCallback = autoFocusCb;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);

        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            Log.d("DBG", "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // Camera preview released in activity
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        /*
         * If your preview can change or rotate, take care of those events here.
         * Make sure to stop the preview before resizing or reformatting it.
         */
        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        try {
         if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
          mCamera.setDisplayOrientation(90);        
         }
         else if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
          mCamera.setDisplayOrientation(0);
         }

            mCamera.setPreviewDisplay(mHolder);
            mCamera.setPreviewCallback(previewCallback);
            mCamera.startPreview();
            mCamera.autoFocus(autoFocusCallback);
        } catch (Exception e){
            Log.d("DBG", "Error starting camera preview: " + e.getMessage());
        }
    }
}

Bu ayarlarla kameramızın bulunduğu oryantasyona göre görüntülenme açısı belirlenecek. Şimdi barkodun okutulacağı MainActivity.java'ımızın içini dolduralım.

import net.sourceforge.zbar.Config;
import net.sourceforge.zbar.Image;
import net.sourceforge.zbar.ImageScanner;
import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;

public class MainActivity extends Activity {

 private Camera mCamera;
    private CameraPreview mPreview;
    private Handler autoFocusHandler;

    TextView scanText;
    Button scanButton;

    ImageScanner scanner;

    private boolean barcodeScanned = false;
    private boolean previewing = true;

    static {
        System.loadLibrary("iconv");
    } 

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        autoFocusHandler = new Handler();
        mCamera = getCameraInstance();

        /* Instance barcode scanner */
        scanner = new ImageScanner();
        scanner.setConfig(0, Config.X_DENSITY, 3);
        scanner.setConfig(0, Config.Y_DENSITY, 3);

        mPreview = new CameraPreview(this, mCamera, previewCb, autoFocusCB);
        FrameLayout preview = (FrameLayout)findViewById(R.id.cameraPreview);
        preview.addView(mPreview);

        scanText = (TextView)findViewById(R.id.scanText);

    }

    public void onPause() {
        super.onPause();
        releaseCamera();
    }

    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open();
        } catch (Exception e){
        }
        return c;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            previewing = false;
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }
    }

    private Runnable doAutoFocus = new Runnable() {
            public void run() {
                if (previewing)
                    mCamera.autoFocus(autoFocusCB);
            }
        };

    PreviewCallback previewCb = new PreviewCallback() {
            public void onPreviewFrame(byte[] data, Camera camera) {
                Camera.Parameters parameters = camera.getParameters();
                Size size = parameters.getPreviewSize();

                Image barcode = new Image(size.width, size.height, "Y800");
                barcode.setData(data);

                int result = scanner.scanImage(barcode);
                
                if (result != 0) {
                    
                    SymbolSet syms = scanner.getResults();
                    for (Symbol sym : syms) {
                        scanText.setText("Barkod Sonucu: " + sym.getData());
                        scanText.setTextColor(Color.parseColor("#00AF03"));
                        barcodeScanned = true;
                        releaseCamera();
                        Intent intent = new Intent();
                        intent.putExtra("SCAN_RESULT", sym.getData());
                        setResult(RESULT_OK, intent);
                    }
                }
            }
        };

    // Mimic continuous auto-focusing
    AutoFocusCallback autoFocusCB = new AutoFocusCallback() {
            public void onAutoFocus(boolean success, Camera camera) {
                autoFocusHandler.postDelayed(doAutoFocus, 1000);
            }
        };

}

Barkod okuyucumuz hazır. Artık kameramızla hem barkod hem de kare barkod okuyabiliyor olacağız. Okuma işlemi başarılı olduğunda kameranın altında okunulan şeyi görebileceksiniz. Aşağıdaki linkten uygulamanın source kodunu paylaşıyorum. Siz okuyucuyu dilediğiniz şekle dönüştürebilirsiniz.

BarcodeReader'ı İndir

7 Ekim 2013 Pazartesi

Java ile Linux Server'a SSH Kodu Gönderme, Sonuçları İzleme

Kullanıcıların artık yazılımlarla kullanıcı dostu arayüzler ile iletişime geçmek istediği bir dünyada yaşıyoruz. Bu konu sadece son kullanıcıların değil, artık biz geliştiricilerin de talepleri arasına girmiş durumda.

Bu bağlamda Linux Server'ı örnek olarak gösterebiliriz. Putty gibi çeşitli araçlarla bağlanabilen bu serverlar DOS gibi komutlarla çalışmaktadır ve hayli uğraştırıcıdır. İşin içine büyük script'ler girdiği zaman gereksiz bir efor sarfedildiğini iddia edersek yanılmış olmayız.

Java ile Linux Server'ları birbiri ile iletişim haline soktuğumuzda bu sorunu ortadan kaldırabiliriz. Daha önceden hazırladığımız SSH kodunu Java ile gönderirsek ve bu aksiyonları Java'nın desteklediği arayüzlere bağlayabilirsek, bu işi tamamen arayüzlerle halledebiliriz.

Java ile Linux Server'a SSH kodu göndermek Ganymed isimli bir kütüphanenin yardımıyla mümkün. Buraya tıklayarak Ganymed'in güncel JAR dosyasını indirip projenize import edin.

Şimdi aşağıda linux serverımıza bir ls kodu göndereceğiz ve sonuçları konsola yazdıracağız.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

public class Main {

 private static int exitStatus;
 
 public static void main(String[] args) {
  // ipAdress: IP adresi
  // userName: Linux serverınızdaki kullanıcı adı
  // password: Kullanıcının şifresi
  // dir: ls kodunun sorgulayacağı directory
  // Bu değerler yerine kendi değerlerinizi girmeyi unutmayın
  ArrayList result = lsViaSSH("ipAddress", "userName", "password", "dir");
  
  if(result != null && result.size() > 0) {
   System.out.println("Results are listing below... \n");   
   for(String item : result) {
    System.out.println(item);
   }
  }
  else {
   System.out.println("Result not found");
  }
  
  System.out.println("\nExitStatus: " + exitStatus);
  System.out.println("\nSSH done!");
 }
 
 public static ArrayList lsViaSSH(String hostname, String username, String password, String dir) {
     ArrayList ls = new ArrayList();

     try {
         Connection conn = new Connection(hostname);

         System.out.println("Trying to connect " + hostname);
         conn.connect();
         System.out.println("Connected!");

         System.out.println("Authenticating...");
         boolean isAuthenticated = conn.authenticateWithPassword(username, password);

         if (isAuthenticated == false) {
          System.out.println("Authentication failed!");
             return null;
         }
         else {
          System.out.println("Authenticated!");
         }

         Session sess = conn.openSession();
         System.out.println("Session opened");
         
         System.out.println("Executing command: ls -r " + dir);
         sess.execCommand("ls -r " + dir);

         InputStream stdout = new StreamGobbler(sess.getStdout());
         InputStream stderr = new StreamGobbler(sess.getStderr());

         BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
         BufferedReader br1 = new BufferedReader(new InputStreamReader(stderr));
         
         while (true) {
             String line = br.readLine();
             if (line == null) {
              break;              
             }
             ls.add(line);
         }
         
         while (true) {
             String line = br1.readLine();
             if (line == null) {
              break;              
             }
             ls.add(line);
         }

         sess.close();
         conn.close();
         exitStatus = sess.getExitStatus();
     }
     catch (IOException e) {
         return null;
     }

     if(ls.size() == 0) {
      return null;      
     }

     return ls;
 }

lsViaSSH metoduna gerekli parametreleri gönderdikten sonra Ganymed kütüphanesini kullanarak önce connect oluyoruz. Daha sonra servera kullanıcı adı ve şifre ile authenticate oluyoruz. Hata çıkmazsa bağlantımızdan kendimize bir session açıyoruz.

Bu işlemlerden sonra istediğimiz SSH komutunu server'a gönderebiliriz. execCommand metodu ile SSH kodunu gönderiyoruz. Sonuçlar session'ımızın Stdout objesine düşüyor. Bu objeyi inputstream'e atarak BufferedReader ile sonuçları satır satır okuyarak ls isminde bir ArrayList'e atıyoruz. Bu liste main metodumuza dönüyor ve listeyi konsola yazdırıyoruz.

Eğer SSH kodunda hata yoksa session'ın içindeki ExitStatus objesi 0'a setleniyor, hata varsa hata koduna setleniyor. SSH kodunda hata olması halinde lsViaSSH metodu ArrayList'e hatanın açıklamalarını koyuyor ve konsola sonuçlar yerine hata mesajı gönderiyor.

Böylece Java ile gönderilen SSH kodunun sonuçlarını Java'da görebiliyor ve istediğimiz şekilde kullanıyoruz. Eğer kullanıcı adı ve şifre ile authenticate olmak yerine bir Public Key'iniz varsa Ganymed kütüphanesi bu tür authentication'ları destekleyecek gerekli implementasyonu yapmış durumda.

Ganymed kütüphanesi Linux Server üzerinde yapılan çalışmaları otomatize etmek için iddialı bir araç olacağa benziyor.