MVVM с классами Location и Sensor, которым требуются методы жизненного цикла, такие как onResume () и onPause () - PullRequest
0 голосов
/ 22 сентября 2018

Я создаю приложение дополненной реальности.Базовым классом для AR является FrameLayout, который содержит классы для данных датчика, данных местоположения, помещения (еще не реализовано) для хранения POI и для Retrofit для получения данных из Интернета.

Нет сложностей для реализации MVVM With Room или Retrofitс репозиторием внутри класса ViewModel, который получает LiveData>, который является шаблоном репозитория, но я не смог выяснить, как решить классы, которые требуют методов жизненного цикла, таких как onResume (), onPause () и onDestroy () или сделать янеобходимо использовать MVVM с необходимыми классами жизненного цикла.

Примечание: Я еще не реализовал Room and Retrofit.И интерфейс LifeObserver изменит финальный выпуск LifeCycleObserver для ARView, я тестирую тот же код и с Eclipse, и у него нет классов арки.

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 для какой-либо деятельности или фрагмента.Я собираюсь выпустить это как библиотеку для разработчиков, желающих создавать простые приложения 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);

    }
}
...