- @param response GetDiscernResultResponse
*/
void getDiscernResultResponse(GetDiscernResultResponse response);
/**
-
获取图像识别结果失败
-
@param throwable 异常
*/
void getDiscernResultFailed(Throwable throwable);
/**
-
搜索物品返回
-
@param response TrashResponse
*/
void getSearchResponse(TrashResponse response);
/**
-
搜索物品异常返回
-
@param throwable 异常
*/
void getSearchResponseFailed(Throwable throwable);
}
}
鉴权方法中的几个全局变量在Constant中定义,
/**
- 鉴权Token
*/
public static final String TOKEN = “accessToken”;
/**
- 获取Token的时间
*/
public static final String GET_TOKEN_TIME = “getTokenTime”;
/**
- Token有效期
*/
public static final String TOKEN_VALID_PERIOD = “tokenValidPeriod”;
/**
- 百度鉴权认证参数值
*/
public static final String GRANT_TYPE = “client_credentials”;
/**
- 百度图像识别 APP ID GoodTrash
*/
public static final String APP_ID = “23943795”;
/**
- 百度图像识别 APP Key GoodTrash
*/
public static final String API_KEY = “PAUCX7vSAd4ZBwv897GAfhEQ”;
请注意,这里的值是我在百度开放平台上注册应用时生成的,请替换为自己的。
下面回到ImageInputActivity,修改代码后如下:
package com.llw.goodtrash.ui;
import android.os.Bundle;
import com.llw.goodtrash.R;
import com.llw.goodtrash.contract.ImageContract;
import com.llw.goodtrash.model.GetDiscernResultResponse;
import com.llw.goodtrash.model.GetTokenResponse;
import com.llw.goodtrash.model.TrashResponse;
import com.llw.mvplibrary.mvp.MvpActivity;
/**
-
图像输入物品进行垃圾分类
-
@author llw
-
@date 2021/4/7 11:04
*/
public class ImageInputActivity extends MvpActivity<ImageContract.ImagePresenter> implements ImageContract.ImageView {
@Override
public void initData(Bundle savedInstanceState) {
}
@Override
public int getLayoutId() {
return R.layout.activity_image_input;
}
@Override
protected ImageContract.ImagePresenter createPresenter() {
return new ImageContract.ImagePresenter();
}
@Override
public void getTokenResponse(GetTokenResponse response) {
}
@Override
public void getTokenFailed(Throwable throwable) {
}
@Override
public void getDiscernResultResponse(GetDiscernResultResponse response) {
}
@Override
public void getDiscernResultFailed(Throwable throwable) {
}
@Override
public void getSearchResponse(TrashResponse response) {
}
@Override
public void getSearchResponseFailed(Throwable throwable) {
}
}
这里使用了MVP,通过P来处理M和V,三个网络请求对应六个返回,下面进行页面的初始化
先声明一些变量
private static final String TAG = “ImageInputActivity”;
/**
- 打开相册
*/
private static final int OPEN_ALBUM_CODE = 100;
/**
- 打开相机
*/
private static final int TAKE_PHOTO_CODE = 101;
/**
- 鉴权Toeken
*/
private String accessToken;
private Toolbar toolbar;
private ImageView ivPicture;
private EditText etImageUrl;
private LinearLayout layRecognitionResult,layClassificationResult;
private RecyclerView rvRecognitionResult,rvClassificationResult;
private RxPermissions rxPermissions;
private File outputImage;
然后新增一个initView的方法。
/**
- 初始化
*/
private void initView() {
toolbar = findViewById(R.id.toolbar);
ivPicture = findViewById(R.id.iv_picture);
etImageUrl = findViewById(R.id.et_image_url);
findViewById(R.id.btn_web_picture).setOnClickListener(this);
findViewById(R.id.btn_open_album).setOnClickListener(this);
findViewById(R.id.btn_take_photo).setOnClickListener(this);
layRecognitionResult = findViewById(R.id.lay_recognition_result);
layClassificationResult = findViewById(R.id.lay_classification_result);
rvRecognitionResult = findViewById(R.id.rv_recognition_result);
rvClassificationResult = findViewById(R.id.rv_classification_result);
//设置页面状态栏
setStatubar(this, R.color.white, true);
back(toolbar, true);
rxPermissions = new RxPermissions(this);
}
然后在initData中调用它。
@Override
public void initData(Bundle savedInstanceState) {
initView();
}
然后继承控件的点击监听回调
重写onClick方法。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_web_picture://网络图片
break;
case R.id.btn_open_album://相册图片
break;
case R.id.btn_take_photo://拍照图片
break;
default:
break;
}
}
由于Token存在有效期的关系,因此不需要每一次都去获取,所以可以在第一次获取之后存入缓存,只要Token在有效期内都可以直接从缓存中获取,这样就可以少请求一次网络。
下面先写一个缓存的工具类。
在utils包下新增一个SPUtils类,里面的代码如下:
package com.llw.goodtrash.utils;
import android.content.Context;
import android.content.SharedPreferences;
/**
-
SharedPreferences工具类
-
@author llw
*/
public class SPUtils {
private static final String NAME = “config”;
public static void putBoolean(String key, boolean value, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putBoolean(key, value).commit();
}
public static boolean getBoolean(String key, boolean defValue, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getBoolean(key, defValue);
}
public static void putString(String key, String value, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putString(key, value).commit();
}
public static String getString(String key, String defValue, Context context) {
if (context != null) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getString(key, defValue);
}
return “”;
}
public static void putInt(String key, int value, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putInt(key, value).commit();
}
public static int getInt(String key, int defValue, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getInt(key, defValue);
}
public static void putLong(String key, long value, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().putLong(key, value).commit();
}
public static long getLong(String key, long defValue, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
return sp.getLong(key, defValue);
}
public static void remove(String key, Context context) {
SharedPreferences sp = context.getSharedPreferences(NAME,
Context.MODE_PRIVATE);
sp.edit().remove(key).commit();
}
}
然后在ImageInputActivity中写一个获取Token的方法,代码如下:
/**
- 获取鉴权Token
*/
private String getAccessToken() {
String token = SPUtils.getString(Constant.TOKEN, null, this);
if (token == null) {
//访问API获取接口
mPresenter.getToken();
} else {
//则判断Token是否过期
if (isTokenExpired()) {
//过期
mPresenter.getToken();
} else {
accessToken = token;
}
}
return accessToken;
}
这里你的isTokenExpired()方法会报红,这是一个用来判断Token是否过期的方法。里面的代码如下:
/**
-
Token是否过期
-
@return
*/
private boolean isTokenExpired() {
//获取Token的时间
long getTokenTime = SPUtils.getLong(Constant.GET_TOKEN_TIME, 0, this);
//获取Token的有效时间
long effectiveTime = SPUtils.getLong(Constant.TOKEN_VALID_PERIOD, 0, this);
//获取当前系统时间
long currentTime = System.currentTimeMillis() / 1000;
return (currentTime - getTokenTime) >= effectiveTime;
}
刚才在获取Token方法中,通过mPresenter.getToken();发起了一个网络请求,返回的结果有成功和失败,成功后会有Token返回,失败了会有失败原因返回。
修改如下方法:
/**
-
获取鉴权Token成功返回
-
@param response GetTokenResponse
*/
@Override
public void getTokenResponse(GetTokenResponse response) {
if (response != null) {
//鉴权Token
accessToken = response.getAccess_token();
//过期时间 秒
long expiresIn = response.getExpires_in();
//当前时间 秒
long currentTimeMillis = System.currentTimeMillis() / 1000;
//放入缓存
SPUtils.putString(Constant.TOKEN, accessToken, this);
SPUtils.putLong(Constant.GET_TOKEN_TIME, currentTimeMillis, this);
SPUtils.putLong(Constant.TOKEN_VALID_PERIOD, expiresIn, this);
} else {
showMsg(“Token为null”);
}
}
/**
-
获取Token失败返回
-
@param throwable 异常
*/
@Override
public void getTokenFailed(Throwable throwable) {
Log.d(TAG, “Token获取失败:”+throwable.toString());
}
我已经写了注释了,那么你就知道这个方法是做什么的了。
我的想法是当我点击这个网络图片的按钮时,页面出现一个输入框,当我输入完成之后,点击键盘的回车直接识别,虽后隐藏这个输入框,嗯,就是这样。
首先来写点击网络图片时的业务逻辑代码。
case R.id.btn_web_picture://网络图片
etImageUrl.setVisibility(View.VISIBLE);
etImageUrl.setOnKeyListener((view, keyCode, keyEvent) -> {
if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_UP) {
String webImageUrl = etImageUrl.getText().toString().trim();
String defaultWebImageUrl = “https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg”;
String imageUrl = “”.equals(webImageUrl) ?defaultWebImageUrl : webImageUrl;
//识别网络图片Url
showLoadingDialog();
Glide.with(context).load(imageUrl).into(ivPicture);
mPresenter.getDiscernResult(accessToken,null,imageUrl);
etImageUrl.setVisibility(View.GONE);
}
return false;
});
break;
在这里发起了一个图片识别的请求,下面来看返回的方法处理。
/**
-
图片识别成功返回
-
@param response GetDiscernResultResponse
*/
@Override
public void getDiscernResultResponse(GetDiscernResultResponse response) {
if(response == null){
hideLoadingDialog();
showMsg(“未获得相应的识别结果”);
return;
}
ivPicture.setVisibility(View.VISIBLE);
List<GetDiscernResultResponse.ResultBean> result = response.getResult();
if (result != null && result.size() > 0) {
//显示识别结果
showDiscernResult(result);
} else {
hideLoadingDialog();
showMsg(“未获得相应的识别结果”);
}
}
/**
-
图片识别成功失败
-
@param throwable 异常
*/
@Override
public void getDiscernResultFailed(Throwable throwable) {
Log.d(TAG, “图片识别失败:”+throwable.toString());
}
返回成功之后,如果数据不为空则显示要识别的图片,然后通过列表展示识别结果数据,
首先得有一个识别的结果列表item布局,item_distinguish_result_rv.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=“@+id/item_distinguish_rv”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_marginBottom=“1dp”
android:background=“#FFF”
android:foreground=“?attr/selectableItemBackground”
android:padding=“16dp”>
<TextView
android:id=“@+id/tv_keyword”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textColor=“#000”
android:textSize=“16sp” />
<TextView
android:id=“@+id/tv_root”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_below=“@+id/tv_keyword”
android:layout_marginTop=“@dimen/dp_4”
android:textSize=“14sp” />
<TextView
android:id=“@+id/tv_score”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentEnd=“true” />
下面写适配器代码,在adapter下新建一个DiscernResultAdapter类,代码如下:
package com.llw.goodtrash.adapter;
import androidx.annotation.Nullable;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.goodtrash.R;
import com.llw.goodtrash.model.GetDiscernResultResponse;
import java.util.List;
/**
-
识别结果列表适配器
-
@author llw
*/
public class DiscernResultAdapter extends BaseQuickAdapter<GetDiscernResultResponse.ResultBean, BaseViewHolder> {
public DiscernResultAdapter(int layoutResId, @Nullable List<GetDiscernResultResponse.ResultBean> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, GetDiscernResultResponse.ResultBean item) {
helper.setText(R.id.tv_keyword,item.getKeyword())
.setText(R.id.tv_root,item.getRoot())
.setText(R.id.tv_score,String.valueOf(item.getScore()))
.addOnClickListener(R.id.item_distinguish_rv);
}
}
适配器和列表item都写好了,下面回到ImageInputActivity中新增方法showDiscernResult(),代码如下:
/**
-
显示识别的结果列表
-
@param result
*/
private void showDiscernResult(List<GetDiscernResultResponse.ResultBean> result) {
DiscernResultAdapter adapter = new DiscernResultAdapter(R.layout.item_distinguish_result_rv, result);
rvRecognitionResult.setLayoutManager(new LinearLayoutManager(this));
rvRecognitionResult.setAdapter(adapter);
//隐藏加载
hideLoadingDialog();
//显示弹窗
layRecognitionResult.setVisibility(View.VISIBLE);
layClassificationResult.setVisibility(View.GONE);
}
下面来运行一下:
不一定第一次就能识别出来,看你的人品。下面识别相册图片
下面写点击这个相册图片按钮的业务逻辑,如下:
case R.id.btn_open_album://相册图片
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rxPermissions.request(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(grant -> {
if (grant) {
//获得权限
openAlbum();
} else {
showMsg(“未获取到权限”);
}
});
} else {
openAlbum();
}
break;
打开相册的openAlbum()方法,代码如下:
/**
- 打开相册
*/
private void openAlbum() {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType(“image/*”);
startActivityForResult(intent, OPEN_ALBUM_CODE);
}
下面就是从相册返回的处理
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
showLoadingDialog();
if (requestCode == OPEN_ALBUM_CODE) {
//打开相册返回
String[] filePathColumns = {MediaStore.Images.Media.DATA};
final Uri imageUri = Objects.requireNonNull(data).getData();
Cursor cursor = getContentResolver().query(imageUri, filePathColumns, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumns[0]);
//获取图片路径
String imagePath = cursor.getString(columnIndex);
cursor.close();
//识别
localImageDiscern(imagePath);
}
} else {
showMsg(“什么都没有”);
}
}
通过相册图片获取图片的路径,通过localImageDiscern方法将这个路径去转字节,再转base64。代码如下:
/**
- 本地图片识别
*/
private void localImageDiscern(String imagePath) {
try {
String token = getAccessToken();
//通过图片路径显示图片
Glide.with(this).load(imagePath).into(ivPicture);
//按字节读取文件
byte[] imgData = FileUtil.readFileByBytes(imagePath);
//字节转Base64
String imageBase64 = Base64Util.encode(imgData);
//本地图片识别
mPresenter.getDiscernResult(token, imageBase64, null);
} catch (IOException e) {
e.printStackTrace();
}
}
这里面还有两个工具类FileUtil和Base64Util。下面在utils包下新建一个FileUtil类,里面的代码如下:
package com.llw.goodtrash.utils;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
- 文件读取工具类
*/
public class FileUtil {
/**
- 读取文件内容,作为字符串返回
*/
public static String readFileAsString(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException(filePath);
}
if (file.length() > 1024 * 1024 * 1024) {
throw new IOException(“File is too large”);
}
StringBuilder sb = new StringBuilder((int) (file.length()));
// 创建字节输入流
FileInputStream fis = new FileInputStream(filePath);
// 创建一个长度为10240的Buffer
byte[] bbuf = new byte[10240];
// 用于保存实际读取的字节数
int hasRead = 0;
while ( (hasRead = fis.read(bbuf)) > 0 ) {
sb.append(new String(bbuf, 0, hasRead));
}
fis.close();
return sb.toString();
}
/**
- 根据文件路径读取byte[] 数组
*/
public static byte[] readFileByBytes(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException(filePath);
} else {
ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
short bufSize = 1024;
byte[] buffer = new byte[bufSize];
int len1;
while (-1 != (len1 = in.read(buffer, 0, bufSize))) {
bos.write(buffer, 0, len1);
}
byte[] var7 = bos.toByteArray();
return var7;
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException var14) {
var14.printStackTrace();
}
bos.close();
}
}
}
}
然后是Base64Util类,代码如下:
package com.llw.goodtrash.utils;
/**
- Base64 工具类
*/
public class Base64Util {
private static final char last2byte = (char) Integer.parseInt(“00000011”, 2);
private static final char last4byte = (char) Integer.parseInt(“00001111”, 2);
private static final char last6byte = (char) Integer.parseInt(“00111111”, 2);
private static final char lead6byte = (char) Integer.parseInt(“11111100”, 2);
private static final char lead4byte = (char) Integer.parseInt(“11110000”, 2);
private static final char lead2byte = (char) Integer.parseInt(“11000000”, 2);
private static final char[] encodeTable = new char[]{‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’, ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’, ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘+’, ‘/’};
public Base64Util() {
}
public static String encode(byte[] from) {
StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);
int num = 0;
char currentByte = 0;
int i;
for (i = 0; i < from.length; ++i) {
for (num %= 8; num < 8; num += 6) {
switch (num) {
case 0:
currentByte = (char) (from[i] & lead6byte);
currentByte = (char) (currentByte >>> 2);
case 1:
case 3:
case 5:
default:
break;
case 2:
currentByte = (char) (from[i] & last6byte);
break;
case 4:
currentByte = (char) (from[i] & last4byte);
currentByte = (char) (currentByte << 2);
if (i + 1 < from.length) {
currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
}
break;
case 6:
currentByte = (char) (from[i] & last2byte);
currentByte = (char) (currentByte << 4);
if (i + 1 < from.length) {
currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
}
}
to.append(encodeTable[currentByte]);
}
}
if (to.length() % 4 != 0) {
for (i = 4 - to.length() % 4; i > 0; --i) {
to.append(“=”);
}
}
return to.toString();
}
}
下面可以直接运行了。
下面该识别拍照图片
点击拍照图片按钮的业务逻辑代码,
case R.id.btn_take_photo://拍照图片
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rxPermissions.request(
Manifest.permission.CAMERA)
.subscribe(grant -> {
if (grant) {
//获得权限
turnOnCamera();
} else {
showMsg(“未获取到权限”);
}
});
} else {
turnOnCamera();
}
break;
turnOnCamera是用来打开相机的方法,里面的代码如下:
/**
- 打开相机
*/
private void turnOnCamera() {
SimpleDateFormat timeStampFormat = new SimpleDateFormat(“HH_mm_ss”);
String filename = timeStampFormat.format(new Date());
//创建File对象
outputImage = new File(getExternalCacheDir(), “takePhoto” + filename + “.jpg”);
Uri imageUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageUri = FileProvider.getUriForFile(this,
“com.llw.goodtrash.fileprovider”, outputImage);
} else {
imageUri = Uri.fromFile(outputImage);
}
//打开相机
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO_CODE);
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
![](https://i-blog.csdnimg.cn/blog_migrate/249b6431da9fcc963a0bd9b791c7c05f.jpeg)
最后
答应大伙的备战金三银四,大厂面试真题来啦!
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析
资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
URE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO_CODE);
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-DYFvjXu5-1711546168679)]
[外链图片转存中…(img-aiyzolAb-1711546168680)]
[外链图片转存中…(img-Ehjn8Bsc-1711546168681)]
[外链图片转存中…(img-nawxLWAb-1711546168681)]
[外链图片转存中…(img-W52aFkTs-1711546168681)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
![](https://i-blog.csdnimg.cn/blog_migrate/249b6431da9fcc963a0bd9b791c7c05f.jpeg)
最后
答应大伙的备战金三银四,大厂面试真题来啦!
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
[外链图片转存中…(img-hZ6HKhYp-1711546168682)]
《379页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-b5kjYkrq-1711546168682)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-9cn9J7Fy-1711546168682)]
腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析
[外链图片转存中…(img-8AQYU65a-1711546168683)]
资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!