편안한 API 서비스


226

웹 기반 REST API를 호출하는 데 사용할 수있는 서비스를 만들고 싶습니다.

기본적으로 앱 init에서 서비스를 시작한 다음 해당 서비스에 URL을 요청하고 결과를 반환하도록 요청할 수 있기를 원합니다. 그 동안 진행률 창이나 비슷한 것을 표시하고 싶습니다.

나는 현재 IDL을 사용하는 서비스를 만들었습니다. 앱 간 통신을 위해 실제로 필요한 부분을 읽었 으므로이 부분을 제거해야하지만 콜백을 수행하는 방법이 확실하지 않다고 생각하십시오. 또한 post(Config.getURL("login"), values)응용 프로그램을 칠 때 응용 프로그램이 잠시 일시 중지 된 것처럼 보입니다 (이상한 것으로 보입니다-서비스의 아이디어는 다른 스레드에서 실행된다는 생각이었습니다!)

현재 내부에 post 및 get HTTP 메소드가있는 서비스가 있으며, 두 가지 AIDL 파일 (양방향 통신용), 서비스 시작, 중지, 바인딩 등을 처리하는 ServiceManager가 있으며 특정 코드로 처리기를 동적으로 작성하고 있습니다 필요에 따라 콜백

누구든지 나에게 완전한 코드 기반을 제공하기를 원하지 않지만 일부 포인터는 크게 감사하겠습니다.

(주로) 전체 코드 :

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

몇 가지 AIDL 파일 :

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

서비스 관리자 :

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

서비스 초기화 및 바인딩 :

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

서비스 기능 호출 :

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};

5
이것은 Android REST 클라이언트 구현을 배우는 사람들에게 매우 도움이 될 수 있습니다. PDF 파일로 Dobjanschi의 프리젠 테이션 전사 : drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/...
케이 데이빗

많은 사람들이 Virgil Dobjanschi의 프레젠테이션을 추천하고 IO 2010에 대한 링크가 끊어 졌으므로 이제 YT 비디오에 대한 직접 링크는 다음과 같습니다. youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

답변:


283

서비스가 응용 프로그램의 일부가 될 경우 필요 이상으로 복잡해집니다. RESTful 웹 서비스에서 일부 데이터를 가져 오는 간단한 유스 케이스가 있으므로 ResultReceiverIntentService조사 해야합니다 .

이 Service + ResultReceiver 패턴은 조치를 수행하려고 할 때 startService () 를 사용하여 서비스를 시작하거나 바인딩하여 작동합니다. 인 텐트의 추가 항목을 통해 ResultReceiver (활동)를 수행하고 전달할 조작을 지정할 수 있습니다.

서비스에서 onHandleIntent 를 구현 하여 Intent에 지정된 작업을 수행합니다. 작업이 완료되면 전달 된 ResultReceiver를 사용하여 onReceiveResult 가 호출 될 활동에 메시지를 다시 보냅니다 .

예를 들어 웹 서비스에서 일부 데이터를 가져 오려고합니다.

  1. 의도를 작성하고 startService를 호출하십시오.
  2. 서비스의 작업이 시작되고 시작되었다는 메시지를 활동에 보냅니다.
  3. 활동은 메시지를 처리하고 진행 상황을 보여줍니다.
  4. 서비스가 작업을 마치고 일부 데이터를 활동으로 다시 보냅니다.
  5. 활동이 데이터를 처리하고 목록보기에 넣습니다.
  6. 서비스는 완료되었다는 메시지를 보내며 자체적으로 종료됩니다.
  7. 활동은 완료 메시지를 받고 진행 대화 상자를 숨 깁니다.

코드 기반을 원하지 않는다고 언급했지만 오픈 소스 Google I / O 2010 앱은 내가 설명하는 방식으로 서비스를 사용합니다.

샘플 코드를 추가하도록 업데이트되었습니다.

활동.

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

서비스:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

ResultReceiver 확장-MyResultReceiver.Receiver를 구현하기 위해 편집 됨

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}

1
훌륭 해요, 고맙습니다! 나는 Google의 iosched 응용 프로그램을 살펴 보았습니다. 아주 복잡하지만 작동하는 것이 있습니다. 이제 왜 작동하는지 알아야합니다! 그러나 그렇습니다. 이것은 내가 일하고있는 기본 패턴입니다. 대단히 감사합니다.
Martyn

29
대답에 하나의 작은 추가 사항 : 당신이하는 것처럼 mReceiver.setReceiver (null); onPause 메소드에서 mReceiver.setReceiver (this)를 수행해야합니다. onResume 메소드에서. 그렇지 않으면 활동을 다시 만들지 않고 재개하면 이벤트를받지 못할 수도 있습니다.
Vincent Mimoun-Prat

7
문서에서 IntentService가 자동으로 stopSelf를 호출 할 필요가 없다고 말하지 않습니까?
Mikael Ohlson 2018 년

2
@MikaelOhlson 맞습니다. 서브 클래스를 호출 하면 호출 하지 말아야 합니다. 그렇게하면 보류중인 요청이 잃어 버리기 때문 입니다. stopSelfIntentServiceIntentService
quietmint

1
IntentService작업이 완료되면 자체적으로 종료되므로 this.stopSelf()불필요합니다.
Euporie 2016 년

17

Android REST 클라이언트 애플리케이션을 개발 하는 것은 훌륭한 리소스였습니다. 스피커는 코드를 보여주지 않고 안드로이드에서 견고한 Rest Api를 구성하는 디자인 고려 사항과 기술을 거칩니다. 귀하의 팟 캐스트가 다소의 사람인지 아닌지, 나는 적어도 하나의 청취를 제공하는 것이 좋지만, 개인적으로 지금까지 4 ~ 5 번 들었으므로 아마 다시들을 것입니다.

Android REST 클라이언트 애플리케이션 개발
작성자 : Virgil Dobjanschi
설명 :

이 세션에서는 Android 플랫폼에서 RESTful 애플리케이션을 개발하기위한 아키텍처 고려 사항을 제시합니다. Android 플랫폼과 관련된 디자인 패턴, 플랫폼 통합 및 성능 문제에 중점을 둡니다.

그리고 제 API의 첫 번째 버전에서 실제로 고려하지 않은 많은 고려 사항이 있습니다.


4
+1 여기에는 처음 시작할 때 생각하지 못한 종류의 고려 사항이 포함됩니다.
Thomas Ahle

예, Rest 클라이언트를 개발하기위한 첫 번째 시도는 정확히하지 말아야 할 것에 대한 그의 설명이었습니다. 나는 그것에 약간 덩어리.
Terrance

이 비디오를 두 번 이상 보았고 두 번째 패턴을 구현하고 있습니다. 내 문제는 복잡한 데이터베이스 모델에서 트랜잭션을 사용하여 서버에서 오는 새로운 데이터에서 로컬 데이터를 업데이트해야하며 ContentProvider 인터페이스가 나에게 방법을 제공하지 않는다는 것입니다. 당신은 어떤 제안이 있습니까, Terrance?
Flávio Faria

2
NB : HttpClient에 대한 Dobjanschi의 의견이 더 이상 유지되지 않습니다. 참조 stackoverflow.com/a/15524143/939250
DONAL 래퍼 티

예, 이제 HttpURLConnection 이 선호됩니다. 또한 Android 6.0이 릴리스되면서 Apache HTTP 클라이언트에 대한 지원이 공식적으로 제거되었습니다 .
RonR

16

또한 post (Config.getURL ( "login"), values)를 눌렀을 때 앱이 잠시 일시 중지 된 것 같습니다.

스레드를 직접 만들 필요는 없습니다 . 로컬 서비스는 기본적으로 UI 스레드에서 실행됩니다.




5

모든 기능을 통합 한 독립형 클래스의 방향으로 모든 것을 지시하고 싶었습니다.

http://github.com/StlTenny/RestService

요청을 비 블로킹으로 실행하고 구현하기 쉬운 핸들러로 결과를 반환합니다. 예제 구현도 제공됩니다.


4

버튼의 이벤트-onItemClicked ()에서 서비스를 시작하려고한다고 가정 해 보겠습니다. 수신자 메커니즘 때문에이 경우에하지 작업 : -
onItemClicked에서 () 의도 추가와 같이 나는 서비스에 수신기를 통과) ()
b)는 배경 작업으로 이동합니다. onPause ()에서 Activity 누수를 피하기 위해 ResultReceiver 내의 수신자 참조를 null로 설정했습니다.
c) 활동이 파괴된다.
d) 활동이 다시 작성됩니다. 그러나이 시점에서 서비스는 수신자 참조가 유실되어 활동에 대한 콜백을 수행 할 수 없습니다.
제한된 방송 또는 PendingIntent의 메커니즘은 이러한 시나리오에서 더 유용 합니다. 서비스에서 활동 알림을 참조하십시오.


1
당신이 말하는 것에 문제가 있습니다. 즉, 활동이 백그라운드로 이동할 때 소멸되지 않습니다. 따라서 수신자가 여전히 존재하고 활동 컨텍스트도 있습니다.
DArkO

@DArkO 활동이 일시 정지 또는 중지되면 메모리 부족 상황에서 Android 시스템에 의해 종료 될 수 있습니다. 활동 수명주기를 참조하십시오 .
jk7

4

Robby Pond의 솔루션에는 다소 부족한 점이 있습니다. IntentService는 한 번에 하나의 의도 만 처리하므로 한 번에 하나의 API 호출 만 허용합니다. 종종 병렬 API 호출을 수행하려고합니다. 이 작업을 수행하려면 IntentService 대신 Service를 확장하고 자체 스레드를 만들어야합니다.


1
당신은 여전히 집행 스레드 서비스에 웹 서비스 API 호출을 위임하여 IntentService에서 여러 통화를 할 수있는, 당신의 IntentService 파생 클래스의 멤버 변수로 존재
Viren

2

또한 post (Config.getURL ( "login"), values)를 눌렀을 때 앱이 잠시 일시 중지 된 것 같습니다.

이 경우 다른 스레드에서 실행되고 완료되면 ui 스레드로 결과를 반환하는 asynctask를 사용하는 것이 좋습니다.


2

여기에는 기본적으로 요청의 전체 관리를 잊어 버리는 데 도움이되는 또 다른 접근법이 있습니다. 비동기 대기열 방법과 콜 러블 / 콜백 기반 응답을 기반으로합니다. 주요 이점은이 방법을 사용하면 전체 프로세스 (요청, 가져 오기 및 구문 분석, save와 db)를 완전히 투명하게 만들 수 있다는 것입니다. 응답 코드를 받으면 작업이 이미 완료된 것입니다. 그 후 당신은 당신의 DB를 호출해야하고 당신은 끝났습니다. 활동이 활성화되지 않은 경우 발생하는 문제에 대한 도움을줍니다. 여기서 일어날 모든 데이터는 로컬 데이터베이스에 저장되지만 응답은 활동에 의해 처리되지 않으므로 이상적인 방법입니다.

나는 여기에 일반적인 접근 방식에 대해 썼습니다 : http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

앞으로의 게시물에 특정 샘플 코드를 넣을 것입니다. 그것이 도움이되기를 바랍니다. 접근법을 공유하고 잠재적 인 의심이나 문제를 해결하기 위해 저에게 연락하십시오.


연결이 끊어졌습니다. 도메인이 만료되었습니다.
jk7

1

Robby는 훌륭한 답변을 제공하지만 여전히 더 많은 정보를 찾고 있음을 알 수 있습니다. REST API 호출을 쉬운 BUT 잘못된 방법으로 구현했습니다. 내가 잘못한 부분을 이해 한이 Google I / O 비디오 를 볼 때까지는 아니 었 습니다. AsyncTask를 HttpUrlConnection get / put 호출과 결합하는 것만 큼 간단하지 않습니다.


연결이 끊어졌습니다. 업데이트 된 Google I / O 2010-Android REST 클라이언트 애플리케이션
zim
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.