开发模式之MVP

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_24675479/article/details/79863215

MVC使用

ServiceApi接口

public interface ServiceApi {
    // 接口涉及到解耦,userLogin 方法是没有任何实现代码
    @POST("loginuser")// 登录接口 GET(相对路径)
    @FormUrlEncoded
    Observable<Result<UserInfo>> queryUserInfo(
            // @Query(后台需要解析的字段)
            @Field("token") String token);
}

UserInfo用户信息

public class UserInfo {
    public String au_nickname;
    public String au_sex;

    @Override
    public String toString() {
        return "UserInfo{" +
                "userName='" + au_nickname + '\'' +
                ", userPwd='" + au_sex + '\'' +
                '}';
    }
}

RetrofitClient

public class RetrofitClient {
    private final static ServiceApi mServiceApi;

    static {
        OkHttpClient okHttpClient = new OkHttpClient
                .Builder()
                .addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                    @Override
                    public void log(String message) {
                        Log.e("TAG",message);
                    }
                }).setLevel(HttpLoggingInterceptor.Level.BODY))
                .build();
        // 各种套路和招式 ,发现问题解决问题,基础,源码的理解
        // 1. 没打印?
        // 2. 数据格式不一致?成功 data 是个对象,不成功 data 是个 String
        // 3. 还有就是 baseUrl 问题? (Retrofit 找不到任何入口可以修改)
        //        3.1 不同的 baseUrl 构建不同的 Retrofit 对象 (直不应该首选)
        //        3.2 自己想办法,取巧也行走漏洞
        Retrofit retrofit = new Retrofit.Builder()
                // 访问后台接口的主路径
                .baseUrl("http://ppw.zmzxd.cn/index.php/api/v1/")
                // 添加解析转换工厂,Gson 解析,Xml解析,等等
                .addConverterFactory(GsonConverterFactory.create())
                // 添加 OkHttpClient,不添加默认就是 光杆 OkHttpClient
                .client(okHttpClient)
                // 支持RxJava Call -> Obsevrable 怎么做到的? 1 2  采用了 Adapter 设计模式
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

        // 创建一个 实例对象
        mServiceApi = retrofit.create(ServiceApi.class);
    }

    public static ServiceApi getServiceApi() {
        return mServiceApi;
    }

    public static <T> Observable.Transformer<Result<T>, T> transformer() {
        return new Observable.Transformer<Result<T>, T>() {
            @Override
            public Observable<T> call(Observable<Result<T>> resultObservable) {
                // resultObservable -> Observable<Result<UserInfo>> userLogin
                return resultObservable.flatMap(new Func1<Result<T>, Observable<T>>() {
                    @Override
                    public Observable<T> call(Result<T> tResult) {
                        // 解析不同的情况返回
                        if(tResult.isOk()){
                            // 返回成功
                            return createObservable(tResult.data);
                        }else {
                            // 返回失败
                            return Observable.error(new ErrorHandle.ServiceError("",tResult.getMsg()));
                        }
                    }
                }).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
            }
        };
    }

    private static <T> Observable<T> createObservable(final T data) {
        return Observable.create(new Action1<Emitter<T>>() {
            @Override
            public void call(Emitter<T> tEmitter) {
                tEmitter.onNext(data);
                tEmitter.onCompleted();
            }
        }, Emitter.BackpressureMode.NONE);
    }
}

BaseResult

public class BaseResult {
    String bol;
    String msg;

    public String getMsg() {
        return msg;
    }

    public String getCode() {
        return bol;
    }

    public boolean isOk(){
        return "true".equals(bol);
    }
}

Result


public class Result<T> extends BaseResult{
    public T data;
}

ErrorHandle

public class ErrorHandle {

    public static class ServiceError extends Throwable{
        String errorCode;
        public ServiceError(String errorCode, String errorMsg) {
            super(errorMsg);
            this.errorCode = errorCode;
        }
    }
}

BaseSubscriber

public  abstract class BaseSubscriber<T> extends Subscriber<T> {
    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {
        // e :网络异常,解析异常,结果处理过程中异常
        e.printStackTrace();
        if(e instanceof ErrorHandle.ServiceError){
            ErrorHandle.ServiceError serviceError = (ErrorHandle.ServiceError) e;
            onError("",serviceError.getMessage());
        }else {
            // 自己处理
            onError("","未知异常");
        }
    }

    protected abstract void onError(String errorCode, String errorMessage);
}

使用

public class MainActivity extends AppCompatActivity {
    private TextView mUserInfoTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // OkHttp +RxJava + Retrofit 这样写代码行不行? 1 ,2 ,
        mUserInfoTv = (TextView) findViewById(R.id.user_info_tv);
        // 1. MVC 两个地方:个人主页,编辑资料,MVC意味着 ,这些代码是需要写很多份
        // 2. 如果团队协作,多人开发,那么这个页面(编辑资料)一般都是一个人在做,项目比较紧凑的时候,不好分配人
        // 3. 如果某些界面需求变更的情况下,不好定位,或者说出了 Bug 的情况下不怎么好修改(代码多)
        RetrofitClient.getServiceApi()
                .queryUserInfo("5daefee2ce7f9208d465fab4ae6e40c2")
                // .subscribeOn().observeOn().subscribe()
                // Subscriber 封装一下
                // 第二个坑 , 坑我们 返回值都是一个泛型,转换返回值泛型
                .compose(RetrofitClient.<UserInfo>transformer())
                // 注册完了要登录
                .subscribe(new BaseSubscriber<UserInfo>() {
                    @Override
                    protected void onError(String errorCode, String errorMessage) {
                        toast(errorMessage);
                    }

                    @Override
                    public void onNext(UserInfo userInfo) {
                        // 这个处理代码不一样
                        mUserInfoTv.setText(userInfo.toString());
                    }
                });
    }

    private void toast(String text) {
        Toast.makeText(this, text, Toast.LENGTH_LONG).show();
    }

    private void log(String text) {
        Log.e("TAG->Result", text);
    }
}

MVP第一个版本

UserInfoContract协议类,可有可无

public class UserInfoContract {
    interface UserInfoView{
        // 1.正在加载中
        // 2.获取出错了
        // 3.成功了要显示数据
        void onLoading();
        void onError();
        void onSucceed(UserInfo userInfo);
    }
    // user presenter 层
    interface UserInfoPresenter {
        void getUsers(String token);
    }
    interface UserInfoModel{
        Observable<UserInfo> getUsers(String token);
    }
}

UserInfoModel:M层

public class UserInfoModel implements UserInfoContract.UserInfoModel {
    @Override
    public Observable<UserInfo> getUsers(String token) {
        return RetrofitClient.getServiceApi()
                .queryUserInfo("5daefee2ce7f9208d465fab4ae6e40c2")
                // .subscribeOn().observeOn().subscribe()
                // Subscriber 封装一下
                // 第二个坑 , 坑我们 返回值都是一个泛型,转换返回值泛型
                .compose(RetrofitClient.<UserInfo>transformer());
    }
}

UserInfoPresenter:P层

class UserInfoPresenter implements UserInfoContract.UserInfoPresenter {
    UserInfoContract.UserInfoView mView;
    UserInfoContract.UserInfoModel mModel;
    public UserInfoPresenter(UserInfoContract.UserInfoView view) {
            this.mView=view;
        mModel=new UserInfoModel();
    }

    @Override
    public void getUsers(String token) {
        mModel.getUsers(token).subscribe(new BaseSubscriber<UserInfo>() {
            @Override
            protected void onError(String code, String message) {
                mView.onError();
            }

            @Override
            public void onNext(UserInfo userInfo) {
            mView.onSucceed(userInfo);
            }
        });
    }
}

使用

public class MainActivity extends AppCompatActivity implements UserInfoContract.UserInfoView {
    private TextView mUserInfoTv;
    private UserInfoPresenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // OkHttp +RxJava + Retrofit 这样写代码行不行? 1 ,2 ,
        mUserInfoTv = (TextView) findViewById(R.id.user_info_tv);
        // 1. MVC 两个地方:个人主页,编辑资料,MVC意味着 ,这些代码是需要写很多份
        // 2. 如果团队协作,多人开发,那么这个页面(编辑资料)一般都是一个人在做,项目比较紧凑的时候,不好分配人
        // 3. 如果某些界面需求变更的情况下,不好定位,或者说出了 Bug 的情况下不怎么好修改(代码多)
       mPresenter=new UserInfoPresenter(this);
        mPresenter.getUsers("ed6b0f7f34dd8cf7b003e40691457175");
    }



    @Override
    public void onLoading() {

    }

    @Override
    public void onError() {

    }

    @Override
    public void onSucceed(UserInfo userInfo) {
        // 成功
        mUserInfoTv.setText(userInfo.toString());
    }
}

MVP第二个版本:解决第一个版本存在的一个当我们进去然后直接退出程序的时候,本不应该继续加载数据,这时候

//这个方法不能满足我们
public UserInfoPresenter(UserInfoContract.UserInfoView view) {
            this.mView=view;
        mModel=new UserInfoModel();
    }

UserInfoPresenter修改

class UserInfoPresenter implements UserInfoContract.UserInfoPresenter {
    UserInfoContract.UserInfoView mView;
    UserInfoContract.UserInfoModel mModel;

    public UserInfoPresenter() {

        mModel = new UserInfoModel();
    }

    public void attachView(UserInfoContract.UserInfoView view) {
        mView = view;
    }

    public void detachView() {
        mView = null;
    }

    @Override
    public void getUsers(String token) {
        mModel.getUsers(token).subscribe(new BaseSubscriber<UserInfo>() {
            @Override
            protected void onError(String code, String message) {
                if (mView != null) {
                    mView.onError();
                }
            }

            @Override
            public void onNext(UserInfo userInfo) {
                if(mView!=null)
                mView.onSucceed(userInfo);
            }
        });
    }
}

MainActivity进行相应修改

public class MainActivity extends AppCompatActivity implements UserInfoContract.UserInfoView {
    private TextView mUserInfoTv;
    private UserInfoPresenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // OkHttp +RxJava + Retrofit 这样写代码行不行? 1 ,2 ,
        mUserInfoTv = (TextView) findViewById(R.id.user_info_tv);
        // 1. MVC 两个地方:个人主页,编辑资料,MVC意味着 ,这些代码是需要写很多份
        // 2. 如果团队协作,多人开发,那么这个页面(编辑资料)一般都是一个人在做,项目比较紧凑的时候,不好分配人
        // 3. 如果某些界面需求变更的情况下,不好定位,或者说出了 Bug 的情况下不怎么好修改(代码多)
       mPresenter=new UserInfoPresenter();
        mPresenter.getUsers("ed6b0f7f34dd8cf7b003e40691457175");
        mPresenter.attachView(this);
    }



    @Override
    public void onLoading() {

    }

    @Override
    public void onError() {

    }

    @Override
    public void onSucceed(UserInfo userInfo) {
        // 成功
        mUserInfoTv.setText(userInfo.toString());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }
}

MVP第三个版本:我们每次的时候都需要添加attach和detach方法太麻烦

BaseView

public interface BaseView {
}

BasePresenter

public class BasePresenter <T extends BaseView> {
    private T mView;
    public void attachView(T view) {
        mView = view;
    }

    public void detachView() {
        mView = null;
    }

    public T getView() {
        return mView;
    }
}

BaseMvpActivity:基类activity

public abstract class BaseMvpActivity<P extends BasePresenter>  extends AppCompatActivity implements BaseView {
    private P mPresenter;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView();
        mPresenter=createPresenter();
        mPresenter.attachView(this);
        initView();
        initData();
    }

    protected abstract P createPresenter();

    protected abstract void initData();

    protected abstract void initView();


    protected abstract void setContentView();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }

    public P getPresenter() {
        return mPresenter;
    }
}

UserInfoPresenter进行修改

class UserInfoPresenter extends BasePresenter<UserInfoContract.UserInfoView> implements UserInfoContract.UserInfoPresenter {

    UserInfoContract.UserInfoModel mModel;

    public UserInfoPresenter() {

        mModel = new UserInfoModel();
    }



    @Override
    public void getUsers(String token) {
        mModel.getUsers(token).subscribe(new BaseSubscriber<UserInfo>() {
            @Override
            protected void onError(String code, String message) {
                if (getView() != null) {
                    getView().onError();
                }
            }

            @Override
            public void onNext(UserInfo userInfo) {
                if(getView()!=null)
                getView().onSucceed(userInfo);
            }
        });
    }
}

MainActivity再次进行修改

public class MainActivity extends BaseMvpActivity<UserInfoPresenter> implements UserInfoContract.UserInfoView {
    private TextView mUserInfoTv;


    @Override
    protected UserInfoPresenter createPresenter() {
        return new UserInfoPresenter();
    }

    @Override
    protected void initData() {
        getPresenter().getUsers("ed6b0f7f34dd8cf7b003e40691457175");
    }

    @Override
    protected void initView() {
        mUserInfoTv = (TextView) findViewById(R.id.user_info_tv);
    }

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }


    @Override
    public void onLoading() {

    }

    @Override
    public void onError() {

    }

    @Override
    public void onSucceed(UserInfo userInfo) {
        // 成功
        mUserInfoTv.setText(userInfo.toString());
    }


}

MVP第四次版本:每次呢,都需要判断是否为空,也很麻烦( if (getView() != null) ),可以使用动态代理

BasePresenter修改

public class BasePresenter <T extends BaseView> {
    private SoftReference<T> mViewReference;
    private T mProxyView;
    public void attachView(final T view) {
        mViewReference = new SoftReference<T>(view);
       mProxyView= (T) Proxy.newProxyInstance(view.getClass().getClassLoader()
               , view.getClass().getInterfaces(), new InvocationHandler() {
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       if(mViewReference==null||mViewReference.get()==null){
                           return null;
                       }
                       // 没解绑执行的是原始被代理 View 的方法
                       return method.invoke(mViewReference.get(),args);
                   }
               });
    }

    public void detachView() {
        this.mViewReference.clear();
        this.mViewReference = null;
        this.mProxyView = null;
    }

    public T getView() {
        return mProxyView;
    }
}

MVP第五次版本:如果一个activity有多个presenter?new可以,但是不太好,可以使用注解,还有动态生成model类

BasePresenter动态生成model

public class BasePresenter<V extends BaseView,M extends BaseModel> {
    // 目前两个两个公用方法 ,传递的时候 会有不同的 View ,怎么办?泛型
    private WeakReference<V> mViewReference;
    private V mProxyView;
    // View 一般都是 Activity ,涉及到内存泄漏,但是已经解绑了不会,如果没解绑就会泄漏
    // 最好还是用一下软引用

    // 动态创建的 model 的对象
    private M mModel;

    // View 有一个特点,都是接口
    // GC 回收的算法机制(哪几种)标记清楚法
    public void attach(V view){
        this.mViewReference = new WeakReference<V>(view);

        // 用代理对象
        mProxyView = (V) Proxy.newProxyInstance(view.getClass().getClassLoader(), view.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 执行这个方法 ,调用的是被代理的对象
                if(mViewReference == null||mViewReference.get() == null) {
                    return null;
                }
                // 没解绑执行的是原始被代理 View 的方法
                return method.invoke(mViewReference.get(),args);
            }
        });

        // 创建我们的 Model ,动态创建? 获取 Class 通过反射 (Activity实例创建的?class 反射创建的,布局的 View 对象怎么创建的?反射)
        // 获取 Class 对象
        Type[] params = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
        try {
            // 最好要判断一下类型
            mModel = (M) ((Class)params[1]).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    public void detach(){
        // 不解绑的问题 Activity -> Presenter  ,Presenter 持有了 Activity 应该是会有内存泄漏
        this.mViewReference.clear();
        this.mViewReference = null;
        // 注释
        // this.mProxyView = null;
    }

    public M getModel() {
        return mModel;
    }

    public V getView() {
        return mProxyView;
    }
}

InjectPresenter注解

@Target(ElementType.FIELD)// 属性
@Retention(RetentionPolicy.RUNTIME)// 运行时
public @interface InjectPresenter {
}

BaseMvpActivity进行修改

public abstract class BaseMvpActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
    private P mPresenter;
    private List<BasePresenter> mPresenters;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView();
        mPresenters = new ArrayList<>();
        // 创建 P,创建只能交给 子类,每个 Activity 都不一样
        mPresenter = createPresenter();
        mPresenter.attach(this);

        // 这个地方要去注入 Presenter 通过反射 (Dagger) soEasy
        //如TextView
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields) {
            InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
            if(injectPresenter != null){
                // 创建注入
                Class<? extends BasePresenter> presenterClazz = null;
                // 自己去判断一下类型? 获取继承的父类,如果不是 继承 BasePresenter 抛异常
                try {
                    presenterClazz = (Class<? extends BasePresenter>) field.getType();
                } catch (Exception e){
                    // 乱七八糟一些注入
                    throw new RuntimeException("No support inject presenter type " + field.getType().getName());
                }

                try {
                    // 创建 Presenter 对象
                    BasePresenter basePresenter = presenterClazz.newInstance();
                    // 并没有解绑,还是会有问题,这个怎么办?1 2
                    basePresenter.attach(this);
                    // 设置
                    field.setAccessible(true);
                    field.set(this,basePresenter);
                    mPresenters.add(basePresenter);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        initView();
        initData();
    }

    // 由子类去实现创建
    protected abstract P createPresenter();

    protected abstract void initData();

    protected abstract void initView();

    protected abstract void setContentView();

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 一定要解绑
        for (BasePresenter presenter : mPresenters) {
            presenter.detach();
        }
        mPresenter.detach();
    }

    public P getPresenter() {
        return mPresenter;
    }
}

MVP第六次版本:静态代理设计模式封装一下

IMvpProxy代理接口

public interface IMvpProxy {
    void bindAndCreatePresenter();// 一个是和创建绑定
    void unbindPresenter();// 一个是解绑
}

MvpProvxyImpl实现类

public class MvpProxyImpl<V extends BaseView> implements IMvpProxy {
    private V view;
    private List<BasePresenter> mPresenters;

    public MvpProxyImpl(V view) {
        this.view = view;
        mPresenters = new ArrayList<>();
    }

    @Override
    public void bindAndPresenter() {
        //注入Presenter
        //Activity,Fragment,ViewGroup
        Field[] fields = view.getClass().getDeclaredFields();
        for (Field field : fields) {
            InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
            if (injectPresenter != null) {
                // 创建注入
                Class<? extends BasePresenter> presenterClazz = null;
                // 自己去判断一下类型? 获取继承的父类,如果不是 继承 BasePresenter 抛异常
                try {
                    //编译器会在运行的时候会对我们的泛型进行擦除(一般是针对系统的)
                    //没编译前的一种约束,写代码的时候只能是同一种类型
                    //泛型擦除一般是针对系统的,我们自己指定的泛型信息一般会保存
                    presenterClazz = (Class<? extends BasePresenter>) field.getType();//泛型擦除
                    if (!BasePresenter.class.isAssignableFrom(presenterClazz)) {
                        //判断Class是否继承自BasePresenter,如果不是抛异常
                        throw new RuntimeException("No support inject presenter type " + presenterClazz.getName());
                    }
                } catch (Exception e) {
                    // 乱七八糟一些注入,没有报错
                    throw new RuntimeException("No support inject presenter type " + field.getType().getName());
                }
                BasePresenter basePresenter = null;
                try {
                    // 创建 Presenter 对象
                    basePresenter = presenterClazz.newInstance();
                    basePresenter.attach(view);
                    // 设置
                    field.setAccessible(true);
                    field.set(view, basePresenter);
                    mPresenters.add(basePresenter);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //检查我们的view层,是否实现了BasePresenter的view接口
                checkView(basePresenter);
            }
        }
    }

    private void checkView(BasePresenter basePresenter) {
        //要拿到view层的所有接口和basePresenter中的所有view接口,view层没有实现就抛出异常
        Type[] params = ((ParameterizedType) basePresenter.getClass().getGenericSuperclass()).getActualTypeArguments();
        Class viewClazz = ((Class) params[0]);
        Class<?>[] interfaces = view.getClass().getInterfaces();
        boolean isImplementsView=false;
        for (Class<?> anInterface : interfaces) {
          if(anInterface.isAssignableFrom(viewClazz)){
               isImplementsView=true;
          }
        }
        if(!isImplementsView){
            throw new RuntimeException(view.getClass().getSimpleName()+"View must implements"+viewClazz.getName());
        }
    }

    @Override
    public void unbindPresenter() {
        // 一定要解绑
        for (BasePresenter presenter : mPresenters) {
            presenter.detach();
        }
        view = null;
    }
}

ActivityMvpProxy,以后可能有FragmentMvpProvxy和VeiwGroup

public class ActivityMvpProxy<V extends BaseView> extends MvpProvxyImpl{
    public ActivityMvpProxy(BaseView view) {
        super(view);
    }
}

BaseMvpActivity进行修改

public abstract class BaseMvpActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
    private P mPresenter;
    private ActivityMvpProxy mMvpProxy;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView();

        // 创建 P,创建只能交给 子类,每个 Activity 都不一样
        mPresenter = createPresenter();
        mPresenter.attach(this);

        mMvpProxy = createMvpProxy();
        mMvpProxy.bindAndCreatePresenter();

        initView();
        initData();
    }

    protected ActivityMvpProxy createMvpProxy() {
        if (mMvpProxy == null) {
            mMvpProxy = new ActivityMvpProxy(this);

        }
        return mMvpProxy;
    }


    // 由子类去实现创建
    protected abstract P createPresenter();

    protected abstract void initData();

    protected abstract void initView();

    protected abstract void setContentView();

    @Override
    public void onDestroy() {
        super.onDestroy();
        mMvpProxy.unbindPresenter();
        mPresenter.detach();
    }

    public P getPresenter() {
        return mPresenter;
    }
}

补充:GC垃圾回收
对象什么时候回收? 对象有没有死掉? 2种算法(判断对象需不需要回收)1. 引用计数法 ,2. GcRoot 可达分析算法
回收算法: 判断死了,回收(回收算法) 分为两步 (需要回收的对象加入队列,调用对象的 finalize 方法)
1. 标记清楚算法(效率低,节省一些内存)
2. 复制算法 (效率要高,浪费一些内存)
3. 标记整理算法
4. 分代算法
涉及GC 分带年龄,对象有年龄 新生代,老年代,永久代。 Class 头信息

猜你喜欢

转载自blog.csdn.net/qq_24675479/article/details/79863215