public int getFsize() {
return fsize;
}
public void setFsize(int fsize) {
this.fsize = fsize;
}
}
}
然后改动一下litepal.xml配置文件
数据表有了,下面就是要请求数据了,所以要新增一个API接口,打开ServiceGenerator,新增如下,我将之前的测试地址做了一个拆分,这样规范管理,虽说有些麻烦。
case 5://APP更新
BASE_URL = “http://api.bq04.com”;//Fr.im更新
break;
然后打开ApiService,新增接口方法,因为里面的id和api token是固定的所以我就不用动态传递过去了,直接写死在url里面,当然如果你要写的话记得要用自己的id和api token,用我的你是那不到返回数据的。
/**
- APP版本更新
*/
@GET(“/apps/latest/你的id?api_token=你的API Token”)
Call getAppInfo();
那么下面就要写一个订阅器了,我的想法是在应用欢迎页就请求数据。那么就在app下的contract包下创建一个SplashContract,里面的代码如下:
package com.llw.goodweather.contract;
import com.llw.goodweather.api.ApiService;
import com.llw.goodweather.bean.NewSearchCityResponse;
import com.llw.goodweather.bean.NowResponse;
import com.llw.mvplibrary.base.BasePresenter;
import com.llw.mvplibrary.base.BaseView;
import com.llw.mvplibrary.bean.AppVersion;
import com.llw.mvplibrary.net.NetCallBack;
import com.llw.mvplibrary.net.ServiceGenerator;
import retrofit2.Call;
import retrofit2.Response;
/**
- 欢迎页订阅器
*/
public class SplashContract {
public static class SplashPresenter extends BasePresenter {
/**
- 获取最新的APP版本信息
*/
public void getAppInfo() {//注意这里的4表示新的搜索城市地址接口
ApiService service = ServiceGenerator.createService(ApiService.class, 5);
service.getAppInfo().enqueue(new NetCallBack() {
@Override
public void onSuccess(Call call, Response response) {
if(getView() != null){
getView().getAppInfoResult(response);
}
}
@Override
public void onFailed() {
if(getView() != null){
getView().getDataFailed();
}
}
});
}
}
public interface ISplashView extends BaseView {
//APP信息返回
void getAppInfoResult(Response response);
//错误返回
void getDataFailed();
}
}
都是属于基本操作了,不需要解释了。
然后打开SplashActivity
增加请求
实现如下三个方法
@Override
protected SplashContract.SplashPresenter createPresent() {
return new SplashContract.SplashPresenter();
}
/**
-
获取APP最新版本信息返回
-
@param response
*/
@Override
public void getAppInfoResult(Response response) {
if (response.body() != null) {
AppVersion appVersion = new AppVersion();
appVersion.setName(response.body().getName());//应用名称
appVersion.setVersion(response.body().getVersion());//应用版本 对应code
appVersion.setVersionShort(response.body().getVersionShort());//应用版本名
appVersion.setChangelog(response.body().getChangelog());//更新日志
appVersion.setUpdate_url(response.body().getUpdate_url());//更新地址
appVersion.setInstall_url(response.body().getInstall_url());//安装地址
appVersion.setAppSize(String.valueOf(response.body().getBinary().getFsize()));//APK大小
//添加数据前先判断是否已经有数据了
if (LitePal.find(AppVersion.class, 1) != null){
appVersion.update(1);//更新数据
}else {
appVersion.save();//保存添加数据
}
}
}
@Override
public void getDataFailed() {
Log.d(“Network Error”,“网络异常”);
}
现在这个数据就已经有了并且也储存起来了,下一步就是检测这个版本的更新了。
这里我写了两个工具类,代码如下
APKVersionInfoUtils .java
package com.llw.goodweather.utils;
import android.content.Context;
import android.content.pm.PackageManager;
/**
-
Created by chenxi on 2019/4/29.
-
APP版本信息获取工具类
*/
public class APKVersionInfoUtils {
/**
-
获取当前本地apk的版本
-
@param mContext
-
@return
*/
public static int getVersionCode(Context mContext) {
int versionCode = 0;
try {
//获取软件版本号,对应AndroidManifest.xml下android:versionCode
versionCode = mContext.getPackageManager().
getPackageInfo(mContext.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return versionCode;
}
/**
-
获取版本号名称
-
@param context 上下文
-
@return
*/
public static String getVerName(Context context) {
String verName = “”;
try {
verName = context.getPackageManager().
getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return verName;
}
}
AppStartUpUtils .java
package com.llw.goodweather.utils;
import android.content.Context;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
- APP启动判断工具类
*/
public class AppStartUpUtils {
/**
-
判断是否是首次启动
-
@param context
-
@return
*/
public static boolean isFirstStartApp(Context context) {
Boolean isFirst = SPUtils.getBoolean(Constant.APP_FIRST_START, true, context);
if (isFirst) {// 第一次
SPUtils.putBoolean(Constant.APP_FIRST_START, false, context);
return true;
} else {
return false;
}
}
/**
-
判断是否是今日首次启动APP
-
@param context
-
@return
*/
public static boolean isTodayFirstStartApp(Context context) {
String saveDate = SPUtils.getString(Constant.START_UP_APP_TIME, “2020-08-27”, context);
String todayDate = new SimpleDateFormat(“yyyy-MM-dd”).format(new Date());
if (!saveDate.equals(todayDate)) {//第一次打开
SPUtils.putString(Constant.START_UP_APP_TIME, todayDate, context);
return true;
} else {
return false;
}
}
}
第二个工具类里面用到了一些全局的参数,如下:
package com.llw.goodweather.utils;
/**
- 统一管理缓存中对应的KEY
*/
public class Constant {
public final static String SUCCESS_CODE = “200”;
public final static String CITY = “city”;//市
public final static String DISTRICT = “district”;//区/县
public final static String LOCATION_ID = “locationId”;//通过搜索接口得到的城市id,在V7中所有数据通过id来查询
public final static String EVERYDAY_IMG = “everyday_img”;//每日图片开关
public final static String IMG_LIST = “img_list”;//图片列表开关
public final static String CUSTOM_IMG = “custom_img”;//手动定义开关
public final static String IMG_POSITION = “img_position”;//选中的本地背景图片
public final static int SELECT_PHOTO = 2;//启动相册标识
public final static String CUSTOM_IMG_PATH = “custom_img_path”;//手动上传图片地址
public final static String FLAG_OTHER_RETURN=“flag_other_return”;//跳转页面的标识
public final static String LOCATION=“location”;
public final static String APP_FIRST_START = “appFirstStart”;//App首次启动
public final static String START_UP_APP_TIME = “startAppTime”;//今日启动APP的时间
}
然后在MainActivity中就可以进行判断了
/**
- 检查APP版本
*/
private void checkAppVersion() {
AppVersion appVersion = LitePal.find(AppVersion.class,1);
if(!appVersion.getVersionShort().equals(APKVersionInfoUtils.getVerName(context))){//提示更新
if(AppStartUpUtils.isTodayFirstStartApp(context)){//今天第一次打开APP
//更新提示弹窗
}
}
}
下面来写这个更新提示弹窗,我就把我好久之前写的放上来了。
首先增加样式文件,在mvplibrary下面的styles.xml中
里面还有6个动画文件。如下:
在anim下创建
dialog_from_bottom_anim_in.xml
<?xml version="1.0" encoding="utf-8"?><translate
android:duration=“400”
android:fromXDelta=“0”
android:fromYDelta=“1000”
android:toXDelta=“0”
android:toYDelta=“0” />
dialog_from_bottom_anim_out.xml
<?xml version="1.0" encoding="utf-8"?><translate
android:duration=“400”
android:fromXDelta=“0”
android:fromYDelta=“0”
android:toXDelta=“0”
android:toYDelta=“1000” />
dialog_from_top_anim_in.xml
<?xml version="1.0" encoding="utf-8"?><translate
android:duration=“1000”
android:fromYDelta=“-100%”
android:toYDelta=“0” />
dialog_from_top_anim_out.xml
<?xml version="1.0" encoding="utf-8"?><translate
android:duration=“1000”
android:fromYDelta=“0”
android:toYDelta=“-100%” />
dialog_scale_anim_in.xml
<?xml version="1.0" encoding="utf-8"?><scale
android:duration=“135”
android:fromXScale=“0.8”
android:fromYScale=“0.8”
android:pivotX=“50%”
android:pivotY=“50%”
android:toXScale=“1.05”
android:toYScale=“1.05” />
<scale
android:duration=“105”
android:fromXScale=“1.05”
android:fromYScale=“1.05”
android:pivotX=“50%”
android:pivotY=“50%”
android:startOffset=“135”
android:toXScale=“0.95”
android:toYScale=“0.95” />
<scale
android:duration=“60”
android:fromXScale=“0.95”
android:fromYScale=“0.95”
android:pivotX=“50%”
android:pivotY=“50%”
android:startOffset=“240”
android:toXScale=“1.0”
android:toYScale=“1.0” />
<alpha
android:duration=“90”
android:fromAlpha=“0.0”
android:interpolator=“@android:anim/accelerate_interpolator”
android:toAlpha=“1.0” />
dialog_scale_anim_out.xml
<?xml version="1.0" encoding="utf-8"?><scale
android:duration=“150”
android:fromXScale=“1.0”
android:fromYScale=“1.0”
android:pivotX=“50%”
android:pivotY=“50%”
android:toXScale=“0.6”
android:toYScale=“0.6” />
<alpha
android:duration=“150”
android:fromAlpha=“1.0”
android:interpolator=“@android:anim/accelerate_interpolator”
android:toAlpha=“0.0” />
下面就是关于自定义Dialog的内容了。
在view下新增一个dialog文件夹,然后一一创建下面的文件
AlertDialog.java
package com.llw.mvplibrary.view.dialog;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;
import com.llw.mvplibrary.R;
/**
- 自定义弹窗
*/
public class AlertDialog extends Dialog {
private AlertController mAlert;
public AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, themeResId);
mAlert = new AlertController(this, getWindow());
}
public void setText(int viewId, CharSequence text) {
mAlert.setText(viewId, text);
}
public T getView(int viewId) {
return mAlert.getView(viewId);
}
public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {
mAlert.setOnClickListener(viewId, onClickListener);
}
//----------------------------------------------------------------------------------------------
public static class Builder {
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, R.style.dialog);
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(context, themeResId);
}
/**
-
设置对话框布局
-
@param view
-
@return
*/
public Builder setContentView(View view) {
P.mView = view;
P.mLayoutResId = 0;
return this;
}
/**
-
@param layoutId
-
@return
*/
public Builder setContentView(int layoutId) {
P.mView = null;
P.mLayoutResId = layoutId;
return this;
}
/**
-
设置文本
-
@param viewId
-
@param text
-
@return
*/
public Builder setText(int viewId, CharSequence text) {
P.mTextArray.put(viewId, text);
return this;
}
/**
-
设置文本颜色
-
@param viewId
-
@param color
-
@return
*/
public Builder setTextColor(int viewId, int color) {
P.mTextColorArray.put(viewId, color);
return this;
}
/**
-
设置图标
-
@param iconId
-
@return
*/
public Builder setIcon(int iconId, int resId) {
P.mIconArray.put(iconId, resId);
return this;
}
/**
-
设置图片
-
@param viewId
-
@return
*/
public Builder setBitmap(int viewId, Bitmap bitmap) {
P.mBitmapArray.put(viewId, bitmap);
return this;
}
/**
-
设置对话框宽度占满屏幕
-
@return
*/
public Builder fullWidth() {
P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
return this;
}
/**
-
对话框底部弹出
-
@param isAnimation
-
@return
*/
public Builder fromBottom(boolean isAnimation) {
if (isAnimation) {
P.mAnimation = R.style.dialog_from_bottom_anim;
}
P.mGravity = Gravity.BOTTOM;
return this;
}
/**
-
对话框右部弹出
-
@param isAnimation
-
@return
*/
public Builder fromRight(boolean isAnimation) {
if (isAnimation) {
P.mAnimation = R.style.dialog_from_bottom_anim;
}
P.mGravity = Gravity.RIGHT;
return this;
}
/**
-
设置对话框宽高
-
@param width
-
@param heigth
-
@return
*/
public Builder setWidthAndHeight(int width, int heigth) {
P.mWidth = width;
P.mHeight = heigth;
return this;
}
/**
-
设置对话框宽高
-
@param width
-
@param heigth
-
@return
*/
public Builder setWidthAndHeightMargin(int width, int heigth, int heightMargin, int widthMargin) {
P.mWidth = width;
P.mHeight = heigth;
P.mHeightMargin = heightMargin;
P.mWidthMargin = widthMargin;
return this;
}
/**
-
添加默认动画
-
@return
*/
public Builder addDefaultAnimation() {
P.mAnimation = R.style.dialog_scale_anim;
return this;
}
/**
-
设置动画
-
@param styleAnimation
-
@return
*/
public Builder setAnimation(int styleAnimation) {
P.mAnimation = styleAnimation;
return this;
}
/**
-
设置点击事件
-
@param viewId
-
@param onClickListener
-
@return
*/
public Builder setOnClickListener(int viewId, View.OnClickListener onClickListener) {
P.mClickArray.put(viewId, onClickListener);
return this;
}
public Builder setOnLongClickListener(int viewId, View.OnLongClickListener onLongClickListener) {
P.mLondClickArray.put(viewId, onLongClickListener);
return this;
}
/**
-
Sets whether the dialog is cancelable or not. Default is true.
-
@return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
return this;
}
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
return this;
}
public Builder setOnDismissListener(OnDismissListener onDismissListener) {
P.mOnDismissListener = onDismissListener;
return this;
}
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
return this;
}
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
}
AlertController.java
package com.llw.mvplibrary.view.dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
/**
- 弹窗控制
*/
public class AlertController {
private AlertDialog mAlertDialog;
private Window mWindow;
private DialogViewHelper mViewHelper;
public AlertController(AlertDialog alertDialog, Window window) {
mAlertDialog = alertDialog;
mWindow = window;
}
public void setDialogViewHelper(DialogViewHelper dialogViewHelper) {
mViewHelper = dialogViewHelper;
}
public void setText(int viewId, CharSequence text) {
mViewHelper.setText(viewId, text);
}
public void setIcon(int viewId, int resId) {
mViewHelper.setIcon(viewId, resId);
}
public T getView(int viewId) {
return mViewHelper.getView(viewId);
}
public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {
mViewHelper.setOnClickListener(viewId, onClickListener);
}
public AlertDialog getDialog() {
return mAlertDialog;
}
public Window getWindow() {
return mWindow;
}
//-------------------------------------------------------------------------------------------------
public static class AlertParams {
public Context mContext;
//对话框主题背景
public int mThemeResId;
public boolean mCancelable;
public DialogInterface.OnCancelListener mOnCancelListener;
public DialogInterface.OnDismissListener mOnDismissListener;
public DialogInterface.OnKeyListener mOnKeyListener;
//文本颜色
public SparseArray mTextColorArray = new SparseArray<>();
//存放文本的更改
public SparseArray mTextArray = new SparseArray<>();
//存放点击事件
public SparseArray<View.OnClickListener> mClickArray = new SparseArray<>();
//存放长按点击事件
public SparseArray<View.OnLongClickListener> mLondClickArray = new SparseArray<>();
//存放对话框图标
public SparseArray mIconArray = new SparseArray<>();
//存放对话框图片
public SparseArray mBitmapArray = new SparseArray<>();
//对话框布局资源id
public int mLayoutResId;
//对话框的view
public View mView;
//对话框宽度
public int mWidth;
//对话框高度
public int mHeight;
//对话框垂直外边距
public int mHeightMargin;
//对话框横向外边距
public int mWidthMargin;
//动画
public int mAnimation;
//对话框显示位置
public int mGravity = Gravity.CENTER;
public AlertParams(Context context, int themeResId) {
mContext = context;
mThemeResId = themeResId;
}
public void apply(AlertController alert) {
//设置对话框布局
DialogViewHelper dialogViewHelper = null;
if (mLayoutResId != 0) {
dialogViewHelper = new DialogViewHelper(mContext, mLayoutResId);
}
if (mView != null) {
dialogViewHelper = new DialogViewHelper();
dialogViewHelper.setContentView(mView);
}
if (dialogViewHelper == null) {
throw new IllegalArgumentException(“please set layout”);
}
//将对话框布局设置到对话框
alert.getDialog().setContentView(dialogViewHelper.getContentView());
//设置DialogViewHelper辅助类
alert.setDialogViewHelper(dialogViewHelper);
//设置文本
for (int i = 0; i < mTextArray.size(); i++) {
alert.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));
}
//设置图标
for (int i = 0; i < mIconArray.size(); i++) {
alert.setIcon(mIconArray.keyAt(i), mIconArray.valueAt(i));
}
//设置点击
for (int i = 0; i < mClickArray.size(); i++) {
alert.setOnClickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));
}
//配置自定义效果,底部弹出,宽高,动画,全屏
Window window = alert.getWindow();
window.setGravity(mGravity);//显示位置
if (mAnimation != 0) {
window.setWindowAnimations(mAnimation);//设置动画
}
//设置宽高
WindowManager.LayoutParams params = window.getAttributes();
params.width = mWidth;
params.height = mHeight;
params.verticalMargin = mHeightMargin;
params.horizontalMargin = mWidthMargin;
window.setAttributes(params);
}
}
}
DialogViewHelper.java
package com.llw.mvplibrary.view.dialog;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import java.lang.ref.WeakReference;
public class DialogViewHelper {
private View mContentView;
private SparseArray<WeakReference> mViews;
public DialogViewHelper(Context context, int layoutResId) {
this();
mContentView = LayoutInflater.from(context).inflate(layoutResId, null);
}
public DialogViewHelper() {
mViews = new SparseArray<>();
}
public void setText(int viewId, CharSequence text) {
TextView tv = getView(viewId);
if (tv != null) {
tv.setText(text);
}
}
public T getView(int viewId) {
WeakReference weakReference = mViews.get(viewId);
View view = null;
if (weakReference != null) {
view = weakReference.get();
}
if (view == null) {
view = mContentView.findViewById(viewId);
if (view != null) {
mViews.put(viewId, new WeakReference<>(view));
}
}
return (T) view;
}
public void setOnClickListener(int viewId, View.OnClickListener onClickListener) {
View view = getView(viewId);
if (view != null) {
view.setOnClickListener(onClickListener);
}
}
public void setIcon(int viewId, int resId) {
ImageView iv = getView(viewId);
if (iv != null) {
iv.setImageResource(resId);
}
}
public void setContentView(View contentView) {
mContentView = contentView;
}
public View getContentView() {
return mContentView;
}
}
OK,弹窗有了,布局还没有的。在app下的layout中新建一个dialog_update_app_tip.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“@dimen/dp_270”
android:layout_height=“wrap_content”
android:orientation=“vertical”>
<ImageView
android:layout_width=“@dimen/dp_270”
android:layout_height=“wrap_content”
android:background=“@mipmap/pic_update” />
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:background=“@color/white”
android:gravity=“center_horizontal”
android:orientation=“vertical”
android:paddingBottom=“@dimen/dp_26”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginTop=“@dimen/dp_8”
android:text=“发现新版本”
android:textColor=“@color/black”
android:textSize=“@dimen/sp_16” />
<TextView
android:id=“@+id/tv_update_info”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_marginTop=“@dimen/dp_12”
android:paddingLeft=“@dimen/dp_24”
android:paddingRight=“@dimen/dp_24”
android:text=“再也不会错过更多精彩啦!” />
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“@dimen/dp_50”>
<TextView
android:id=“@+id/tv_cancel”
android:layout_width=“@dimen/dp_0”
android:layout_height=“match_parent”
android:layout_weight=“1”
android:background=“@drawable/shape_left_bg”
android:foreground=“@drawable/bg_white”
android:gravity=“center”
最后
我这里整理了一份完整的学习思维以及Android开发知识大全PDF。
当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
v != null) {
iv.setImageResource(resId);
}
}
public void setContentView(View contentView) {
mContentView = contentView;
}
public View getContentView() {
return mContentView;
}
}
OK,弹窗有了,布局还没有的。在app下的layout中新建一个dialog_update_app_tip.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“@dimen/dp_270”
android:layout_height=“wrap_content”
android:orientation=“vertical”>
<ImageView
android:layout_width=“@dimen/dp_270”
android:layout_height=“wrap_content”
android:background=“@mipmap/pic_update” />
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:background=“@color/white”
android:gravity=“center_horizontal”
android:orientation=“vertical”
android:paddingBottom=“@dimen/dp_26”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginTop=“@dimen/dp_8”
android:text=“发现新版本”
android:textColor=“@color/black”
android:textSize=“@dimen/sp_16” />
<TextView
android:id=“@+id/tv_update_info”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_marginTop=“@dimen/dp_12”
android:paddingLeft=“@dimen/dp_24”
android:paddingRight=“@dimen/dp_24”
android:text=“再也不会错过更多精彩啦!” />
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“@dimen/dp_50”>
<TextView
android:id=“@+id/tv_cancel”
android:layout_width=“@dimen/dp_0”
android:layout_height=“match_parent”
android:layout_weight=“1”
android:background=“@drawable/shape_left_bg”
android:foreground=“@drawable/bg_white”
android:gravity=“center”
最后
我这里整理了一份完整的学习思维以及Android开发知识大全PDF。
[外链图片转存中…(img-kFrM2Xie-1714412175678)]
当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!