我正在构建一个增强现实应用程序。 AR的基类是一个FrameLayout,其中包含用于存储POI和用于翻新以从Web获取数据的传感器数据,位置数据,房间(尚未实现)的类。
使用RoomModel或翻新来实现MVVM并没有复杂性,该模型是使用获取LiveData的ViewModel类内部的存储库>,这是一种存储库模式,但我无法弄清楚如何解决需要生命周期方法(例如onResume())的类,onPause()和onDestroy()还是我需要将MVVM与生命周期所需的类一起使用。
注意:我尚未实施Room and Retrofit。而且LifeObserver界面将在最终版本中更改用于ARView的LifeCycleObserver,我也使用Eclipse测试了相同的代码,并且它没有arch类。
public class ARView extends FrameLayout implements LifeObserver {
/**
* Camera Preview that contains layout SurfaceView / TextureView
*/
private PreviewImpl mPreview;
/**
* Camera1 or Camera2 Api for retrieving and controlling Camera
*/
private CameraImpl mCameraProvider;
/**
* Sensor Controller manages all sensor registering, retrieving, filterig and returning sensor data.
*/
private ISensorController mSensorController;
/**
* Location Provider retrieves current location and location details
*/
private ILocationProvider mLocationProvider;
/**
* Surface for drawing app's required drawings and other things. This SurfaceView runs on a separate
* thread and joins on onPause method
*/
private BaseDrawSurfaceView mDrawSurface;
/**
* Objects that implement LifeObserver to implement onResume, onPause and onDestroy methods
* Camera, SensorController, Drawing Surface, Location and POIs have life cycles
*/
private List<LifeObserver> mLifeObserverList;
public ARView(Context context) {
super(context);
init();
}
public ARView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mLifeObserverList = new ArrayList<>();
// TODO Choose SurfaceView / TextureView
mPreview = new SurfaceViewPreview(getContext());
addView(mPreview.getView());
// TODO Choose Camera1 / Camera2 Api
mCameraProvider = new Camera1(getContext(), mPreview);
addLifeObserver(mCameraProvider);
}
@Override
public void onResume() {
System.out.println("ARView onResume()");
Toast.makeText(getContext(), "ARView onResume()", Toast.LENGTH_SHORT).show();
for (LifeObserver lifeObserver : mLifeObserverList) {
lifeObserver.onResume();
}
}
@Override
public void onPause() {
for (LifeObserver lifeObserver : mLifeObserverList) {
lifeObserver.onPause();
}
}
@Override
public void onDestroy() {
for (LifeObserver lifeObserver : mLifeObserverList) {
lifeObserver.onDestroy();
}
mLifeObserverList.clear();
mLifeObserverList = null;
removeAllViews();
System.out.println("ARView onDestroy()");
Toast.makeText(getContext(), "ARView onDestroy()", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
System.out.println("ARView onDetachedFromWindow()");
Toast.makeText(getContext(), "ARView onDetachedFromWindow()", Toast.LENGTH_SHORT).show();
}
/**
* Adds a child view
*
* @param child the child view to add
*/
public void addChildView(View child) {
addView(child);
if (child instanceof LifeObserver) {
addLifeObserver((LifeObserver) child);
}
}
/**
* Add an object that implements onResume, onPause, and onDestroy methods via LifeObserver interface
*
* @param lifeObserver implements LifeObserver interface
*/
public void addLifeObserver(LifeObserver lifeObserver) {
if (lifeObserver != null && mLifeObserverList != null
&& !mLifeObserverList.contains(lifeObserver))
mLifeObserverList.add(lifeObserver);
}
/**
* Add base drawing layer that has access to location and sensor data. This drawing surface is
* base drawing layer and also used to merge camera data with drawing data
*
* @param drawingSurfaceView drawing SurfaceView to draw sensor and / or location data above camera preview
*/
public void addBaseDrawView(BaseDrawSurfaceView drawingSurfaceView) {
mDrawSurface = drawingSurfaceView;
addChildView(drawingSurfaceView);
drawingSurfaceView.setZOrderMediaOverlay(true);
if (mCameraProvider != null) {
mCameraProvider.setImageMergeCallback(drawingSurfaceView);
}
}
/**
* Add extra drawing surfaces over camera preview. Each drawing surface run on it's separate thread. For instance,
* Focus Manager layout is one of the drawing surfaces for drawing focusing and metering area on camera view.
*
* @param drawSurfaceView surface that is instance of SurfaceView that runs on a separate thread
* @param imageMerge if set to true camera data is merged with drawing surface
*/
public void addDrawSurface(BaseDrawSurfaceView drawSurfaceView, boolean imageMerge) {
addChildView(drawSurfaceView);
drawSurfaceView.setZOrderMediaOverlay(true);
if (imageMerge && mCameraProvider != null) {
mCameraProvider.setImageMergeCallback(drawSurfaceView);
}
}
public void setPreviewSize(int width, int height) {
mCameraProvider.setPreviewSize(width, height);
CameraImpl.Size size = mCameraProvider.getPreviewSize();
}
/**
* Get Camera implementation of this view. Camera1 Api or Camera2 api might be returned
*
* @return camera provider that manages camera framework features
*/
public CameraImpl getCameraProvider() {
return mCameraProvider;
}
/**
* Set Camera and Preview implementations
*/
public void setCameraAndPreview() {
if (mPreview == null) {
mPreview = new SurfaceViewPreview(getContext());
addView(mPreview.getView());
// TODO Choose Camera1 / Camera2 Api
mCameraProvider = new Camera1(getContext(), mPreview);
addLifeObserver(mCameraProvider);
}
}
/**
* Get preview surface used by the camera api.
*
* @return might return a SurfaceView or TextureView
*/
public PreviewImpl getPreview() {
return mPreview;
}
/**
* Get location provider interface used for retrieving current location
*
* @return location provider to access current location
*/
public ILocationProvider getLocationProvider() {
return mLocationProvider;
}
public void setLocationProvider(ILocationProvider locationProvider) {
mLocationProvider = locationProvider;
addLifeObserver(locationProvider);
if (mDrawSurface != null) {
mDrawSurface.setLocationData(mLocationProvider.getLocationData());
}
if (mCameraProvider != null) {
mCameraProvider.setLocationData(mLocationProvider.getLocationData());
}
}
/**
* Get interface responsible of registering and computing sensor data
*
* @return sensor interface for sensor output data
*/
public ISensorController getSensorController() {
return mSensorController;
}
public void setSensorController(ISensorController sensorController) {
mSensorController = sensorController;
addLifeObserver(sensorController);
if (mDrawSurface != null) {
mDrawSurface.setSensorData(mSensorController.getSensorData());
}
}
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (mLocationProvider != null) {
mLocationProvider.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mLocationProvider != null) {
mLocationProvider.onActivityResult(requestCode, resultCode, data);
}
}
}
对于图形视图,传感器和位置数据类,图形需要LifeObserver接口。我将ARView的界面更改为LifeCycleObserver,以使其生命周期由AppcompatActivity处理。
我没有将ARView的任何其他具体类公开给任何Activity或Fragment。我打算将其发布为一个库,供希望构建简单的AR应用程序的开发人员使用。
public interface LifeObserver {
void onResume();
void onPause();
void onDestroy();
}
传感器接口
public interface ISensorController extends SensorEventListener, LifeObserver {
void useSeparateThread(boolean useThread);
void addSensor(Sensor sensor);
void remapCoordinateSystem(int x, int y);
boolean hasRotationVector();
boolean hasGravitySensor();
boolean hasMagneticFieldSensor();
void setSensorDelay(int delay);
void computeCompassValues(boolean compute);
void computeAccelerationValues(boolean compute);
void computeAmbientValues(boolean compute);
SensorData getSensorData();
void setOnSensorChangeListener(OnSensorChangeListener listener);
}
位置界面
public interface ILocationProvider extends LifeObserver {
void setInterval(long milis);
void setPriority(int priority);
void setSmallestDisplacement(float displacement);
Location getLocation();
LocationData getLocationData();
/**
* Method for checking device location settings result
*
* @param requestCode for changing device location settings
* @param resultCode result of location settings change request
* @param data contains details
*/
void onActivityResult(int requestCode, int resultCode, Intent data);
/**
* Method for checking ACCESS_FINE_LOCATION permission request result
*
* @param requestCode is for checking location permission
* @param permissions permissions supposed to contain ACCESS_FINE_LOCATION for this method
* @param grantResults permission resuls
*/
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults);
}
我还发布了具体的位置类,以供您查看是否可以在Activity中而不是在单独的类中实现。如果您对位置详细信息不感兴趣,则可以忽略此部分。
我的问题是我应该如何使用ViewModel处理生活类?
public class FusedLocationManager implements ILocationProvider {
private static final String TAG = FusedLocationManager.class.getName();
/**
* Constant used in the location settings dialog.
*/
private static final int REQUEST_CHECK_SETTINGS = 0x1;
/**
* Provides access to the Fused Location Provider API.
*/
private FusedLocationProviderClient mFusedLocationClient;
/**
* Provides access to the Location Settings API.
*/
private SettingsClient mSettingsClient;
/**
* Stores parameters for requests to the FusedLocationProviderApi.
*/
private LocationRequest mLocationRequest;
/**
* Stores the types of location services the client is interested in using. Used for checking
* settings to determine if the device has optimal location settings.
*/
private LocationSettingsRequest mLocationSettingsRequest;
/**
* Callback for Location events.
*/
private LocationCallback mLocationCallback;
/*
* Location Request Properties
*/
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 60000;
// The fastest rate for active location updates. Exact. Updates will never
// be more frequent than this value.
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2;
private long updateIntervalInMs = UPDATE_INTERVAL_IN_MILLISECONDS;
private long fastestUpdateIntervalInMs = FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS;
private int priority = LocationRequest.PRIORITY_HIGH_ACCURACY;
private float smallestDisplacement = 0;
/**
* Represents a geographical location.
*/
private Location mCurrentLocation;
private boolean mRequestingLocationUpdates;
private HandlerThread mHandlerThread = null;
private Activity mActivity;
private LocationData mLocationData;
private Geocoder geocoder;
private PrefManager mPrefManager;
public FusedLocationManager(Activity activity) {
mActivity = new WeakReference<>(activity).get();
mPrefManager = new PrefManager(mActivity.getApplicationContext());
mRequestingLocationUpdates = true;
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(activity);
mSettingsClient = LocationServices.getSettingsClient(activity);
mLocationData = new LocationData();
geocoder = new Geocoder(activity, Locale.getDefault());
// Kick off the process of building the LocationCallback, LocationRequest, and
// LocationSettingsRequest objects.
createLocationCallback();
createLocationRequest();
buildLocationSettingsRequest();
}
/**
* Sets up the location request. Android has two location request settings:
* {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control
* the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
* the AndroidManifest.xml.
* <p/>
* When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
* interval (5 seconds), the Fused Location Provider API returns location updates that are
* accurate to within a few feet.
* <p/>
* These settings are appropriate for mapping applications that show real-time location
* updates.
*/
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
setLocationRequestProperties();
}
private void getLocationSettings() {
String updateIntervalPref = mPrefManager.getString(PrefKeys.KEY_LOCATION_UPDATE_INTERVAL, "60000");
String priorityPref = mPrefManager.getString(PrefKeys.KEY_LOCATION_PRIORITY, String.valueOf(LocationRequest.PRIORITY_HIGH_ACCURACY));
String smallestDisplacementPref = mPrefManager.getString(PrefKeys.KEY_LOCATION_SMALLEST_DISPLACEMENT, String.valueOf(smallestDisplacement));
try {
updateIntervalInMs = Long.parseLong(updateIntervalPref);
fastestUpdateIntervalInMs = updateIntervalInMs / 2;
priority = Integer.parseInt(priorityPref);
smallestDisplacement = Float.parseFloat(smallestDisplacementPref);
} catch (NumberFormatException e) {
e.printStackTrace();
updateIntervalInMs = 0;
fastestUpdateIntervalInMs = updateIntervalInMs / 2;
priority = LocationRequest.PRIORITY_HIGH_ACCURACY;
smallestDisplacement = 0;
}
System.out.println("FusedLocationManager getLocationSettings() updateIntervalInMs: " + updateIntervalInMs + ", request update: " + mRequestingLocationUpdates);
}
private void setLocationRequestProperties() {
mLocationRequest.setInterval(updateIntervalInMs);
mLocationRequest.setFastestInterval(fastestUpdateIntervalInMs);
mLocationRequest.setPriority(priority);
if (smallestDisplacement != 0) {
mLocationRequest.setSmallestDisplacement(smallestDisplacement);
}
}
/**
* Creates a callback for receiving location events.
*/
private void createLocationCallback() {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(final LocationResult locationResult) {
super.onLocationResult(locationResult);
if (locationResult.getLastLocation() != null) {
mCurrentLocation = locationResult.getLastLocation();
mLocationData.setLocation(mCurrentLocation);
fetchAddress(mCurrentLocation);
}
}
};
}
/**
* Uses a {@link com.google.android.gms.location.LocationSettingsRequest.Builder} to build
* a {@link com.google.android.gms.location.LocationSettingsRequest} that is used for checking
* if a device has the needed location settings.
*/
private void buildLocationSettingsRequest() {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// Check for the integer request code originally supplied to startResolutionForResult().
case REQUEST_CHECK_SETTINGS:
switch (resultCode) {
case Activity.RESULT_OK:
// Log.i(TAG, "User agreed to make required location settings changes.");
// Nothing to do. startLocationupdates() gets called in onResume again.
break;
case Activity.RESULT_CANCELED:
// Log.i(TAG, "User chose not to make required location settings changes.");
mRequestingLocationUpdates = false;
break;
}
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
}
/**
* Requests location updates from the FusedLocationApi. Note: we don't call this unless location
* runtime permission has been granted.
*/
private void startLocationUpdates() {
if (mHandlerThread == null) {
mHandlerThread = new HandlerThread("Location Thread");
}
System.out.println("FusedLocationManager startLocationUpdates()");
// Begin by checking if the device has the necessary location settings.
mSettingsClient.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(mActivity, new OnSuccessListener<LocationSettingsResponse>() {
@SuppressLint("MissingPermission")
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
if (mHandlerThread != null) {
mHandlerThread.start();
mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, mHandlerThread.getLooper());
} else {
mFusedLocationClient.requestLocationUpdates(mLocationRequest,
mLocationCallback, Looper.myLooper());
}
}
})
.addOnFailureListener(mActivity, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
"location settings ");
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(mActivity, REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException sie) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be " +
"fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);
mRequestingLocationUpdates = false;
}
}
});
}
/**
* Removes location updates from the FusedLocationApi.
*/
private void stopLocationUpdates() {
/* if (!mRequestingLocationUpdates) {
return;
}*/
// It is a good practice to remove location requests when the activity is in a paused or
// stopped state. Doing so helps battery performance and is especially
// recommended in applications that request frequent location updates.
mFusedLocationClient.removeLocationUpdates(mLocationCallback)
.addOnCompleteListener(mActivity, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
mRequestingLocationUpdates = false;
}
});
}
@Override
public void onResume() {
getLocationSettings();
// Within {@code onPause()}, we remove location updates. Here, we resume receiving
// location updates if the user has requested them.
if (updateIntervalInMs != 0 && mRequestingLocationUpdates && checkPermissions()) {
setLocationRequestProperties();
startLocationUpdates();
}
}
@Override
public void onPause() {
// Remove location updates to save battery.
stopLocationUpdates();
// Finish Handler Thread
if (mHandlerThread != null) {
if (Build.VERSION.SDK_INT >= 18) {
mHandlerThread.quitSafely();
} else {
mHandlerThread.quit();
}
mHandlerThread = null;
}
}
@Override
public void onDestroy() {
}
/**
* Return the current state of the permissions needed.
*/
private boolean checkPermissions() {
return (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED);
}
@Override
public void setInterval(long millis) {
updateIntervalInMs = millis;
}
@Override
public void setPriority(int priority) {
this.priority = priority;
}
@Override
public void setSmallestDisplacement(float displacement) {
this.smallestDisplacement = displacement;
}
@Override
public Location getLocation() {
return mCurrentLocation;
}
@Override
public LocationData getLocationData() {
return mLocationData;
}
private void fetchAddress(Location location) {
List<Address> addresses = null;
String addressInfo;
try {
addresses = geocoder.getFromLocation(
location.getLatitude(),
location.getLongitude(),
// In this sample, get just a single address.
1);
} catch (IOException e) {
e.printStackTrace();
// Catch network or other I/O problems.
addressInfo = mActivity.getString(R.string.location_updates_disabled);
mLocationData.setAddress(addressInfo);
} catch (IllegalArgumentException illegalArgumentException) {
// Catch invalid latitude or longitude values.
addressInfo = mActivity.getString(R.string.location_invalid_coordinates);
mLocationData.setAddress(addressInfo);
}
// Handle case where no address was found.
if (addresses == null || addresses.size() == 0) {
addressInfo = mActivity.getString(R.string.location_address_unavailable);
} else {
Address address = addresses.get(0);
ArrayList<String> addressFragments = new ArrayList<String>();
// Fetch the address lines using getAddressLine,
// join them, and send them to the thread.
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
addressFragments.add(address.getAddressLine(i));
}
addressInfo = TextUtils.join(System.getProperty("line.separator"), addressFragments);
}
mLocationData.setAddress(addressInfo);
}
}