제트 팩을 사용하여 원칙과 핵심 구성 요소, 뷰 모델 해상도

머리말

저장하고 활동 및 조각의 데이터를 관리하는 데 사용할 수있는 사용자 인터페이스, 관련 데이터를 관리하기위한 라이프 사이클의 뷰 모델에 대한 인식. 또한 조각 조각 등 사이의 통신을 처리하는 데 사용할 수 있습니다.

활동이나 조각이 관련된 뷰 모델, 너무 오래 같은 활동을 만들거나 조각이 활성화되면, 다음 뷰 모델은 활동 화면 회전의 경우에도 재건을 파괴하지 않습니다. 이 데이터를 어떻게 사용할 수 있도록 일시적으로 저장된다.

뷰 모델 주로 구 또는 개발자가 활성 뷰 모델 관측을 변경할 수 있고, 데이터 액티비티 / 단편은 필요한 유지하는데 사용된다 /의 단편 (이 LiveData 먹게한다).

UI의 ViewModel은 데이터를 관리하는 데 사용, 그것은보기, 활동 또는 조각 참조 (주의 메모리 누수 수) 개최하지 않습니다.

본 논문에서는 방법에 대한 진보적 인 접근 방식은 뷰 모델을 학습.

뷰 모델을 사용하여

도입의 ViewModel

//引入AndroidX吧,替换掉support包
implementation 'androidx.appcompat:appcompat:1.0.2'

def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

사용이 간편

사용자는 데이터 클래스를 정의합니다.

class User implements Serializable {

    public int age;
    public String name;

    public User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

그리고 오늘의 주인공 뷰 모델에 연결됩니다.

public class UserModel extends ViewModel {

    public final MutableLiveData<User> mUserLiveData = new MutableLiveData<>();

    public UserModel() {
        //模拟从网络加载用户信息
        mUserLiveData.postValue(new User(1, "name1"));
    }

    //模拟 进行一些数据骚操作
    public void doSomething() {
        User user = mUserLiveData.getValue();
        if (user != null) {
            user.age = 15;
            user.name = "name15";
            mUserLiveData.setValue(user);
        }
    }

}

이번에는 당신은 활동의 뷰 모델을 사용할 수 있습니다. 사실, 코드의 간단한 예, 다음은 뷰 모델을 사용할 수 있습니다.

//这些东西我是引入的androidx下面的
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;

public class MainActivity extends FragmentActivity {

    private TextView mContentTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mContentTv = findViewById(R.id.tv_content);

        //构建ViewModel实例
        final UserModel userModel = ViewModelProviders.of(this).get(UserModel.class);

        //让TextView观察ViewModel中数据的变化,并实时展示
        userModel.mUserLiveData.observe(this, new Observer<User>() {
            @Override
            public void onChanged(User user) {
                mContentTv.setText(user.toString());
            }
        });

        findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //点击按钮  更新User数据  观察TextView变化
                userModel.doSomething();
            }
        });
    }
}

사실이 작업이 다시 만들어, 그리고 때이 시간, 우리는, 우리가 휴대 전화 화면을 회전 할 수 있습니다 (사용자가 15 세의이됩니다) 버튼을 클릭 (즉에서 onCreate () 메서드를 다시 호출 있지만, 사실 뷰 모델이 다시 생성되지 않습니다 또는 뷰 모델 전),하지만 우리가 회전 할 때, 우리는 나이가 실제로 뷰 모델 마법 거짓말 인 텍스트 뷰 (15)에 표시되는 것을 발견했다. 뷰 모델은 수명을 언급 할 것, 그리고 그것은 단지 활동의 파괴 후,이 자폭 (그래서 뷰 ​​모델이 활동 인용 아, 윌 메모리 누수를 보유하지 않는) 것입니다. 뷰 모델의 수명주기의 구글 공식 사진에 대한 다음의 인용문은 가장을 보여줍니다.
제트 팩을 사용하여 원칙과 핵심 구성 요소, 뷰 모델 해상도

뷰 모델 마법 1 : 활동의 조각과 "소통"

조각이의 ViewModel을 인스턴스화 활동 들어오는 ViewModelProviders의 활동에 의존하기 때문에 뷰 모델로, 활동 및 조각이하는 뷰 모델을 공유 할 수 있습니다, 그것은 당신에게 줄 것이다 good've는 뷰 모델의 활동을 만들어,이 프래그먼트 수 있습니다 뷰 모델의 데이터에 편리하게 액세스 할 수 있습니다. 활동 userModel 수정 된 데이터는 조각의 수를 업데이트받을 수 있습니다.

뷰 모델 마법 2 : 조각 조각이와 "소통"

다음의 예제 (구글 공식 예)를 살펴 보자

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}
  1. 첫째, 뷰 모델을 정의 거기에 일부 데이터를 넣어.

(2) 다음 MasterFragment DetailFragment는 뷰 모델을 얻을 수 있고, 뷰 모델을 통해 간접적으로 통신에 대응하는 데이터를 얻을 수있는 뷰 모델 내부 얻는다. 너무 쉽게 ...

소스 해결의 ViewModel

우리는 아래의 코드에서 시작합니다.

final UserModel userModel = ViewModelProviders.of(this).get(UserModel.class);

우리는 ViewModelProviders.of (이) 따라 새로운 세계의 문을 엽니 다.

ViewModelProviders.of (이) 方法

/**
 * 用于构建一个ViewModelProvider,当Activity是alive时它会保留所有的该Activity对应的ViewModels.
 */
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    //检查application是否为空,不为空则接收
    Application application = checkApplication(activity);
    if (factory == null) {
        //构建一个ViewModelProvider.AndroidViewModelFactory
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

() 함수의 내부 ViewModelProviders 우리가 ViewModelProvider를 구축 사실을 촉진하는 것입니다. 그리고 ViewModelProvider는 모습하고, 뷰 모델을 제공의 이름을 알고있다.

공장 ViewModelProvider 내부 인터페이스이며, 그 구현은 뷰 모델 클래스의 인스턴스를 생성하기 위해 사용된다. 그것은 단 하나의 방법은 뷰 모델을 만드는 것입니다있다.

/**
 * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
 */
public interface Factory {
    /**
     * Creates a new instance of the given {@code Class}.
     * <p>
     *
     * @param modelClass a {@code Class} whose instance is requested
     * @param <T>        The type parameter for the ViewModel.
     * @return a newly created ViewModel
     */
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

하나 NewInstanceFactory, 하나 AndroidViewModelFactory입니다 : 공장 구현 클래스는 두 가지가 있습니다.

NewInstanceFactory 소스

public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

NewInstanceFactory 인자가없는 생성자 클래스가 없을 것이라는 점을 인스턴스화하도록 설계 뷰 모델 컨텍스트없이 어떤 한 다음이 인스턴스를 위해서, newInstance ()하는 것입니다.

AndroidViewModelFactory 소스

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    /**
     * Retrieve a singleton instance of AndroidViewModelFactory.
     *
     * @param application an application to pass in {@link AndroidViewModel}
     * @return A valid {@link AndroidViewModelFactory}
     */
    @NonNull
    public static AndroidViewModelFactory getInstance(@NonNull Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    /**
     * Creates a {@code AndroidViewModelFactory}
     *
     * @param application an application to pass in {@link AndroidViewModel}
     */
    public AndroidViewModelFactory(@NonNull Application application) {
        mApplication = application;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
        return super.create(modelClass);
    }
}

AndroidViewModelFactory 내부 클래스 생성자 매개 변수의 인스턴스를 설계하고, 컨텍스트와 뷰 모델이있을 수 있습니다.

newInstance와 (응용 프로그램) 인스턴스화를 통해 때문이다. 경우이 응용 프로그램 인스턴스와 인수가 있습니다.

매개 변수없이 응용 프로그램이 있다면, 여전히 인스턴스를 구축하기 위해서, newInstance () 메소드를 이동합니다.

의 ViewModel 응용 프로그램에 생성자가 제기 AndroidViewModelFactory, 당신은 APP 응용 프로그램은 글로벌이기 때문에, 다음 메모리 누수 문제가 없으며, 내부의 상황에 맞는 뷰 모델에서 얻을 수있는 완벽한 솔루션 컨텍스트 참조는 내부의 뷰 모델을 필요로하지만, 메모리 누수에 대한 걱정 문제.

여기에 우리가 ViewModelProviders.of (이) 방법은 그것을 분석하는 새로운 ViewModelProvider (activity.getViewModelStore (), 공장)의 마지막 문장에주의를 기울여야 계속 계속, 첫 번째 인수는 getViewModelStore의 활동 () 메소드를 (이 방법은 ViewModelStore을 반환 호출이 클래스 뷰 모델을 저장하는 데 사용됩니다, 우리는 활동은 여기 getViewModelStore () 메소드를 보면, androidx.fragment.app.FragmentActivity입니다) 아래에 설명합니다.

/**
 * 获取这个Activity相关联的ViewModelStore
 */
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
        //获取最近一次横竖屏切换时保存下来的数据
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

//没想到吧,Activity在横竖屏切换时悄悄保存了viewModelStore
//注意,这是FragmentActivity中的NonConfigurationInstances(其实Activity中还定义了一个NonConfigurationInstances,内容要比这个多一些,但是由于没有关系到它,这里就不提及了)
static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
    FragmentManagerNonConfig fragments;
}

() 복원 onRestoreInstanceState 호출 onSaveInstanceState ()를 전환 할 때 안드로이드 수평 및 수직 화면이 실행됩니다,하지만 두 가지 방법 onRetainNonConfigurationInstance ()와 getLastNonConfigurationInstance ()이 두 가지 방법이라고 안드로이드의 활동 클래스가 있습니다.

결코 만난 적이 두 가지의 구체적인 방법을보십시오.

/**
 保留所有fragment的状态。你不能自己覆写它!如果要保留自己的状态,请使用onRetainCustomNonConfigurationInstance()
 这个方法在FragmentActivity里面
 */
@Override
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();

    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

    if (fragments == null && mViewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = mViewModelStore;
    nci.fragments = fragments;
    return nci;
}

//这个方法在Activity里面,而mLastNonConfigurationInstances.activity实际就是就是上面方法中年的nci
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

이제 getLastNonConfigurationInstance ()를 호출 할 수있는 기회를 살펴 보자, 그리고

protected void onCreate(@Nullable Bundle savedInstanceState) {
    ......
    super.onCreate(savedInstanceState);

    NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
    if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
        mViewModelStore = nc.viewModelStore;
    }
    ......
}

나는 당시 활동으로 전환했다 그것을 기대하지 않았다 스크린 조용히 따라서 수평 및 수직 화면을 피하고, NonConfigurationInstances 예, 다시 전환 할 때, 해당하는 뷰 모델 인스턴스가 아 아직 재개 저장 수평 및 수직 화면 안에 배치 viewModelStore를 저장할 때 스위치 데이터 손실.

viewModelProvider.get (UserModel.class)

여기에 우리가 그것을 실현 있는지 확인하기 위해 GET () 메소드 ViewModelProvider 인의 절반을 구축하기 위해 뷰 모델 코드 구문에 와서, 실제로는 매우 간단합니다.

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //先取缓存  有缓存则用缓存
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    //无缓存  则重新通过mFactory构建
    viewModel = mFactory.create(modelClass);
    //缓存起来
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

일반적인 아이디어가 복원되지 않으며, 캐시는 캐시가, 뷰 모델 캐시 키를 사용하는 것입니다. 공장 () 메서드의 상단을 구성 할 때 공장이 사용됩니다.

ViewModelStore

하나 개 이상의 장소는 위 실제로 뷰 모델 클래스 저장 보통이다, ViewModelStore를 사용합니다.

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

ViewModelStore 일반 줄을 저장하는 전용의 HashMap이있다.

전화했을 때의 명확 보자 ().

ViewModel.onCleared () 자원화

뷰 모델은 인식의 라이프 사이클이기 때문에, 다음 뷰 모델은 청소해야 할 때?

우리는 () 메소드들의 OnDestroy의 FragmentActivity 와서는 청소 여기 것으로 나타났습니다.

/**
 * Destroy all fragments.
 */
@Override
protected void onDestroy() {
    super.onDestroy();

    if (mViewModelStore != null && !isChangingConfigurations()) {
        mViewModelStore.clear();
    }

    mFragments.dispatchDestroy();
}

뷰 모델을 봐

내 친구의 많은 요청해야 할 수도 있습니다, 뷰 모델은 결국 무엇인가?

public abstract class ViewModel {
    /**
     * 这个方法会在ViewModel即将被销毁时调用,可以在这里清理垃圾
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

사실, 아주 간단한, 추상 빈 방법에 ??? 내가 문질러 클래스, 그래서 지금, 원래의 ViewModel하지 영웅 ...

AndroidViewModel

뷰 모델이 AndroidViewModel있는 서브 클래스가있다. 그것은 내부의 상황에 맞는 뷰 모델을 용이하게하기 위해, 아무 응용 프로그램 속성을 포함하지 않는다.

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

개요

뷰 모델의 소스는,별로 이해하기 쉽게, 주요 공식 FragmentActivity 기술, onRetainNonConfigurationInstance () 상태를 저장하고, getLastNonConfigurationInstance () 복구 기능을 제공합니다.

난 그냥 onSaveInstanceState ()와 onRestoreInstanceState (), 상승 자세를 알기도 전에 너무 원래 활동이 물건이 있습니다.

추천

출처blog.51cto.com/14332859/2401440