0. TL;DR
Для нетерпеливых программистов рабочую версию следующей реализации можно найти на Гитхаб.
Сводя нашу проблему только к концепции подключения, мы можем считать, что:
- Он имеет конечные состояния.
- Он инкапсулирует клиент соединения.
- Это (скорее) быть уникальным.
- Текущее состояние влияет на поведение приложения.
1. Образец состояния
Это поведенческий паттерн, позволяющий объекту изменять свое поведение при изменении его внутреннего состояния. В книге GoF Design Patterns описывается, как TCP-соединение может быть представлен этим шаблоном (что также является нашим случаем).
Состояние из конечного автомата должно быть singleton
, и самым простым способом сделать это в Java было создание Enum
с именем State
следующим образом:
public enum State {
CREATED {
void connect(Connection connection) {
connection.onSignUp();
}
},
OPENING {
void connect(Connection connection) {
connection.onSignIn();
}
},
OPENED {
void disconnect(Connection connection) {
connection.onSignOut();
}
void revoke(Connection connection) {
connection.onRevokeAndSignOut();
}
},
CLOSED {
void connect(Connection connection) {
connection.onSignIn();
}
};
void connect(Connection connection) {}
void disconnect(Connection connection) {}
void revoke(Connection connection) {}
}
Activity
будет взаимодействовать с абстрактным классом Connection
(который содержит контекст) через методы connect()
, disconnect()
и revoke()
. Текущее состояние определяет, как будут вести себя эти методы:
public void connect() {
currentState.connect(this);
}
public void disconnect() {
currentState.disconnect(this);
}
public void revoke() {
currentState.revoke(this);
}
private void changeState(State state) {
currentState = state;
setChanged();
notifyObservers(state);
}
2. Шаблон прокси
Класс GoogleConnection
наследуется от Connection
и инкапсулирует GoogleApiClient
, поэтому он должен предоставлять как ConnectionCallbacks
, так и OnConnectionFailedListener
следующим образом:
@Override
public void onConnected(Bundle connectionHint) {
changeState(State.OPENED);
}
@Override
public void onConnectionSuspended(int cause) {
mGoogleApiClient.connect();
}
@Override
public void onConnectionFailed(ConnectionResult result) {
if (state.equals(State.CLOSED) && result.hasResolution()) {
changeState(State.CREATED);
connectionResult = result;
} else {
connect();
}
}
public void onActivityResult(int resultCode) {
if (resultCode == Activity.RESULT_OK) {
connect();
} else {
changeState(State.CREATED);
}
}
Методы onSignIn()
, onSignUp()
, onSignOut()
и onRevokeAndSignOut
требуются на втором этапе этого объяснения.
public void onSignUp() {
try {
Activity activity = activityWeakReference.get();
changeState(State.OPENING);
connectionResult.startResolutionForResult(activity, REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
changeState(State.CREATED);
mGoogleApiClient.connect();
}
}
public void onSignIn() {
if (!mGoogleApiClient.isConnected() && !mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
public void onSignOut() {
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
mGoogleApiClient.disconnect();
changeState(State.CLOSED);
mGoogleApiClient.connect();
}
public void onRevokeAndSignOut() {
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient);
changeState(State.CLOSED);
mGoogleApiClient = mGoogleApiClientBuilder.build();
mGoogleApiClient.connect();
}
3. Одноэлементный шаблон
Поскольку нет необходимости повторно создавать этот класс повторно, мы предоставляем его как синглтон:
public static Connection getInstance(Activity activity) {
if (null == sConnection) {
sConnection = new GoogleConnection(activity);
}
return sConnection;
}
public void onActivityResult(int result) {
if (result == Activity.RESULT_OK) {
changeState(State.CREATED);
} else {
changeState(State.CLOSED);
}
onSignIn();
}
private GoogleConnection(Activity activity) {
activityWeakReference = new WeakReference<>(activity);
googleApiClientBuilder = new GoogleApiClient
.Builder(activity)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API, Plus.PlusOptions.builder().build())
.addScope(new Scope("email"));
googleApiClient = googleApiClientBuilder.build();
currentState = State.CLOSED;
googleApiClient.connect();
}
4. Наблюдаемый паттерн
Класс Connection
расширяет Java Observable
, поэтому одно или несколько действий могут отслеживать изменения состояния:
@Override
protected void onCreate(Bundle bundle) {
mConnection = GoogleConnection.getInstance(this);
mConnection.addObserver(this);
}
@Override
protected void onDestroy() {
mConnection.deleteObserver(this);
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
if (Connection.REQUEST_CODE == request) {
mConnection.onActivityResult(result);
}
}
@Override
public void update(Observable observable, Object data) {
if (observable == mGoogleConnection) {
// UI/UX magic happens here ;-)
}
}
person
JP Ventura
schedule
30.07.2015