Опыт применения http запросов в реальном j2me приложении
Около 2-х лет назад решил попробовать себя в разработке приложений для телефонов. А именно — на j2me.
Практически во всех задачах требовалось обеспечить обмен информацией между мобильным приложением и сервером по http. Написав пару — тройку приложений, решил оформить код работы с соединением в виде отдельных классов, которые использую в своих текущих проектах.
Итак, при программировании обмена информацией между мобильным приложением и сервером каждому программисту приходится решать несколько задач
1. Создание потока соединения и обмен данными между интерфейсом пользователя и этим потоком.
2. Обеспечение реакции потока на обрыв связи или действия пользователя — например отмену соединения
3. Парсинг ответа и обновление интерфейса в зависимости от содержимого ответа.
Ниже приведен код с комментариями. Думаю он будет полезен для начинающих проограммистов, поскольку приходилось видеть довольно кривые примеры реализации описанного функционала. Хотя, не могу сказать, что меня самого до конца устраивает то, что я написал. Но по крайней мере данный код был неоднократно проверен на практике.
Замечание: в коде используются ссылки на классы из библиотеки LWUIT.
Итак поехали.
Класс Sender. Создает соединение, посылает запрос и возвращает ответ
Интерфейс ResponseHandler — занимается парсингом ответа
Класс SimpleResponseHandler — реализация интерфейса ResponseHandler
Интерфейс ConnectedListener — сигнализирует об успешном выполнении запроса
Класс WatchSheduler — реализует ConnectedListener. Представляет собой шедулер, который стартует с заданной задержкой и проверяет был ли сигнал от соединения. Если сигнала не было, убивает поток соединения.
Интерфейс AbortConnection — содержит методы для получения и остановки потока.
Класс AbortConnectionAdapter реализует AbortConnection а также обеспечивает показ сплэш-формы на время соединения и реакцию на отмену соединения пользователем.
Как это все работает.
Сначала пропишем интерфейсы
Реализация интерфейсов
Забыл упомянуть UIContext — по сути это класс основного потока сообщений, то бишь EDT
Его функция здесь — вывод сообщения об ошибке (или иное действие в интерфейсе пользователя).
В качестве формата данных выбран json, но может быть и любой другой формат, все зависит от реализации
Далее, дадим юзеру возможность убить подвисшее соединение, заодно добавим красоты, чтобы было видно, что выполняется запрос.
Сплэш скрин примитивный — просто формочка с текстом и одной командой «отмена».
Далее можно наследовать этот класс, например если захочется переопределить метод
Проверка того, что на наш запрос к серверу пришел ответ
Теперь, собственно, класс соединения
В класс
в который передается адрес и данные. Сразу же после отправки запроса запускается слежение за состоянием потока
И, после получения ответа от сервера вызывается
В случае, если ответа от сервера нет, а
Итак, поток соединения может завершиться нормально и произойдут действия, прописанные в парсере ответа. Поток может быть отрублен автоматически по истечении времени ожидания или его может остановить пользователь.
Небольшой пример использования в коде, выполнение авторизации
Практически во всех задачах требовалось обеспечить обмен информацией между мобильным приложением и сервером по http. Написав пару — тройку приложений, решил оформить код работы с соединением в виде отдельных классов, которые использую в своих текущих проектах.
Итак, при программировании обмена информацией между мобильным приложением и сервером каждому программисту приходится решать несколько задач
1. Создание потока соединения и обмен данными между интерфейсом пользователя и этим потоком.
2. Обеспечение реакции потока на обрыв связи или действия пользователя — например отмену соединения
3. Парсинг ответа и обновление интерфейса в зависимости от содержимого ответа.
Ниже приведен код с комментариями. Думаю он будет полезен для начинающих проограммистов, поскольку приходилось видеть довольно кривые примеры реализации описанного функционала. Хотя, не могу сказать, что меня самого до конца устраивает то, что я написал. Но по крайней мере данный код был неоднократно проверен на практике.
Замечание: в коде используются ссылки на классы из библиотеки LWUIT.
Итак поехали.
Класс Sender. Создает соединение, посылает запрос и возвращает ответ
Интерфейс ResponseHandler — занимается парсингом ответа
Класс SimpleResponseHandler — реализация интерфейса ResponseHandler
Интерфейс ConnectedListener — сигнализирует об успешном выполнении запроса
Класс WatchSheduler — реализует ConnectedListener. Представляет собой шедулер, который стартует с заданной задержкой и проверяет был ли сигнал от соединения. Если сигнала не было, убивает поток соединения.
Интерфейс AbortConnection — содержит методы для получения и остановки потока.
Класс AbortConnectionAdapter реализует AbortConnection а также обеспечивает показ сплэш-формы на время соединения и реакцию на отмену соединения пользователем.
Как это все работает.
Сначала пропишем интерфейсы
public interface AbortConnection {
public void cancel();
public void showSplashForm();
public void setAbortedThread(Thread t);
public Thread getAbortedThread();
}
public interface ConnectListener {
public void connected();
public void start();
}
public interface ResponseHandler {
void handleResponse(byte[] response);
}
Реализация интерфейсов
public class AbortConnectionAdapter implements AbortConnection{
Thread abortedThread;
public void cancel() {
abortedThread.interrupt();
}
public void showSplashForm() {}
public void setAbortedThread(Thread t) {
abortedThread= t;
}
public Thread getAbortedThread() {
return abortedThread;
}
}
abstract class SimpleResponseHandler implements ResponseHandler {
UIContext context;
SimpleResponseHandler(UIContext context){
this.context= context;
}
public void handleResponse(byte[] response) {
final String s= new String(response);
new Thread(new Runnable(){
public void run() {
try{
final JSONObject json= new JSONObject(s);
handleSpecInfo(json);
}
catch (JSONException e) {
context.showMessage("Ошибка блин!");
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}).start();
}
abstract public void handleSpecInfo(JSONObject json) throws UnsupportedEncodingException;
}
Забыл упомянуть UIContext — по сути это класс основного потока сообщений, то бишь EDT
Его функция здесь — вывод сообщения об ошибке (или иное действие в интерфейсе пользователя).
В качестве формата данных выбран json, но может быть и любой другой формат, все зависит от реализации
handleSpecInfo
Далее, дадим юзеру возможность убить подвисшее соединение, заодно добавим красоты, чтобы было видно, что выполняется запрос.
class Splash extends AbortConnectionAdapter{
Form splashform;
Splash(String splashText){
splashform= new Form();
splashform.refreshTheme();
TextArea ta= new TextArea(splashText);
ta.setAlignment(Form.CENTER);
ta.setEditable(false);
splashform.setLayout(new BorderLayout());
splashform.addComponent(BorderLayout.CENTER, ta);
splashform.addCommand(new Command("Отмена", 0));
splashform.addCommandListener(new ActionListener(){
public void actionPerformed(ActionEvent actionEvent) {
cancel();
// изменения в интерфейсе, например показ другой формы
}
});
}
public void showSplashForm() {
splashform.show();
}
}
Сплэш скрин примитивный — просто формочка с текстом и одной командой «отмена».
Далее можно наследовать этот класс, например если захочется переопределить метод
showSplashForm
, или делать что-то дополнительно в методе cancel
Проверка того, что на наш запрос к серверу пришел ответ
public class WatchSheduler implements ConnectListener{
UIContext context;
Timer timer;
TimerTask task;
AbortConnection abortConnection;
final int watchdelay= 45000;
boolean isConnected= false;
public WatchSheduler(UIContext context, AbortConnection abortConnection) {
this.context = context;
this.abortConnection= abortConnection;
}
public void start(){
task= new TimerTask() {
public void run() {
if(!isConnected){
Display.getInstance().callSerially(new Runnable(){
public void run() {
if(abortConnection!= null)
abortConnection.cancel();
}
});
}
}
};
timer= new Timer();
timer.schedule(task, watchdelay);
}
public void connected() {
this.isConnected= true;
}
}
Теперь, собственно, класс соединения
public class Sender {
UIContext context;
String address;
ResponseHandler handler;
HttpRequest request;
static Vector activeThreads= new Vector();
ConnectListener connectListener;
AbortConnection abortConnection;
Sender(UIContext context, ResponseHandler handler, AbortConnection abortConnection){
this.context= context;
this.handler= handler;
this.abortConnection= abortConnection;
}
private void send(final HttpRequest request){
try {
abortConnection.setAbortedThread(Thread.currentThread());
connectListener= new WatchSheduler(context, abortConnection);
connectListener.start();
request.send();
byte[] response= request.getResponse();
connectListener.connected();
if(handler!= null && response!= null){
handler.handleResponse(response);
}
} catch (IOException e) {
e.printStackTrace();
context.showMessage("Ошибка раз: "+ e.getMessage());
} catch (SecurityException e) {
e.printStackTrace();
context.showMessage("Ошибка два: "+ e.getMessage());
context.exit();
}catch(OutOfMemoryError e){
e.printStackTrace();
context.showMessage("Ошибка три: "+ e.getMessage());
}catch(NullPointerException e){
e.printStackTrace();
context.showMessage("ошибка 4 "+ e.getMessage());
}
finally{
activeThreads.removeElement(Thread.currentThread());
}
}
void executeGet(String address, Hashtable map){
if(abortConnection!= null)
abortConnection.showSplashForm();
this.address= address;
request= new HttpGetRequest(map);
Thread thread= new Thread(new Runnable(){
public void run() {
send(request);
}
}, "get");
activeThreads.addElement(thread);
thread.start();
}
void executePost(String address, Hashtable map){
if(abortConnection!= null)
abortConnection.showSplashForm();
this.address= address;
request= new HttpPostRequest(map);
Thread thread= new Thread(new Runnable(){
public void run() {
send(request);
}
}, "post");
activeThreads.addElement(thread);
thread.start();
}
abstract private class HttpRequest{
HttpConnection connection;
Hashtable map;
HttpRequest(Hashtable map) {
this.map= map;
}
abstract void send() throws IOException, NullPointerException;
byte[] getResponse() throws IOException {
InputStream is= null;
ByteArrayOutputStream baos= null;
byte[] response= null;
int rc= 0;
try{
rc = connection.getResponseCode();
if(rc!= 200)
throw new IOException("Какой-то код "+ rc);
if(handler!= null){
is= connection.openInputStream();
int c;
baos = new ByteArrayOutputStream();
while( (c= is.read())!=-1 )
baos.write( c );
response= baos.toByteArray();
}
}catch (IOException e){
try{
if(connection!= null) connection.close();
if(is!= null) is.close();
if(baos!= null) baos.close();
}catch (Exception ex){
ex.printStackTrace();
}
if(rc!= 200)
throw new IOException("Перенаправляем эксепшн наверх = "+ rc);
}finally{
try{
if(connection!= null) connection.close();
if(is!= null) is.close();
if(baos!= null) baos.close();
}catch(Exception e){
e.printStackTrace();
}
}
return response;
}
}
private class HttpPostRequest extends HttpRequest{
HttpPostRequest(Hashtable map) {
super(map);
}
void send() throws IOException, NullPointerException{
OutputStream os= null;
try{
connection = (HttpConnection) Connector.open(address, Connector.READ_WRITE);
connection.setRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.1");
connection.setRequestMethod(HttpConnection.POST);
JSONObject json= new JSONObject();
if(map!= null){
Enumeration names= map.keys();
while(names.hasMoreElements()){
String key= (String)names.nextElement();
String s= JavaEncoder.codeData((String)map.get(key));
json.put(key, s);
}
String jsonstring= json.toString();
connection.setRequestProperty("Content-Length", String.valueOf(jsonstring.length()));
os = connection.openOutputStream();
os.write(jsonstring.getBytes());
}
}catch (IOException e){
try{
if (os!= null) os.close();
}catch(IOException ee){
ee.printStackTrace();
}
throw e;
} catch (JSONException e) {
e.printStackTrace();
}
}
}
private class HttpGetRequest extends HttpRequest{
HttpGetRequest(Hashtable map) {
super(map);
}
void send() throws IOException, NullPointerException {
try{
String res= address+ "/?";
if(map!= null){
Enumeration e= map.keys();
while(e.hasMoreElements()){
String key= (String)e.nextElement();
String s= JavaEncoder.codeData((String)map.get(key));
res+=key+ "="+ s;
res+= "&";
}
res= res.substring(0, res.length()-1);
}
connection = (HttpConnection) Connector.open(res, Connector.READ_WRITE);
connection.setRequestMethod(HttpConnection.GET);
connection.setRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.1");
}catch (IOException e){
throw new IOException("HttpGetRequest");
}
}
}
static void dispose(){
for(int i=0; i< activeThreads.size(); i++){
Thread t= (Thread)activeThreads.elementAt(i);
if(t.isAlive()){
t.interrupt();
}
}
}
}
В класс
Sender
при создании передаются экземпляры парсера и убийцы потока. Далее в основном коде создается Hashtable
и заполняется параметрами запроса, т.е. параметр-значение. Затем просто вызывается executeGet()
или executePost()
,в который передается адрес и данные. Сразу же после отправки запроса запускается слежение за состоянием потока
connectListener= new WatchSheduler(context, abortConnection);
connectListener.start();
И, после получения ответа от сервера вызывается
connectListener.connected();
В случае, если ответа от сервера нет, а
WatchSheduler
запустил свой поток, то в нем произойдет вызов abortConnection.cancel()
и поток соединения завершится. После чего, если мы используем не AbortConnectionAdapter а его наследника, могут быть выполнены дополнительные действия в пользовательском интерфейсе. Итак, поток соединения может завершиться нормально и произойдут действия, прописанные в парсере ответа. Поток может быть отрублен автоматически по истечении времени ожидания или его может остановить пользователь.
Небольшой пример использования в коде, выполнение авторизации
void authorize(){
Sender sender= new Sender(this, new SimpleResponseHandler(this){
public void handleSpecInfo(JSONObject json) {
String id= json.optString("id");
if(id!= null){
Driver.ID= id;
driver.store();
createMainForm();
}
}
}, new Splash("Авторизация..."){
public void cancel() {
super.cancel();
createLoginForm();
}
});
Hashtable hashtable= new Hashtable();
hashtable.put("user", driver.login);
hashtable.put("pass", driver.pass);
sender.executePost(LOGIN_ADDRESS, hashtable);
}
0 комментариев