четверг, 12 января 2012 г.

ProgressDialog в Android

Вы можете найти множество информации о диалогах в интернете. Возможно я не открою Вам что-то новое, но все же написать об этом стоит. Дело в том, что недавно я столкнулся с проблемой. Она заключается в том, что когда я запускаю ProgressDialog во второй и последующие разы, он как будто зависает (Не крутиться прогресс-колесо). И более того не обновляется сообщение в нем! Использовал я стандартный код:

private static final int PROGRESS = 0;

 @Override
   protected Dialog onCreateDialog(int id) {
       super.onCreateDialog(id);
       switch (id) {

                case PROGRESS:                
               ProgressDialog dialog = new ProgressDialog(this);
               dialog.setTitle("Please, wait...");                 
               dialog.setMessage(infoMsg);                 
               dialog.setIndeterminate(true);                 
               dialog.setCancelable(true);
               return dialog;
           default:
               return null;
       }
   }


Немного поэкспериментировав, я нашел выход из этой ситуации. Вместо использования "стандартного" кода, я использовал следующий:


public void StartProgressDialog(String msg){
 if(progressDialog==null) progressDialog = new ProgressDialog(this);
 progressDialog.setTitle("Please, wait...");          
 progressDialog.setMessage(msg);         
 progressDialog.setIndeterminate(true);          
 progressDialog.setCancelable(true);
 progressDialog.show();
}

И убираем его следующим образом:


public void StopProgressDialog(){
 if(progressDialog!=null){
 progressDialog.cancel();
 progressDialog.dismiss();
 progressDialog=null;
 }
}

Таким образом прогресс диалог всегда с нужным вам сообщением и прогресс-колесо крутится! :-)


Выгрузка RAW

Иногда требуется выгрузить какой-нибудь файл или картинку на карту памяти или еще куда-нибудь. Android позволяет это сделать! Все что для этого требуется, это создать папку в res в проекте (например raw) и поместить туда необходимый файл. А далее Вы можете использовать, например, мою функцию:

public static void UnloadRawData(Context context, int resource, File to){
if(to.exists()) to.delete();
try {
to.createNewFile();
FileOutputStream fos = new FileOutputStream(to);

InputStream is = context.getResources().openRawResource(resource);
byte[] bufferData = new byte[16384];

int bytesAmount=0;
while((bytesAmount=is.read(bufferData))!=-1){
if(bytesAmount<bufferData.length){
byte[] b = new byte[bytesAmount]; 
CopyBytesArray(bufferData, b);
fos.write(b);
}
else{
fos.write(bufferData); 
}
}
fos.flush();
fos.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}


Я думаю здесь не нужны пояснения. Поясню лишь параметры функции. Второй параметр - это ресурс, который Вы хотите выгрузить. Третий параметр - это файл куда Вы хотите выгрузить этот  ресурс.

Пример использования функции:

public void Example(String path){
   final File rawFile = new File(path);
   UnloadRawData(this, R.raw.yourfile, rawFile);
}

Бессмертный сервис

Иногда Вы включаете музыку в фоновом режиме, а сами, например, пишите смс или общаетесь в чате с друзьями. Так вот знайте, что Android убивает все ненужные сервисы и приложения, а в случае если пользователю потребуется снова отобразить это приложение, система восстанавливает его или создает заново. Но это не действует на сервис который проигрывает музыку! Хотите знать почему?! Система может убить этот сервис только в крайнем случае, таком как нехватка памяти. Ладно, ближе к делу. У нас есть сервис, который будет работать до тех пор пока его не отключит сам пользователь (а не система). Для этого надо указать системе, что он является Foreground-сервисом, а не Background (как по умолчанию).
Все что для этого нужно сделать, это переопределить метод onStartCommand и в нем вызвать метод startForeground с двумя параметрами.

private static int NOTIFICATION_ID = 1;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
                   startForeground( NOTIFICATION_ID,  ??? );
        }

Первый параметр - это уникальный идентификатор, по которому система будет определять ваше оповещение.
Со вторым параметром сложнее. Это переменная на само оповещение. Мы должны его создать для начала:
         int icon = R.drawable.icon;         
         long when = System.currentTimeMillis();

         Notification notification = new android.app.Notification(icon, "Hello world!!!", when); 
         //Создание намерения с указанием класса вашей Activity, которую хотите вызвать при           нажатии на оповещение.        
         Intent notificationIntent = new Intent(this, yourActivityClass);
         notificationIntent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);        
         PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);  
         notification.setLatestEventInfo(context, "TITTLE", "Text", contentIntent); 

Оповещение готово! Теперь передаем его в качестве второго параметра.
startForeground( NOTIFICATION_ID, notification);

Осталось лишь одно. Как сказать системе что наш сервис теперь "смертный" и его можно убить? Еще проще:  this.stopForeground(false).
Здесь параметром является булева переменная, которая спрашивает убирать оповещение после остановки сервиса или нет.
Вот и все!

P.S. 1. Все это работает начиная с версии 2.0.
        2. Метод startForeground обязательно требует оповещение (т.е. null передать не получиться) Сделано это для того, чтобы пользователь всегда знал, что сервис работает. Иными словами сервис, который будет инкогнито, сделать не получиться. :)


Сохранение и загрузка данных

Рано или поздно, но все разработчики под Android придут к необходимости хранить данные.
Вот и я при разработке очередной программы задался целью сделать универсальный класс, который поможет мне сохранять и извлекать данные.
Первое с чего мы начнем это с создания собственно класса. Он должен наследоваться от класса Serializable.


public class SerializableClass implements Serializable{
//Здесь мы перечисляем все поля, которые хотим хранить.
  public boolean field1;  
 public String field2;
  ...........

Далее создадим конструктор этого класса:


 public SerializableClass(boolean field1, String field2){
 this.field1 = field1 ;  
 this.field2 = field2 ;
 }

Затем идут статические методы которые преобразуют класс в массив байт и обратно соответственно:


public static byte[] serializeObject(SerializableClass o) { 
   ByteArrayOutputStream bos = new ByteArrayOutputStream();   
   try { 
     ObjectOutput out = new ObjectOutputStream(bos); 
     out.writeObject(o); 
     out.close();        
     byte[] buf = bos.toByteArray();   
     return buf; 
   } catch(IOException ioe) { 
     Log.e("serializeObject", "error", ioe);  
     return null; 
   } 




 public static SerializableClass deserializeObject(byte[] b) { 
 if(b==null) return null;  
 try { 
     ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(b)); 
     Object object = in.readObject(); 
     in.close();   
     return (SerializableClass)object; 
   } catch(ClassNotFoundException cnfe) { 
     Log.e("deserializeObject", "class not found error", cnfe);  
     return null; 
   } catch(IOException ioe) { 
     Log.e("deserializeObject", "io error", ioe);  
     return null; 
   } 
}

Но это еще не все. Нам необходимо сохранить массив байт и загрузить его. Для этого предназначены следующие два статических метода:


public static void SaveBytes(Context context, String filename, byte[] data){
File file = new File(filename);
if(file.exists()) file.delete();  
try {  
FileOutputStream fos = context.openFileOutput(filename, 0);
fos.write(data);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}  
}



public static byte[] LoadBytes(Context context, String filename){
byte[] buffer = null;
try {  
FileInputStream fis = context.openFileInput(filename);
buffer = new byte[(int) fis.getChannel().size()];
fis.read(buffer);
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return buffer;  
}






Вот и весь класс. Чтобы использовать его просто вызовите его методы в какой-либо функции.
Загрузка данных:

private void LoadData(){
    byte[] b = SerializableClass.LoadBytes(this,PATH); 
    SerializableClass serializable = SerializableClass.deserializeObject(b);
    if(serializable!=null){ 
    System.out.println(serializable.field1);
    System.out.println(serializable.field2);
    }
}    

Сохранение данных:


private void SaveData(){
SerializableClass serializable = new SerializableClass(true, "Hello world!!!");
SerializableClass.SaveBytes(this, PATH, 
SerializableClass.serializeObject(serializable));
}


Полный код класса:


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;


import android.content.Context;
import android.util.Log;


public class SerializableClass implements Serializable{


  public boolean field1;  
  public String field2;
  
 public SerializableClass(boolean field1, String field2){
  this.field1 = field1 ;   
  this.field2 = field2 ;
         }
  
 public static SerializableClass deserializeObject(byte[] b) { 
 if(b==null) return null;  
 try { 
     ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(b)); 
     Object object = in.readObject(); 
     in.close(); 
 
     return (SerializableClass)object; 
   } catch(ClassNotFoundException cnfe) { 
     Log.e("deserializeObject", "class not found error", cnfe); 
 
     return null; 
   } catch(IOException ioe) { 
     Log.e("deserializeObject", "io error", ioe);  
     return null; 
   } 
}
 
 public static byte[] serializeObject(SerializableClass o) { 
   ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
 
   try { 
     ObjectOutput out = new ObjectOutputStream(bos); 
     out.writeObject(o); 
     out.close(); 
 
     // Get the bytes of the serialized object 
     byte[] buf = bos.toByteArray(); 
 
     return buf; 
   } catch(IOException ioe) { 
     Log.e("serializeObject", "error", ioe);  
     return null; 
   } 

 
public static byte[] LoadBytes(Context context, String filename){
byte[] buffer = null;
try {  
FileInputStream fis = context.openFileInput(filename);
buffer = new byte[(int) fis.getChannel().size()];
fis.read(buffer);
fis.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return buffer;
 
}
 
public static void SaveBytes(Context context, String filename, byte[] data){
File file = new File(filename);
if(file.exists()) file.delete();
 
try {  
FileOutputStream fos = context.openFileOutput(filename, 0);
fos.write(data);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}  
}
}

Надеюсь он Вам пригодиться и сэкономит Ваше время!


среда, 11 января 2012 г.

Root Android Emulator

Недавно я столкнулся с проблемой root'а эмулятора. Дело в том, что при замене файла su выходила ошибка: Cannot create su: Out of memory! 
А теперь все по порядку. Этапы как я рутил эмулятор:
Для начала надо запустить эмулятор с параметрами:


emulator -avd MyAndroidVirtualDeviceName -partition-size 128 


После этого ошибка не выходит! И эмулятор рутится на ура!
Вот полное руководство что нужно сделать:
1. Запускаем эмулятор с параметрами, как показано выше.
2. Ждем пока эмулятор загрузиться.
3. Выполняем последовательность команд:
3.1 adb shell mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system
3.2 adb shell mv /system/xbin/su /system/xbin/osu
3.3 adb push su /system/xbin
3.4 adb shell chmod 6755 /system/xbin/su
3.5 adb shell ls -l /system/xbin/*su
3.6 adb install superuser.apk
3.7 adb shell sync


Все! Наш эмулятор rooted!


P.S. Вы можете создать .bat-файлы на этой основе и рутить свой эмулятор в два клика. ВАЖНО: Вы должны помнить, что рутить эмулятор необходимо каждый раз, когда его запускаете. 
Удачи в создании невероятных программ! 



Привязка выбранных данных к Spinner

Допустим у Вас есть определенная выборка данных из базы данных (SQLite) и Вы хотите отобразить ее в компоненте Spinner. Для этого Вам потребуется создать Cursor с данными, CursorAdapter который будет манипулировать вашими данными, ну и собственно сам компонент Spinner объявленный в xml файле вашего проекта.
Поехали:
Функция которая возвращает выборку данных:


public Cursor GetCursorCities(){
String cmd ="select -1 as _id,'' name from t_city union "+
"SELECT s.id as _id, s.name name FROM t_city s ORDER BY s.name";
return getReadableDatabase().rawQuery(cmd, null);
}

Стоит обратить внимание на то, что в запросе я делаю объединение для того, чтобы пользователь мог выбрать пустое значение в списке. И по умолчанию в компоненте Spinner оно установиться тоже как пустое значение. ВАЖНО: ID_KEY нужно помечать как _id!!! Так как в SQLite это считается первичным ключом. 

Далее нам необходимо найти наш компонент определенный в xml файле.

sp_city= (Spinner)findViewById(R.id.sp_city);

Следующим шагом будет создание курсора:


Cursor mCursor =  GetCursorCities();
startManagingCursor(mCursor);  

startManagingCursor - необходимо вызвать эту функцию для того, чтобы mCursor уничтожился после того как он будет не нужен. (Все попытки самому закрыть курсор приводили к ошибкам).

Подходим к финалу и создаем CursorAdapter:


CursorAdapter mAdapter = new SimpleCursorAdapter(this,
            android.R.layout.simple_spinner_item ,mCursor, new String[]{ "name" }, new int[]{android.R.id.text1});

Здесь стоит пояснить:
1. android.R.layout.simple_spinner_item - это типичная разметка для лотка в Spinner'e. Она уже есть в Android системе. Вы можете заменить эту разметку на свою.
2. mCursor - наша выборка данных.
3. new String[]{ "name" } - имя поля, которое мы собираемся отображать (оно есть в запросе).
4. new int[]{android.R.id.text1} - также стандартная разметка в Android'e означающая, что здесь будет находиться текст.

Осталось только присвоить этот CursorAdapter Spinner'у:
city.setAdapter(mAdapter);

Теперь при загрузке Вашего Activity появиться Spinner с данными.



вторник, 10 января 2012 г.

Простой способ анимации вашего приложения

Для начала мы должны создать саму анимацию. Для этого необходимо создать xml файл и поместить его в папку res\(далее папка на Ваше усмотрение). Я выбрал папку anim. Таким образом получилось: res\anim\fadeout.xml
Примеров анимации множество в интернете, я лишь приведу один из них немного модифицированный:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator">
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
   android:interpolator="@android:anim/accelerate_interpolator"
   android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="1200" />
   <translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="1200" />
</set>

Эта анимация выполняет исчезновение и смещение вправо окна продолжительностью 1.2 сек.
Затем чтобы вызвать его, мы просто выполняем функцию overridePendingTransition с двумя параметрами (анимация появления и исчезновения) после вызова Activity как показано ниже:


Intent intent = new Intent(this, HelpActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.fadeout, R.anim.fadeout);

Для краткости я использовал одну и ту же анимацию.
Вот и все...
P.S. Функция overridePendingTransition появилась только в Android версии 2.0. Так что на более старых версия она работать не будет.