четвер, 23 серпня 2012 р.

Пишем свой SimpleCursorAdapter

Всем привет! Эта статтья будет о прекрасном адаптере под названием SimpleCursorAdapter который помогает связать данные из бд со списком ListView. Эта статтья является как бы продолжением предыдущей ("Пишем свой SQLiteOpenHelper"), так как с той бд мы связываем наш адаптер и "прикручиваем" его к списку.
Для начала напомню что у нас есть объект-контейнер в который "кладутся" данные из бд:

public class Shop {
 
 private int id;
 private String title;
 private double price;
 
 public Shop() {
 
 }
 
 public Shop(String _title, double _price) {
  title = _title;
  price = _price;
 }
 
 public Shop(int _id, String _title, double _price) {
  id = _id;
  title = _title;
  price = _price;
 }
 
 public void setId(int _id) {
  id = _id;
 }
 
 public void setTitle(String _title) {
  title = _title;
 }
 
 public void setPrice(double _price) {
  price = _price;
 }
 
 public int getId() { return id; }
 
 public String getTitle() { return title; }
 
 public double getPrice() { return price; }
}
 
Определим для нашего списка главный лейаут:

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@android:id/list"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#2898FA">
 
</ListView>

Так же определим ещё один лейаут, он будет ячейкой списка. В ячейке будет два TextView, один для названия товара, другой для цены:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >
 
    <TextView
        android:id="@+id/titleTV"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:padding="8dp"
        android:background="#32D1D6"
        android:textColor="#000000" />
 
    <TextView
        android:id="@+id/priceTV"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:padding="8dp"
        android:background="#32D1D6"
        android:textColor="#000000" />
 
</LinearLayout>

Всё, марафет навели. Теперь будем разбираться что же всё таки такое SimpleCursorAdapter. Что нам нужно? Нужно определить класс (назовём его MyCursorAdapter) и расширить его SimpleCursorAdapter'ом. В SimpleCursorAdapter'e есть обязательный конструктор, который выглядит так:

 public MyCursorAdapter(Context _context, int _layout, Cursor _cursor, String[] _from, int[] _to) {
  super(_context, _layout, _cursor, _from, _to);
  layout = _layout;
 }
Не важно какой конструктор мы напишем, главное вызвать наследуемый конструктор в который следует передать: 1) текущий контекст; 2) лейаут ячейки; 3) курсор который указывает на данные из бд; 4) названия столбцов с которых следует взять данные; 5) view которые следует заполнить. Немножко не ясно, но потом когда нарисуется вся картина всё прояснится.
Значит наследуя класс SimpleCursorAdapter мы должны реализовать миниммум два метода. Первый называется newView:

@Override
public View newView(Context _context, Cursor _cursor, ViewGroup parent) {
 LayoutInflater inflater = (LayoutInflater) _context.getSystemService(_context.LAYOUT_INFLATER_SERVICE);
 View view = inflater.inflate(layout, parent, false);
 return view;
}

Этот метод создаёт view-ячейку. В качестве параметров ему передаём текущий контекст, курсор и элементы-родителя. В самом методе нам следует определить view ячейки, что мы и делаем с помощью LayoutInflater. На перёд забегая скажу что методы SimpleCursorAdapter'a не вызываются явно пользователем, они вызываются адаптером автоматически.
Второй метод будет "вешать" данные в ячейку:

@Override
public void bindView(View view, Context _context, Cursor _cursor) {
 String title = _cursor.getString(_cursor.getColumnIndex(AppData.COLUMN_TITLE));
 Double price = Double.parseDouble(_cursor.getString(_cursor.getColumnIndex(AppData.COLUMN_PRICE)));
 TextView titleTV = (TextView) view.findViewById(R.id.titleTV);
 TextView priceTV = (TextView) view.findViewById(R.id.priceTV);
 titleTV.setText(title);
 priceTV.setText(price.toString() + "$");
}

В качестве параметров ему передаётся view которую мы создали в предыдущем методе, текущий контекст и курсор. В первую очередь достаём необходимые данные из курсора, далее вставляем их в наши две TextView. Всё, как видим ничего сложного! Полностью класс MyCursorAdapter будет выглядеть так:

import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
 
public class MyCursorAdapter extends SimpleCursorAdapter {
 
 private int layout;
 
 public MyCursorAdapter(Context _context, int _layout, Cursor _cursor, String[] _from, int[] _to) {
  super(_context, _layout, _cursor, _from, _to);
  layout = _layout;
 }
 
 //связывает данные с view на которые указывает курсор
 @Override
 public void bindView(View view, Context _context, Cursor _cursor) {
  String title = _cursor.getString(_cursor.getColumnIndex(AppData.COLUMN_TITLE));
  Double price = Double.parseDouble(_cursor.getString(_cursor.getColumnIndex(AppData.COLUMN_PRICE)));
  TextView titleTV = (TextView) view.findViewById(R.id.titleTV);
  TextView priceTV = (TextView) view.findViewById(R.id.priceTV);
  titleTV.setText(title);
  priceTV.setText(price.toString() + "$");
 }
 
 //сoздаёт нвоую view для хранения данных на которую указывает курсор
 @Override
 public View newView(Context _context, Cursor _cursor, ViewGroup parent) {
  LayoutInflater inflater = (LayoutInflater) _context.getSystemService(_context.LAYOUT_INFLATER_SERVICE);
  View view = inflater.inflate(layout, parent, false);
  return view;
 }
 
}

Теперь переходим в главное активити которое наследуется от ListActivity. Там нам следует провести инициализацию.

    private void init() {
     String[] from = { AppData.COLUMN_TITLE, AppData.COLUMN_PRICE };
     int[] to = { R.id.titleTV, R.id.priceTV };
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     Cursor cursor = db.getAllCursor();
     adapter = new MyCursorAdapter(this, R.layout.item_listview, cursor, from, to);
     getListView().setAdapter(adapter);
     getListView().setOnItemClickListener(new OnItemClickListener() {
 
   public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Log.i("MyTag", "position = " + position + " && id = " + id);
 
   }
  });
    }

В первую очередь нам нужно подготовить данные для конструктора адаптера: from - необходимые столбцы из бд; to - эелементы куда вставлять данные взятые из from; db - наш DBHelper из предыдущего урока; cursor - получаем данные таблицы из бд. Далее вызываем конструктор адаптера и присваиваем своему списку адаптер. Так же создаём обработчик onItemClick для нашего списка, где в лог для наглядности будем выводить позицию в списке и айди из бд.
Скриншот программы:


На первом скриншоте видим что данные успешно и красиво загрузились. На втором же скриншоте продемонстрирован лог в который выводятся данные при нажатии на ячейки. Как видим разницу между position и id. Position - позиция нажатой ячейки в списке (начинается снуля). Id - айдишник из бд. Это наверное главный плюс этой всей затеей, так как при клике по ячейке списка мы уже знаем айди данной записи в бд и уже с этой записью можем делать всё что захочем.
Проект можно скачать по ссылкам:

середа, 22 серпня 2012 р.

Пишем свой SQLiteOpenHelper

И так сегодня поговорим о вспомогательном классе SQLiteOpenHelper для работы с SQLite. В этой теме нет ничего сложного, думаю будет всё просто и понятно. Начнём.
Для начала следует описать свой класс-контейнер для удобного хранения данных:

public class Shop {
 
 private int id;
 private String title;
 private double price;
 
 public Shop() {
 
 }
 
 public Shop(String _title, double _price) {
  title = _title;
  price = _price;
 }
 
 public Shop(int _id, String _title, double _price) {
  id = _id;
  title = _title;
  price = _price;
 }
 
 public void setId(int _id) {
  id = _id;
 }
 
 public void setTitle(String _title) {
  title = _title;
 }
 
 public void setPrice(double _price) {
  price = _price;
 }
 
 public int getId() { return id; }
 
 public String getTitle() { return title; }
 
 public double getPrice() { return price; }
}

Главное что бы в классе присутствовало поле id - это будет id из базы данных. Для наглядности в нашем классе присутствует ещё два поля с названием товара и его ценой.
Ещё создадим класс где будут описаны константы. Замечу что это одна из моих прихоть(хоть это является даже не моей прихотью, а просто это удобно так делать), так что вы можете описать эти константы в классе расширяющего SQLiteOpenHelper.

public class AppData {
 
 public final static int DATABASE_VERSION = 1;
 public final static String DATABASE_NAME = "shopDB.sqlite3";
 public final static String TABLE_NAME = "shop";
 public final static String COLUMN_ID = "_id";
 public final static String COLUMN_TITLE = "title";
 public final static String COLUMN_PRICE = "price";
}

Всё просто и не требует лишних комментариев, так как из названия полей можно понять для чего они.
Далее создаём класс который наследуется от SQLiteOpenHelper. Сразу он будет подчёркнутым красным потому что не реализованы два основных метода и конструктор. Eclipse сам создаст необходимые минимальные методы для функционирования класса.
Конструктор по умолчанию оставляем как есть:

 public DBHelper(Context context, String name, CursorFactory factory, int version) {
  super(context, name, factory, version);
 }

Если вам что нибудь нужно ещё то передавайте в конструктор и выделяйте отедльное поле. Но самое главное в конструкторе вызвать наследуемый конструктор.

@Override
public void onCreate(SQLiteDatabase db) {
 String createShopTable = "CREATE TABLE " + AppData.TABLE_NAME + " (" + AppData.COLUMN_ID +
  " INTEGER PRIMARY KEY AUTOINCREMENT, " + AppData.COLUMN_TITLE + " TEXT NOT NULL, " +
  AppData.COLUMN_PRICE + " DOUBLE NOT NULL)";
 db.execSQL(createShopTable);
}

Этот метод обязателен, он создаёт таблицу если её не существует. Те кто не знаком с основами языка SQL или его диалектами тому наверное трудно будет понять что написано в переменной createShopTable. В этоме запросе создаётся таблица с тремя полями: _id (именно с подчёркиванием так обязатльно, это написано в документации, айди всегда с подчёркиванием) это порядковый номер строки таблицы и является идентификатором/счётчиком; title - название товара; price - цена товара.

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 if(AppData.DATABASE_VERSION < newVersion) {
  db.execSQL("DROP TABLE IF EXISTS " + AppData.TABLE_NAME);
  onCreate(db);
 }
}

Ещё один обязательный метод onUpgrade. Он создаёт саму базу данных если её ещё не существует. В метод так же передаётся версии бд, если передаваемая версия выше существубющей то бд пересаздаётся.
Главные составляющие бд мы уже описали, и она является работоспособной. Но она малофункциональна и наш класс может лишь создавать бд и таблицу. В любой активити или классе мы можем получить экземпляр класса и выполнить любой SQL запрос, но это лишние "движения" и мы реализуем как можно больше методов для удобства в нашем классе, ведь мы его создавали в первую очередь для удобства!
Первым делом предлагаю сделать что бы класс сам добавлял данные в бд. Для этого напишем вот такой вот не трудный метод:

 public void addShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  ContentValues cv = new ContentValues();
  cv.put(AppData.COLUMN_TITLE, shop.getTitle());
  cv.put(AppData.COLUMN_PRICE, shop.getPrice());
  db.insert(AppData.TABLE_NAME, null, cv);
  db.close();
 }
В метод передаётся объект который следует добавить в бд. Открываем бд для записи, определяем объект ContentValues в который будем ложить данные для записи в методе put. В метод put первый аргумент название столбца в бд, второй - данные. Вызываем метод insert в который следует передать название таблицы, второй параметр оставляем нулевым так как он нужен если не ошибаюсь для того если мы пытаемся вставить пустую строку в SQL, третий параметр это ContentValues с данными. И в конце стоит закрыть соединение с бд. Кстати метод insert возвращает long единицу если операция прошла успешно и ноль  если нет. Так что можете ещё проверять были ли вставленны данные в бд.
Далее розумно думаю написать метод который по id вытаскивает нужный объект.

 public Shop getShop(int id) {
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.query(AppData.TABLE_NAME, new String[] { AppData.COLUMN_ID, AppData.COLUMN_TITLE, AppData.COLUMN_PRICE },
    AppData.COLUMN_ID + "=?", new String[] { String.valueOf(id) }, null, null, null);
  if(cursor != null)
   cursor.moveToFirst();
  Shop shop = new Shop(Integer.parseInt(cursor.getString(0)), cursor.getString(1), Double.parseDouble(cursor.getString(2)));
  return shop;
 }

Метод должен возвращать объект. В метод передаём айди необходимого объекта. Сначала открывает бд для чтения. далее определяем Cursor. Здесь следует по подробней остановится так как это важная часть. В Cursor мы получаем результат запроса. Главное что здесь нужно, так это правильно заполнить метод query. Первый параметр это название таблицы, второй параметр - массив столбцов которые следует вернуть в результате, третий параметр - название столбца для запроса со знаком "=?", четвёртый параметр - массив сравниваемых данных, остальные параметры оставляем нулевыми (они используются для группирования результата). Если объяснять запрос на пальцех то говорится так: "верни мне  из таблицы shop значение столбцов _id, title, price где _id будет равен входному числу". Далее првоеряем Cursor не равен он нулю, если нулевый то результат не вернул или же ошибочный запрос. Объявляем объект Shop в конструктор которого передаём полученные данные. Данные из Cursor вытягиваются по индексам. Индексы являются столбцы которые мы указывали в методе query и именно в том порядке в каком мы указали. Возвратили объект.
Следующим шагом будет удаление необходимых данных с бд:

 public int deleteShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  return db.delete(AppData.TABLE_NAME, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
 }

В метод передаём удаляемый объект. Традиционно открываем соединение с бд на запись. Далее выполняем команду delete которая позволяет удалять данные. Входными данными этого метода является: первый параметр - название таблицы; второй параметр - название столбца с _id и строка "=?"; третий параметр - массив с сравниваемыми данными. На пальцах запрос звучит так: "удали мне данные из таблицы shop где столбец _id равен значению shop.getId()". Метод возвращает результат запроса - один если удачно и ноль если fail.
Неплохо было бы реализовать метод который обновляет необходимые нам данные:

public int updateShop(Shop shop) {
 SQLiteDatabase db = this.getWritableDatabase();
 ContentValues cv = new ContentValues();
 cv.put(AppData.COLUMN_TITLE, shop.getTitle());
 cv.put(AppData.COLUMN_PRICE, shop.getPrice());
 return db.update(AppData.TABLE_NAME, cv, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
}

Входным параметром является объект который следует обновить. Открываем бд на запись. Определяем ContentValues, кладём в него обновлённые данные. Выполняем запрос update с такими входными параметрами: первый - название таблицы; второй - объект ContentValues, тертий - условие (в нашем случае это _id который равняется следующим параметрам), четвёртый - параметры удовлетворяющие условия третьего параметра. На пальцах: "вот тебе новые данные и вставь их в таблицу shop где _id равняется shop.getId()".
Наш вспомогательный класс всё разрастается и разрастается и следующим полезным методом будет метод который возврощает количество данных бд:

public int getShopCount() {
 String query = "SELECT * FROM " + AppData.TABLE_NAME;
 SQLiteDatabase db = this.getReadableDatabase();
 Cursor cursor = db.rawQuery(query, null);
 cursor.close();
 return cursor.getCount();
}

Здесь всё просто. Формируем простой запрос который возвращает нам все данные из таблицы shop. Открываем соединение с бд на чтение. Выполняем команду rawQuery в которой указываем в качестве входных параметров запрос и нул (вторым параметром являются данные сравнения которые в данном запросе нам не нужны). В ответ получаем Cursor с результатами и возвращаем количество полей таблицы.
Для полного удобства предлагаю ещё написать один метод который возвращает уже готовый список с объектами из бд:

public List<Shop> getAllShop() {
 List<Shop> shopList = new ArrayList<Shop>();
 String query = "SELECT * FROM " + AppData.TABLE_NAME;
 SQLiteDatabase db = this.getReadableDatabase();
 Cursor cursor = db.rawQuery(query, null);
 if(cursor.moveToFirst()) {
  do {
   Shop shop = new Shop();
   shop.setId(Integer.parseInt(cursor.getString(0)));
   shop.setTitle(cursor.getString(1));
   shop.setPrice(Double.parseDouble(cursor.getString(2)));
   shopList.add(shop);
  } while(cursor.moveToNext());
 }
 return shopList;
}

И так, определяем список объектов Shop. Формируем запрос который возвращает все данные из таблицы shop. Подключаемся к бд с правами на чтение. Возвращаем все данные в Cursor. Если в cursor'e есть данные то организовываем цикл по нему в котором кладём в объект Shop данные из cursor'a. Далее этот объект shop добавляем коллекцию и в конце возвращаем коллекцию с объектами.
Ну и на последок напишу ещё метод который возвращает Cursor с данными всей таблицы, это бывает полезно когда используется список с такими адаптерами как SimpleCursorAdapter:

public Cursor getAllCursor() {
 SQLiteDatabase db = this.getReadableDatabase();
 return db.query(AppData.TABLE_NAME, null, null, null, null, null, null);
}

Целиком класс будет выглядеть так:

import java.util.ArrayList;
import java.util.List;
 
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
 
public class DBHelper extends SQLiteOpenHelper {
 
 public DBHelper(Context context, String name, CursorFactory factory, int version) {
  super(context, name, factory, version);
 }
 
 @Override
 public void onCreate(SQLiteDatabase db) {
  String createShopTable = "CREATE TABLE " + AppData.TABLE_NAME + " (" + AppData.COLUMN_ID +
    " INTEGER PRIMARY KEY AUTOINCREMENT, " + AppData.COLUMN_TITLE + " TEXT NOT NULL, " +
    AppData.COLUMN_PRICE + " DOUBLE NOT NULL)";
  db.execSQL(createShopTable);
 }
 
 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  if(AppData.DATABASE_VERSION < newVersion) {
   db.execSQL("DROP TABLE IF EXISTS " + AppData.TABLE_NAME);
   onCreate(db);
  }
 }
 
 public List<Shop> getAllShop() {
  List<Shop> shopList = new ArrayList<Shop>();
  String query = "SELECT * FROM " + AppData.TABLE_NAME;
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.rawQuery(query, null);
  if(cursor.moveToFirst()) {
   do {
    Shop shop = new Shop();
    shop.setId(Integer.parseInt(cursor.getString(0)));
    shop.setTitle(cursor.getString(1));
    shop.setPrice(Double.parseDouble(cursor.getString(2)));
    shopList.add(shop);
   } while(cursor.moveToNext());
  }
  return shopList;
 }
 
 public void addShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  ContentValues cv = new ContentValues();
  cv.put(AppData.COLUMN_TITLE, shop.getTitle());
  cv.put(AppData.COLUMN_PRICE, shop.getPrice());
  db.insert(AppData.TABLE_NAME, null, cv);
  db.close();
 }
 
 public Shop getShop(int id) {
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.query(AppData.TABLE_NAME, new String[] { AppData.COLUMN_ID, AppData.COLUMN_TITLE, AppData.COLUMN_PRICE },
    AppData.COLUMN_ID + "=?", new String[] { String.valueOf(id) }, null, null, null);
  if(cursor != null)
   cursor.moveToFirst();
  Shop shop = new Shop(Integer.parseInt(cursor.getString(0)), cursor.getString(1), Double.parseDouble(cursor.getString(2)));
  return shop;
 }
 
 public int updateShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  ContentValues cv = new ContentValues();
  cv.put(AppData.COLUMN_TITLE, shop.getTitle());
  cv.put(AppData.COLUMN_PRICE, shop.getPrice());
  return db.update(AppData.TABLE_NAME, cv, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
 }
 
 public int deleteShop(Shop shop) {
  SQLiteDatabase db = this.getWritableDatabase();
  return db.delete(AppData.TABLE_NAME, AppData.COLUMN_ID + "=?", new String[] { String.valueOf(shop.getId()) });
 }
 
 public int getShopCount() {
  String query = "SELECT * FROM " + AppData.TABLE_NAME;
  SQLiteDatabase db = this.getReadableDatabase();
  Cursor cursor = db.rawQuery(query, null);
  cursor.close();
  return cursor.getCount();
 }
 
 public Cursor getAllCursor() {
  SQLiteDatabase db = this.getReadableDatabase();
  return db.query(AppData.TABLE_NAME, null, null, null, null, null, null);
 }
 
}

А теперь пришо время проверить работоспособность сего чуда. В главном активити прописываем и вызываем:

    private void addToShop() {
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     db.addShop(new Shop("Сапоги", 283.23));
     db.addShop(new Shop("Куртка", 683.28));
     db.addShop(new Shop("Шапка", 99.99));
     db.addShop(new Shop("Штаны", 160));
     db.addShop(new Shop("Футболка", 29.99));
     db.addShop(new Shop("Очки", 65.50));
    }
 
    private void deleteShop() {
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     List<Shop> shopList = db.getAllShop();
     int result = 0;
     for(Shop shop : shopList) {
      if(shop.getId() == 3)
       result = db.deleteShop(shop);
     }
     if(result == 1)
      Log.i("MyTag", "запись усшено удалена");
     else
      Log.i("MyTag", "при удалении произошла ошибка");
    }
 
    private void readShop() {
     db = new DBHelper(this, AppData.DATABASE_NAME, null, 1);
     List<Shop> shopList = db.getAllShop();
     for(Shop shop : shopList) {
      Log.i("MyTag", "ID = " + shop.getId() + " && TITLE = " + shop.getTitle() + " && PRICE = " + shop.getPrice());
     }
    }
Можете прикрутить это к любой View или же выводить для наглядности в Log как я сделал, а лучше всего в ListView (этому будет посвящён следующий урок).
Всем спасибо, с ув. Вячеслав.
Скачать полностью проект можно по ссылкам:

середа, 8 серпня 2012 р.

Учимся отправлять смс

Введение
Всем привет! Сегодня будем учиться отправлять смс. Научимся так же отправлять смс "пачками", отслеживать доставку.

Отправка sms
Передача смс осуществлятся при помощи объекта SmsManager:

SmsManager smsManager = SmsManager.getDefault();

получили ссылку на системный SmsManager. Так же стоит обновить манифест:

<uses-permission android:name="android.permission.SEND_SMS"/> //разрешение на отправку смс сообщений

Далее следует прописать номер поулчателя (запустите ещё один эмулятор) и тело сообщения:

String phoneNumber = "5556";
String message = "Hello world!!!";

и непосредственно с помощу нашего менеджера отправляем смс:

smsManager.sendTextMessage(phoneNumber, null, message, null, null);

метод sendTextMessage отправляет текстовое сообщение, в него мы передаём номер получателя и тело сообщения, остальные поля пока что не трогаем. Всё, можете отправлять.

Подтверждение доставки sms
Что бы отследить подтверждение о доставке необходимо зарегистрировать два слушателя. Первый слушатель будет срабатывать когда смс будет отправляться, второе сработает тогда когда получатель получил смс. Лучше всего для наглядности переопределить слушатели:

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsManager;
import android.util.Log;
 
public class SendSms extends BroadcastReceiver {
 
 private final static String MY_TAG = "MyTag";
 
 public void onReceive(Context context, Intent intent) {
  switch(getResultCode()) {
  case Activity.RESULT_OK:
   Log.i(MY_TAG, "SMS send");
   break;
  case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
   Log.i(MY_TAG, "unknown problems");
   break;
  case SmsManager.RESULT_ERROR_RADIO_OFF:
   Log.i(MY_TAG, "modul is down");
   break;
  case SmsManager.RESULT_ERROR_NULL_PDU:
   Log.i(MY_TAG, "PDU error");
   break;
  }
 }
 
}

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
 
public class DeliverySms extends BroadcastReceiver {
 
 private final static String MY_TAG = "MyTag";
 
 public void onReceive(Context context, Intent intent) {
  switch(getResultCode()) {
  case Activity.RESULT_OK:
   Log.i(MY_TAG, "SMS delivered");
   break;
  case Activity.RESULT_CANCELED:
   Log.i(MY_TAG, "SMS not delivered");
   break;
  }
 }
 
}

И так что мы здесь накодили =) ... Создали два слушателя (SendSms - будет срабатывать когда смс будет отправленно, DeliverySms - когда смс доходит до получателя). В слушателях нужно переопределить только один метод onReceive, он вызывается тогда когда слушатель получает сообщение. Полученные сообщения можно отсортировать с помощу метода getResultCode(). Есть такие сообщения:

  • Activity.RESULT_OK - операция успешна;
  • Activity.RESULT_CANCELED - операция отмененна;
  • SmsManager.RESULT_ERROR_GENERIC_FAILURE - при отправке возникли неизвестные  проблемы;
  • SmsManager.RESULT_ERROR_RADIO_OFF - телефонный модуль выключен;
  • SmsManager.RESULT_ERROR_NULL_PDU - возникла проблема формата PDU;
Теперь осталось зарегистрировать слушатили в главном активити:

private final static String SENT = "SENT_SMS_ACTION", DELIVERED = "DELIVERED_SMS_ACTION", ISNULL = "Entered, not all data";
 
SendSms sendSms = new SendSms();
DeliverySms deliverySms = new DeliverySms();
registerReceiver(sendSms, new IntentFilter(SENT));
registerReceiver(deliverySms, new IntentFilter(DELIVERED));
 
PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, new Intent(SENT), 0);
PendingIntent delivertPI = PendingIntent.getBroadcast(this, 0, new Intent(DELIVERED), 0);
 
И так, видим один метод и класс которые нам не известны. registerReceiver - регистрирует слушатель, первый параметр сам слушатель, второй параметр интентфильтр передачи (в него передаём текст что бы как то отличать фильтры). PendingIntent - передаётся в другое приложение.
Собственно наверное немножко не понятно, BroadcasReceiver и PendingIntent своеобразные "мосты". BroadcasReceiver слушатель который слушает что получает PendingIntent на всём телефоне, а не только в вашем приложении.
Так же стоит отметить что стоит переопределить метод onStop() главного активити:

    protected void onStop() {
     //когда приложение переходит в ожидание или же закрывается то снимаем с регистрации приёмники
     unregisterReceiver(sendSms);
     unregisterReceiver(deliverySms);
     super.onStop();
    }
как следует из комментария, приёмники следует снять с регистрации, в том числе для экономии памяти.
Далее можете отправить сообщение и посмотреть за результатами.

Отправка несколько сообщений сразу.
Что же делать если сообщение вышло на две и более смс-ки? ведь метод sendTextMessage отправляет только одно сообщение. А вот для этого существует метод sendMultipartTextMessage:

PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, new Intent(SENT), 0);
PendingIntent delivertPI = PendingIntent.getBroadcast(this, 0, new Intent(DELIVERED), 0);
SmsManager smsManager = SmsManager.getDefault();
if(message.length() > 160) {
 ArrayList<String> mArray = smsManager.divideMessage(message);
 ArrayList<PendingIntent> sentArrayIntents = new ArrayList<PendingIntent>();
 for(int i = 0; i < mArray.size(); i++)
  sentArrayIntents.add(sentPI);
 smsManager.sendMultipartTextMessage(phoneNumber, null, mArray, sentArrayIntents, null);

Если наше сообщение больше 160 символов, то разбиваем его на нескольк сообщений. Для этого нам не нужно делать это в ручную, за нас это сделает метод divideMessage в который мы должны передать набранное сообщение. Этот метод возвращает список с сообщениями, который мы потом разбиваем и по очереди добавляем в список слушателей, присоединяя таким способом к каждому слушателю по сообщению. Далее метод sendMultipartTextMessage идентичен sendTextMessage только отличается тем что мы передаём списки.
Теперь можете проверить как это всё работает отправив большое сообщение.

The end...
В конце предоставлю маленькое тестовое приложение:


Скачать можно по ссылкам:
http://uafile.com.ua/get/39754/
http://файлообменник.рф/ewdnmr1e60er.html