拿来就用:Android 对sharedpreferences 数据进行加密

  • 创建一个公共和私人密钥,并将其存储使用Android密钥存储库中,因此,只有

  • 这个应用程序将能够访问键。

  • @param context

  • @throws InvalidAlgorithmParameterException

  • @throws NoSuchProviderException

  • @throws NoSuchAlgorithmException

*/

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)

public static KeyPair generateRSAKeyPair(Context context) throws

InvalidAlgorithmParameterException,

NoSuchProviderException, NoSuchAlgorithmException {

setAlias(SAMPLE_ALIAS);

//创建一个开始和结束时间,有效范围内的密钥对才会生成。

Calendar start = new GregorianCalendar();

Calendar end = new GregorianCalendar();

end.add(Calendar.YEAR, 1);//往后加一年

AlgorithmParameterSpec spec;

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {

//使用别名来检索的key 。这是一个key 的key !

spec = new KeyPairGeneratorSpec.Builder(context)

//使用别名来检索的关键。这是一个关键的关键!

.setAlias(mAlias)

// 用于生成自签名证书的主题 X500Principal 接受 RFC 1779/2253的专有名词

.setSubject(new X500Principal(“CN=” + mAlias))

//用于自签名证书的序列号生成的一对。

.setSerialNumber(BigInteger.valueOf(1337))

// 签名在有效日期范围内

.setStartDate(start.getTime())

.setEndDate(end.getTime())

.build();

} else {

//Android 6.0(或者以上)使用KeyGenparameterSpec.Builder 方式来创建,

// 允许你自定义允许的的关键属性和限制

spec = new KeyGenParameterSpec.Builder(mAlias, KeyProperties.PURPOSE_SIGN

| KeyProperties.PURPOSE_ENCRYPT

| KeyProperties.PURPOSE_DECRYPT)

.setKeySize(DEFAULT_KEY_SIZE)

.setUserAuthenticationRequired(false)

.setCertificateSubject(new X500Principal(“CN=” + mAlias))

//, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA384,

// KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_MD5)

.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)

.setCertificateNotBefore(start.getTime())

.setCertificateNotAfter(end.getTime())

.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)

.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)

.build();

}

KeyPairGenerator kpGenerator = KeyPairGenerator

.getInstance(SecurityConstants.TYPE_RSA,

SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);

kpGenerator.initialize(spec);

KeyPair kp = kpGenerator.generateKeyPair();

return kp;

}

/**

  • 用公钥对字符串进行加密

  • @param data 原文

*/

public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {

// 得到公钥

X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);

KeyFactory kf = KeyFactory.getInstance(SecurityConstants.TYPE_RSA);

PublicKey keyPublic = kf.generatePublic(keySpec);

// 加密数据

Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);

cp.init(Cipher.ENCRYPT_MODE, keyPublic);

return cp.doFinal(data);

}

/**

  • 私钥加密

  • @param data 待加密数据

  • @param privateKey 密钥

  • @return byte[] 加密数据

*/

public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) throws Exception {

// 得到私钥

PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);

KeyFactory kf = KeyFactory.getInstance(SecurityConstants.TYPE_RSA);

PrivateKey keyPrivate = kf.generatePrivate(keySpec);

// 数据加密

Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);

cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);

return cipher.doFinal(data);

}

/**

  • 公钥解密

  • @param data 待解密数据

  • @param publicKey 密钥

  • @return byte[] 解密数据

*/

public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {

// 得到公钥

X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);

KeyFactory kf = KeyFactory.getInstance(SecurityConstants.TYPE_RSA);

PublicKey keyPublic = kf.generatePublic(keySpec);

// 数据解密

Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);

cipher.init(Cipher.DECRYPT_MODE, keyPublic);

return cipher.doFinal(data);

}

/**

  • 使用私钥进行解密

*/

public static byte[] decryptByPrivateKey(byte[] encrypted) throws Exception {

KeyStore ks = KeyStore.getInstance(“AndroidKeyStore”);

ks.load(null);

if (mAlias == null) {

setAlias(SAMPLE_ALIAS);

}

//从Android加载密钥对密钥存储库中

KeyStore.Entry entry = ks.getEntry(mAlias, null);

if (entry == null) {

return null;

}

if (!(entry instanceof KeyStore.PrivateKeyEntry)) {

return null;

}

PrivateKey keyPrivate = ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();

// 解密数据

Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);

cp.init(Cipher.DECRYPT_MODE, keyPrivate);

byte[] arr = cp.doFinal(encrypted);

return arr;

}

/**

  • 用公钥对字符串进行分段加密

*/

public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {

int dataLen = data.length;

if (dataLen <= DEFAULT_BUFFERSIZE) {

return encryptByPublicKey(data, publicKey);

}

List allBytes = new ArrayList(2048);

int bufIndex = 0;

int subDataLoop = 0;

byte[] buf = new byte[DEFAULT_BUFFERSIZE];

for (int i = 0; i < dataLen; i++) {

buf[bufIndex] = data[i];

if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {

subDataLoop++;

if (subDataLoop != 1) {

for (byte b : DEFAULT_SPLIT) {

allBytes.add(b);

}

}

byte[] encryptBytes = encryptByPublicKey(buf, publicKey);

for (byte b : encryptBytes) {

allBytes.add(b);

}

bufIndex = 0;

if (i == dataLen - 1) {

buf = null;

} else {

buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];

}

}

}

byte[] bytes = new byte[allBytes.size()];

{

int i = 0;

for (Byte b : allBytes) {

bytes[i++] = b.byteValue();

}

}

return bytes;

}

/**

  • 分段加密

  • @param data 要加密的原始数据

  • @param privateKey 秘钥

*/

public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {

int dataLen = data.length;

if (dataLen <= DEFAULT_BUFFERSIZE) {

return encryptByPrivateKey(data, privateKey);

}

List allBytes = new ArrayList(2048);

int bufIndex = 0;

int subDataLoop = 0;

byte[] buf = new byte[DEFAULT_BUFFERSIZE];

for (int i = 0; i < dataLen; i++) {

buf[bufIndex] = data[i];

if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {

subDataLoop++;

if (subDataLoop != 1) {

for (byte b : DEFAULT_SPLIT) {

allBytes.add(b);

}

}

byte[] encryptBytes = encryptByPrivateKey(buf, privateKey);

for (byte b : encryptBytes) {

allBytes.add(b);

}

bufIndex = 0;

if (i == dataLen - 1) {

buf = null;

} else {

buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];

}

}

}

byte[] bytes = new byte[allBytes.size()];

{

int i = 0;

for (Byte b : allBytes) {

bytes[i++] = b.byteValue();

}

}

return bytes;

}

/**

  • 公钥分段解密

  • @param encrypted 待解密数据

  • @param publicKey 密钥

*/

public static byte[] decryptByPublicKeyForSpilt(byte[] encrypted, byte[] publicKey) throws Exception {

int splitLen = DEFAULT_SPLIT.length;

if (splitLen <= 0) {

return decryptByPublicKey(encrypted, publicKey);

}

int dataLen = encrypted.length;

List allBytes = new ArrayList(1024);

int latestStartIndex = 0;

for (int i = 0; i < dataLen; i++) {

byte bt = encrypted[i];

boolean isMatchSplit = false;

if (i == dataLen - 1) {

// 到data的最后了

byte[] part = new byte[dataLen - latestStartIndex];

System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);

byte[] decryptPart = decryptByPublicKey(part, publicKey);

for (byte b : decryptPart) {

allBytes.add(b);

}

latestStartIndex = i + splitLen;

i = latestStartIndex - 1;

} else if (bt == DEFAULT_SPLIT[0]) {

// 这个是以split[0]开头

if (splitLen > 1) {

if (i + splitLen < dataLen) {

// 没有超出data的范围

for (int j = 1; j < splitLen; j++) {

if (DEFAULT_SPLIT[j] != encrypted[i + j]) {

break;

}

if (j == splitLen - 1) {

// 验证到split的最后一位,都没有break,则表明已经确认是split段

isMatchSplit = true;

}

}

}

} else {

// split只有一位,则已经匹配了

isMatchSplit = true;

}

}

if (isMatchSplit) {

byte[] part = new byte[i - latestStartIndex];

System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);

byte[] decryptPart = decryptByPublicKey(part, publicKey);

for (byte b : decryptPart) {

allBytes.add(b);

}

latestStartIndex = i + splitLen;

i = latestStartIndex - 1;

}

}

byte[] bytes = new byte[allBytes.size()];

{

int i = 0;

for (Byte b : allBytes) {

bytes[i++] = b.byteValue();

}

}

return bytes;

}

/**

  • 使用私钥分段解密

*/

public static byte[] decryptByPrivateKeyForSpilt(byte[] encrypted) throws Exception {

int splitLen = DEFAULT_SPLIT.length;

if (splitLen <= 0) {

return decryptByPrivateKey(encrypted);

}

int dataLen = encrypted.length;

List allBytes = new ArrayList(1024);

int latestStartIndex = 0;

for (int i = 0; i < dataLen; i++) {

byte bt = encrypted[i];

boolean isMatchSplit = false;

if (i == dataLen - 1) {

// 到data的最后了

byte[] part = new byte[dataLen - latestStartIndex];

System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);

byte[] decryptPart = decryptByPrivateKey(part);

for (byte b : decryptPart) {

allBytes.add(b);

}

latestStartIndex = i + splitLen;

i = latestStartIndex - 1;

} else if (bt == DEFAULT_SPLIT[0]) {

// 这个是以split[0]开头

if (splitLen > 1) {

if (i + splitLen < dataLen) {

// 没有超出data的范围

for (int j = 1; j < splitLen; j++) {

if (DEFAULT_SPLIT[j] != encrypted[i + j]) {

break;

}

if (j == splitLen - 1) {

// 验证到split的最后一位,都没有break,则表明已经确认是split段

isMatchSplit = true;

}

}

}

} else {

// split只有一位,则已经匹配了

isMatchSplit = true;

}

}

if (isMatchSplit) {

byte[] part = new byte[i - latestStartIndex];

System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);

byte[] decryptPart = decryptByPrivateKey(part);

for (byte b : decryptPart) {

allBytes.add(b);

}

latestStartIndex = i + splitLen;

i = latestStartIndex - 1;

}

}

byte[] bytes = new byte[allBytes.size()];

{

int i = 0;

for (Byte b : allBytes) {

bytes[i++] = b.byteValue();

}

}

return bytes;

}

/**

  • 通过字符串生成私钥,转换服务器传递过来的私钥

*/

public static PrivateKey getPrivateKey(String privateKeyData) {

PrivateKey privateKey = null;

try {

byte[] decodeKey = Base64Decoder.decodeToBytes(privateKeyData);

PKCS8EncodedKeySpec x509 = new PKCS8EncodedKeySpec(decodeKey);//创建x509证书封装类

KeyFactory keyFactory = KeyFactory.getInstance(“RSA”);//指定RSA

privateKey = keyFactory.generatePrivate(x509);//生成私钥

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

} catch (InvalidKeySpecException e) {

e.printStackTrace();

}

return privateKey;

}

/**

  • 通过字符串生成公钥,转换服务器传递过来的公钥

*/

public static PublicKey getPublicKey(String publicKeyData) {

PublicKey publicKey = null;

try {

byte[] decodeKey = Base64Decoder.decodeToBytes(publicKeyData);

X509EncodedKeySpec x509 = new X509EncodedKeySpec(decodeKey);

KeyFactory keyFactory = KeyFactory.getInstance(“RSA”);

publicKey = keyFactory.generatePublic(x509);

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

} catch (InvalidKeySpecException e) {

e.printStackTrace();

}

return publicKey;

}

/**

  • 判断是否创建过秘钥

  • @return

  • @throws KeyStoreException

  • @throws CertificateException

  • @throws NoSuchAlgorithmException

  • @throws IOException

  • @throws UnrecoverableEntryException

*/

public static boolean isHaveKeyStore() {

try {

KeyStore ks = KeyStore.getInstance(“AndroidKeyStore”);

ks.load(null);

if (mAlias == null) {

setAlias(SAMPLE_ALIAS);

}

//从Android加载密钥对密钥存储库中

KeyStore.Entry entry = ks.getEntry(mAlias, null);

if (entry == null) {

return false;

}

} catch (KeyStoreException e) {

e.printStackTrace();

return false;

} catch (CertificateException e) {

e.printStackTrace();

return false;

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

return false;

} catch (IOException e) {

e.printStackTrace();

return false;

} catch (UnrecoverableEntryException e) {

e.printStackTrace();

return false;

}

return true;

}

/**

  • 获得本地AndroidKeyStore中的公钥

  • @return

*/

public static PublicKey getLocalPublicKey() {

try {

KeyStore ks = KeyStore.getInstance(“AndroidKeyStore”);

ks.load(null);

if (mAlias == null) {

setAlias(SAMPLE_ALIAS);

}

//从Android加载密钥对密钥存储库中

KeyStore.Entry entry = ks.getEntry(mAlias, null);

if (entry == null) {

return null;

}

if (!(entry instanceof KeyStore.PrivateKeyEntry)) {

return null;

}

PublicKey publicKey = ((KeyStore.PrivateKeyEntry) entry).getCertificate().getPublicKey();

return publicKey;

} catch (KeyStoreException e) {

e.printStackTrace();

return null;

} catch (CertificateException e) {

e.printStackTrace();

return null;

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

return null;

} catch (IOException e) {

e.printStackTrace();

return null;

} catch (UnrecoverableEntryException e) {

e.printStackTrace();

return null;

}

}

}

package tsou.com.encryption.androidkeystoresign;

public class SecurityConstants {

public static final String KEYSTORE_PROVIDER_ANDROID_KEYSTORE = “AndroidKeyStore”;

public static final String TYPE_RSA = “RSA”;

public static final String TYPE_DSA = “DSA”;

public static final String TYPE_BKS = “BKS”;

public static final String SIGNATURE_SHA256withRSA = “SHA256withRSA”;

public static final String SIGNATURE_SHA512withRSA = “SHA512withRSA”;

}

  • 加密封装在SPSecuredUtils秘钥中方便拿过来直接用

package tsou.com.encryption.sp;

import android.content.Context;

import android.content.SharedPreferences;

import android.util.Base64;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.security.interfaces.RSAPublicKey;

import java.util.Map;

import tsou.com.encryption.AndroidKeyStoreRSA.AndroidKeyStoreRSAUtils;

import tsou.com.encryption.aescbc.Base64Decoder;

import tsou.com.encryption.aescbc.Base64Encoder;

/**

  • Created by zb666 on 2017/2/9.

*/

public class SPSecuredUtils {

/**

  • 保存在手机里面的文件名

*/

public static final String FILE_NAME = “sp_secured”;

private static SharedPreferences mSharedPreferences;

/**

  • 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法

  • @param context

  • @param key

  • @param object

  • @param publicKey

*/

public static void put(Context context, String key, Object object, RSAPublicKey publicKey) {

SharedPreferences sp = context.getSharedPreferences(FILE_NAME,

Context.MODE_PRIVATE);

SharedPreferences.Editor editor = sp.edit();

// byte[] encryptBytes = AndroidKeyStoreRSAUtils.encryptByPublicKeyForSpilt(encryptionString.getBytes(),

// publicKey.getEncoded());

try {

if (object instanceof String) {

byte[] encryptBytes = AndroidKeyStoreRSAUtils.encryptByPublicKey(((String) object).getBytes(),

publicKey.getEncoded());

editor.putString(key, Base64Encoder.encode(encryptBytes));

} else if (object instanceof Integer) {

put(context, key, Integer.toString((Integer) object), publicKey);

} else if (object instanceof Boolean) {

put(context, key, Boolean.toString((Boolean) object), publicKey);

} else if (object instanceof Float) {

put(context, key, Float.toString((Float) object), publicKey);

} else if (object instanceof Long) {

put(context, key, Long.toString((Long) object), publicKey);

} else {

put(context, key, object.toString(), publicKey);

}

SharedPreferencesCompat.apply(editor);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

  • 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值

  • @param context

  • @param key

  • @param defaultObject

  • @return

*/

public static Object get(Context context, String key, Object defaultObject) {

SharedPreferences sp = context.getSharedPreferences(FILE_NAME,

Context.MODE_PRIVATE);

// byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKeyForSpilt(

// Base64Decoder.decodeToBytes(decodeString));

try {

if (defaultObject instanceof String) {

String string = sp.getString(key, (String) defaultObject);

if (!string.equals((String) defaultObject)) {

byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKey(

Base64Decoder.decodeToBytes(string));

return new String(decryptBytes);

}

return (String) defaultObject;

} else if (defaultObject instanceof Integer) {

String string = sp.getString(key, Integer.toString((Integer) defaultObject));

if (!string.equals(Integer.toString((Integer) defaultObject))) {

byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKey(

Base64Decoder.decodeToBytes(string));

return Integer.valueOf(new String(decryptBytes));

}

return (Integer) defaultObject;

} else if (defaultObject instanceof Boolean) {

String string = sp.getString(key, Boolean.toString((Boolean) defaultObject));

if (!string.equals(Boolean.toString((Boolean) defaultObject))) {

byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKey(

Base64Decoder.decodeToBytes(string));

return Boolean.valueOf(new String(decryptBytes));

}

return (Boolean) defaultObject;

} else if (defaultObject instanceof Float) {

String string = sp.getString(key, Float.toString((Float) defaultObject));

if (!string.equals(Float.toString((Float) defaultObject))) {

byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKey(

Base64Decoder.decodeToBytes(string));

return Float.valueOf(new String(decryptBytes));

}

return (Float) defaultObject;

} else if (defaultObject instanceof Long) {

String string = sp.getString(key, Long.toString((Long) defaultObject));

if (!string.equals(Long.toString((Long) defaultObject))) {

byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKey(

Base64Decoder.decodeToBytes(string));

return Long.valueOf(new String(decryptBytes));

}

return (Long) defaultObject;

}else if (defaultObject instanceof Double){

String string = sp.getString(key, Double.toString((Double) defaultObject));

if (!string.equals(Double.toString((Double) defaultObject))) {

byte[] decryptBytes = AndroidKeyStoreRSAUtils.decryptByPrivateKey(

Base64Decoder.decodeToBytes(string));

return Double.valueOf(new String(decryptBytes));

}

return (Double) defaultObject;

}

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

/**

  • 将对象储存到sharepreference

  • @param key

  • @param device

  • @param

*/

public static boolean saveDeviceData(Context context, String key, T device, RSAPublicKey publicKey) {

if (mSharedPreferences == null) {

mSharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);

}

ByteArrayOutputStream baos = new ByteArrayOutputStream();

try { //Device为自定义类

// 创建对象输出流,并封装字节流

ObjectOutputStream oos = new ObjectOutputStream(baos);

// 将对象写入字节流

oos.writeObject(device);

// 将字节流编码成base64的字符串

String oAuth_Base64 = new String(Base64.encode

(baos.toByteArray(), Base64.DEFAULT));

byte[] encryptBytes = AndroidKeyStoreRSAUtils.encryptByPublicKey(oAuth_Base64.getBytes(),

publicKey.getEncoded());

mSharedPreferences.edit().putString(key, Base64Encoder.encode(encryptBytes)).apply();

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**

  • 将对象从shareprerence中取出来

  • @param key

  • @param

  • @return

*/

public static T getDeviceData(Context context, String key) {

if (mSharedPreferences == null) {

mSharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);

}

try {

T device = null;

String productBase64 = mSharedPreferences.getString(key, null);

if (productBase64 == null) {

return null;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

NAME, Context.MODE_PRIVATE);

}

try {

T device = null;

String productBase64 = mSharedPreferences.getString(key, null);

if (productBase64 == null) {

return null;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-OVqdWdkG-1713623953151)]

[外链图片转存中…(img-sfzBZ9Br-1713623953153)]

[外链图片转存中…(img-xcM8PZJM-1713623953154)]

[外链图片转存中…(img-PdUSwho0-1713623953155)]

[外链图片转存中…(img-cj0Imovi-1713623953155)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-FeMwpL2P-1713623953156)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值