Android应用Preference相关及源码浅析(SharePreferences篇),面试的时候突然遇到答不上的问题怎么办

在我们开发Android过程中数据的存储会有很多种解决方案,譬如常见的文件存储、数据库存储、网络云存储等,但是Android系统为咱们提供了更加方便的一种数据存储方式,那就是SharePreference数据存储。其实质也就是文件存储,只不过是符合XML标准的文件存储而已,而且其也是Android中比较常用的简易型数据存储解决方案。

我们在这里不仅要探讨SharePreference如何使用,还要探讨其源码是如何实现的;同时还要在下一篇博客讨论由SharePreference衍生出来的Preference相关Android组件实现,不过有意思的是我前几天在网上看见有人对google的Preference有很大争议,有人说他就是鸡肋,丑而不灵活自定义,有人说他是一个标准,很符合设计思想,至于谁说的有道理,我想看完本文和下一篇文章你自然会有自己的观点看法的,还有一点就是关于使用SharePreference耗时问题也是一个争议,分析完再说吧,那就现在开始分析吧(基于API 22源码)。

这里写图片描述

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

2 SharePreferences基本使用实例


在Android提供的几种数据存储方式中SharePreference属于轻量级的键值存储方式,以XML文件方式保存数据,通常用来存储一些用户行为开关状态等,也就是说SharePreference一般的存储类型都是一些常见的数据类型(PS:当然也可以存储一些复杂对象,不过需要曲线救国,下面会给出存储复杂对象的解决方案的)。

在我们平时应用开发时或多或少都会用到SharePreference,这里就先给出一个常见的使用实例,具体如下:

public class MainActivity extends ActionBarActivity {

private SharedPreferences mSharedPreferences;

private SharedPreferences mSharedPreferencesContext;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initTest();

}

private void initTest() {

mSharedPreferencesContext = getSharedPreferences(“Test”, MODE_PRIVATE);

mSharedPreferences = getPreferences(MODE_PRIVATE);

SharedPreferences.Editor editor = mSharedPreferencesContext.edit();

editor.putBoolean(“saveed”, true);

Set set = new HashSet<>();

set.add(“aaaaa”);

set.add(“bbbbbbbb”);

editor.putStringSet(“content”, set);

editor.commit();

SharedPreferences.Editor editorActivity = mSharedPreferences.edit();

editorActivity.putString(“name”, “haha”);

editorActivity.commit();

}

}

运行之后adb进入data应用包下的shared_prefs目录可以看见如下结果:

-rw-rw---- u0_a84 u0_a84 108 2015-08-23 10:34 MainActivity.xml

-rw-rw---- u0_a84 u0_a84 214 2015-08-23 10:34 Test.xml

其内容分别如下:

at Test.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>

aaaaa

bbbbbbbb

at MainActivity.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>

haha

可以看见SharePreference的使用还是非常简单easy的,所以不做太多的使用说明,我们接下来重点依然是关注其实现原理。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

3 SharePreferences源码分析


3-1 从SharePreferences接口说起

其实讲句实话,SharePreference的源码没啥深奥的东东,其实质和ACache类似,都算时比较独立的东东。分析之前我们还是先来看下SharePreference这个类的源码,具体如下:

//你会发现SharedPreferences其实是一个接口而已

public interface SharedPreferences {

//定义一个用于在数据发生改变时调用的监听回调

public interface OnSharedPreferenceChangeListener {

//哪个key对应的值发生变化

void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);

}

//编辑SharedPreferences对象设定值的接口

public interface Editor {

//一些编辑存储基本数据key-value的接口方法

Editor putString(String key, String value);

Editor putStringSet(String key, Set values);

Editor putInt(String key, int value);

Editor putLong(String key, long value);

Editor putFloat(String key, float value);

Editor putBoolean(String key, boolean value);

//删除指定key的键值对

Editor remove(String key);

//清空所有键值对

Editor clear();

//同步的提交到硬件磁盘

boolean commit();

//将修改数据原子提交到内存,而后异步提交到硬件磁盘

void apply();

}

//获取指定数据

Map<String, ?> getAll();

String getString(String key, String defValue);

Set getStringSet(String key, Set defValues);

int getInt(String key, int defValue);

long getLong(String key, long defValue);

float getFloat(String key, float defValue);

boolean getBoolean(String key, boolean defValue);

boolean contains(String key);

//针对preferences创建一个新的Editor对象,通过它你可以修改preferences里的数据,并且原子化的将这些数据提交回SharedPreferences对象

Editor edit();

//注册一个回调函数,当一个preference发生变化时调用

void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);

//注销一个之前(注册)的回调函数

void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);

}

很明显的可以看见,SharePreference源码其实是很简单的。既然这里说了SharePreference类只是一个接口,那么他一定有自己的实现类的,怎么办呢?我们继续往下看。

3-2 SharePreferences实现类SharePreferencesImpl分析

我们从上面SharePreference的使用入口可以分析,具体可以知道SharePreference的实例获取可以通过两种方式获取,一种是Activity的getPreferences方法,一种是Context的getSharedPreferences方法。所以我们如下先来看下这两个方法的源码。

先来看下Activity的getPreferences方法源码,如下:

public SharedPreferences getPreferences(int mode) {

return getSharedPreferences(getLocalClassName(), mode);

}

哎?可以发现,其实Activity的SharePreference实例获取方法只是对Context的getSharedPreferences再一次封装而已,使用getPreferences方法获取实例默认生成的xml文件名字是当前activity类名而已。既然这样那我们还是转战Context(其实现在ContextImpl中,至于不清楚Context与ContextImpl及Activity关系的请先看这篇博文,点我迅速脑补)的getSharedPreferences方法,具体如下:

//ContextImpl类中的静态Map声明,全局的一个sSharedPrefs

private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;

//获取SharedPreferences实例对象

public SharedPreferences getSharedPreferences(String name, int mode) {

//SharedPreferences的实现类对象引用声明

SharedPreferencesImpl sp;

//通过ContextImpl保证同步操作

synchronized (ContextImpl.class) {

if (sSharedPrefs == null) {

//实例化对象为一个复合Map,key-package,value-map

sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();

}

//获取当前应用包名

final String packageName = getPackageName();

//通过包名找到与之关联的prefs集合packagePrefs

ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);

//懒汉模式实例化

if (packagePrefs == null) {

//如果没找到就new一个包的prefs,其实就是一个文件名对应一个SharedPreferencesImpl,可以有多个对应,所以用map

packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();

//以包名为key,实例化的所有文件map作为value添加到sSharedPrefs

sSharedPrefs.put(packageName, packagePrefs);

}

if (mPackageInfo.getApplicationInfo().targetSdkVersion <

Build.VERSION_CODES.KITKAT) {

if (name == null) {

//nice处理,name传null时用"null"代替

name = “null”;

}

}

//找出与文件名name关联的sp对象

sp = packagePrefs.get(name);

if (sp == null) {

//如果没找到则先根据name构建一个File的prefsFile对象

File prefsFile = getSharedPrefsFile(name);

//依据上面的File对象创建一个SharedPreferencesImpl对象的实例

sp = new SharedPreferencesImpl(prefsFile, mode);

//以key-value方式添加到packagePrefs中

packagePrefs.put(name, sp);

返回与name相关的SharedPreferencesImpl对象

return sp;

}

}

//如果不是第一次,则在3.0之前(默认具备该mode)或者mode为MULTI_PROCESS时调用reload方法

if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||

getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {

//重新加载文件数据

sp.startReloadIfChangedUnexpectedly();

}

//返回SharedPreferences实例对象sp

return sp;

}

我们可以发现,上面方法中首先调运了getSharedPrefsFile来获取一个File对象,所以我们继续先来看下这个方法,具体如下:

public File getSharedPrefsFile(String name) {

//依据我们传入的文件名字符串创建一个后缀为xml的文件

return makeFilename(getPreferencesDir(), name + “.xml”);

}

private File getPreferencesDir() {

synchronized (mSync) {

if (mPreferencesDir == null) {

//获取当前app的data目录下的shared_prefs目录

mPreferencesDir = new File(getDataDirFile(), “shared_prefs”);

}

return mPreferencesDir;

}

}

可以看见,原来SharePreference文件存储路径和文件创建是这个来的。继续往下看可以发现接着调运了SharedPreferencesImpl的构造函数,至于这个构造函数用来干嘛,下面会分析。

好了,到这里我们先回过头稍微总结一下目前的源码分析结论,具体如下:

前面我们有文章分析了Android中的Context,这里又发现ContextImpl中有一个静态的ArrayMap变量sSharedPrefs。这时候你想到了啥呢?无论有多少个ContextImpl对象实例,系统都共享这一个sSharedPrefs的Map,应用启动以后首次使用SharePreference时创建,系统结束时才可能会被垃圾回收器回收,所以如果我们一个App中频繁的使用不同文件名的SharedPreferences很多时这个Map就会很大,也即会占用移动设备宝贵的内存空间,所以说我们应用中应该尽可能少的使用不同文件名的SharedPreferences,取而代之的是合并他们,减小内存使用。同时上面最后一段代码也及具有隐藏含义,其表明了SharedPreferences是可以通过MODE_MULTI_PROCESS来进行夸进程访问文件数据的,其reload就是为了夸进程能更好的刷新访问数据。

好了,还记不记得上面我们分析留的尾巴呢?现在我们就来看看这个尾巴,可以发现SharedPreferencesImpl类其实就是SharedPreferences接口的实现类,其构造函数如下:

final class SharedPreferencesImpl implements SharedPreferences {

//构造函数,file是前面分析data目录下创建的传入name的xml文件,mode为传入的访问方式

SharedPreferencesImpl(File file, int mode) {

mFile = file;

//依据文件名创建一个同名的.bak备份文件,当mFile出现crash的会用mBackupFile来替换恢复数据

mBackupFile = makeBackupFile(file);

mMode = mode;

mLoaded = false;

mMap = null;

//将文件从flash或者sdcard异步加载到内存中

startLoadFromDisk();

}

//创建同名备份文件

private static File makeBackupFile(File prefsFile) {

return new File(prefsFile.getPath() + “.bak”);

}

private void startLoadFromDisk() {

//同步操作mLoaded标志,写为未加载,这货是关键的关键!!!!

synchronized (this) {

mLoaded = false;

}

//开启一个线程异步同步加载disk文件到内存

new Thread(“SharedPreferencesImpl-load”) {

public void run() {

synchronized (SharedPreferencesImpl.this) {

//新线程中在SharedPreferencesImpl对象锁中异步load数据,如果此时数据还未load完成,则其他线程调用SharedPreferences.getXXX方法都会被阻塞,具体原因关注mLoaded标志变量即可!!!!!

loadFromDiskLocked();

}

}

}.start();

}

}

好了,到这里你会发现整个SharedPreferencesImpl的构造函数很简单,那我们就继续分析真正的异步加载文件到内存过程,如下:

private void loadFromDiskLocked() {

//如果已经异步加载直接return返回

if (mLoaded) {

return;

}

//如果存在备份文件则直接使用备份文件

if (mBackupFile.exists()) {

mFile.delete();

mBackupFile.renameTo(mFile);

}

Map map = null;

StructStat stat = null;

try {

//获取Linux文件stat信息,Linux高级C中经常出现的

stat = Os.stat(mFile.getPath());

//文件至少是可读的

if (mFile.canRead()) {

BufferedInputStream str = null;

try {

//把文件以BufferedInputStream流读出来

str = new BufferedInputStream(

new FileInputStream(mFile), 16*1024);

//使用系统提供的XmlUtils工具类将xml流解析转换为map类型数据

map = XmlUtils.readMapXml(str);

} catch (XmlPullParserException e) {

Log.w(TAG, “getSharedPreferences”, e);

} catch (FileNotFoundException e) {

Log.w(TAG, “getSharedPreferences”, e);

} catch (IOException e) {

Log.w(TAG, “getSharedPreferences”, e);

} finally {

IoUtils.closeQuietly(str);

}

}

} catch (ErrnoException e) {

}

//标记置为为已读

mLoaded = true;

if (map != null) {

//把解析的map赋值给mMap

mMap = map;

mStatTimestamp = stat.st_mtime;//记录时间戳

mStatSize = stat.st_size;//记录文件大小

} else {

mMap = new HashMap<String, Object>();

}

//唤醒其他等待线程(其实就是调运该类的getXXX方法的线程),因为在getXXX时会通过mLoaded标记是否进入wait,所以这里需要notify

notifyAll();

}

OK,到此整个Android应用获取SharePreference实例的过程我们就分析完了,简单总结下如下:

创建相关权限和mode的xml文件,异步同步锁加载xml文件并解析xml数据为map类型到内存中等待使用操作,特别注意,在xml文件异步加载未完成时调运SharePreference的getXXX及setXXX方法是阻塞等待的。由此也可以知道,一旦拿到SharePreference对象之后的getXXX操作其实都不再是文件读操作了,也就不存在网上扯蛋的认为多次频繁使用getXXX方法降低性能的说法了。

分析完了构造实例化,我们回忆可以知道使用SharePreference可以通过getXXX方法直接获取已经存在的key-value数据,下面我们就来看下这个过程,这里我们随意看一个方法即可,如下:

public boolean getBoolean(String key, boolean defValue) {

//可以看见,和上面异步load数据使用的是同一个对象锁

synchronized (this) {

//阻塞等待异步加载线程加载完成notify

awaitLoadedLocked();

//加载完成后解析的xml数据放在mMap对象中,我们从mMap中找出指定key的数据

Boolean v = (Boolean)mMap.get(key);

//存在返回找到的值,不存在返回设置的defValue

return v != null ? v : defValue;

}

}

先不解释,我们来关注下上面方法调运的awaitLoadedLocked方法,具体如下:

private void awaitLoadedLocked() {

//核心,这就是异步阻塞等待

while (!mLoaded) {

try {

wait();

} catch (InterruptedException unused) {

}

}

}

哈哈,不解释,这也太赤裸裸的明显了,就是阻塞,就是这么任性,没辙。那我们继续攻占高地呗,get完事了,那就是set了呀。

3-3 SharePreferences内部类Editor实现EditorImpl分析

还记不记得set是在SharePreference接口的Editor接口中定义的,而SharePreference提供了edit()方法来获取Editor实例,我们先来看下这个edit()方法吧,如下:

public Editor edit() {

//握草!这也和异步load用的一把锁

synchronized (this) {

//阻塞等待,不解释吧,向上看。。。

awaitLoadedLocked();

}

//异步加载OK以后通过EditorImpl创建Editor实例

return new EditorImpl();

}

可以看见,SharePreference的edit()方法其实就是阻塞等待返回一个Editor的实例(Editor的实现是EditorImpl),那我们就顺藤摸瓜一把,来看下这个EditorImpl这个类,如下:

public final class EditorImpl implements Editor {

//创建一个mModified的key-value集合,用来在内存中暂存数据

private final Map<String, Object> mModified = Maps.newHashMap();

//一个是否清除preference的flag

private boolean mClear = false;

…//省略类似的putXXX方法

public Editor putBoolean(String key, boolean value) {

//同步锁操作

synchronized (this) {

//将我们要存储的数据放入mModified集合中

mModified.put(key, value);

//返回当前对象实例,方便这种模式的代码写法:putXXX().putXXX();

return this;

}

}

//不用过多解释,同步删除mModified中包含key的数据

public Editor remove(String key) {

synchronized (this) {

mModified.put(key, this);

return this;

}

}

//不解释,要清楚所有数据则直接置位mClear标记

public Editor clear() {

synchronized (this) {

mClear = true;

return this;

}

}

}

好了,到此你可以发现Editor的setXXX及clear操作仅仅只是将相关数据暂存到内存中或者设置好标记为,也就是说调运了Editor的putXXX后其实数据是没有存入SharePreference的。那么通过我们一开始的实例可以知道,要想将Editor的数据存入SharePreference文件需要调运Editor的commit或者apply方法来生效。所以我们接下来先来看看Editor类常用的commit方法实现原理,如下:

public boolean commit() {

//1.先通过commitToMemory方法提交到内存

MemoryCommitResult mcr = commitToMemory();

//2.写文件操作

SharedPreferencesImpl.this.enqueueDiskWrite(

mcr, null /* sync write on this thread okay */);

try {

//阻塞等待写操作完成,UI操作需要注意!!!所以如果不关心返回值可以考虑用apply替代,具体原因等会分析apply就明白了。

mcr.writtenToDiskLatch.await();

} catch (InterruptedException e) {

return false;

}

//3.通知数据发生变化了

notifyListeners(mcr);

//4.返回写文件是否成功状态

return mcr.writeToDiskResult;

}

我去,小小一个commit方法做了这么多操作,主要分为四个步骤,我们先来看下第一个步骤,通过commitToMemory方法提交到内存返回一个MemoryCommitResult对象。分析commitToMemory方法前先看下MemoryCommitResult这个类,具体如下:

// Return value from EditorImpl#commitToMemory()

//也是内部类,只是为了组织数据结构而诞生,也就是EditorImpl.commitToMemory()的返回值

private static class MemoryCommitResult {

public boolean changesMade; // any keys different?

public List keysModified; // may be null

public Set<android.content.SharedPreferences.OnSharedPreferenceChangeListener> listeners; // may be null

public Map<?, ?> mapToWriteToDisk;

public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

public volatile boolean writeToDiskResult = false;

public void setDiskWriteResult(boolean result) {

writeToDiskResult = result;

writtenToDiskLatch.countDown();

}

}

回过头现在来看commitToMemory方法,具体如下:

// Returns true if any changes were made

private MemoryCommitResult commitToMemory() {

//啥也不说,先整一个实例化对象

MemoryCommitResult mcr = new MemoryCommitResult();

//和SharedPreferencesImpl共用一把锁

synchronized (SharedPreferencesImpl.this) {

// We optimistically don’t make a deep copy until

// a memory commit comes in when we’re already

// writing to disk.

if (mDiskWritesInFlight > 0) {

// We can’t modify our mMap as a currently

// in-flight write owns it. Clone it before

// modifying it.

// noinspection unchecked

//有多个未完成的写操作时复制一份,但是我们不知道用来干啥???????

mMap = new HashMap<String, Object>(mMap);

}

//构造数据结构,把通过SharedPreferencesImpl构造函数里异步加载的文件xml解析结果mMap赋值给要写到disk的Map

mcr.mapToWriteToDisk = mMap;

//增加一个未完成的写opt

mDiskWritesInFlight++;

//判断有没有监听设置

boolean hasListeners = mListeners.size() > 0;

if (hasListeners) {

//创建监听队列

mcr.keysModified = new ArrayList();

mcr.listeners =

new HashSet<android.content.SharedPreferences.OnSharedPreferenceChangeListener>(mListeners.keySet());

}

//再加一把自己的锁

synchronized (this) {

//如果调运的是Editor的clear方法,则这里commit时这么处理

if (mClear) {

//如果从文件里加载出来的xml不为空

if (!mMap.isEmpty()) {

//设置数据结构中数据变化标志为true

mcr.changesMade = true;

//清空内存中xml数据

mMap.clear();

}

//处理完毕,标记复位,程序继续执行,所以如果这次Editor中如果有写数据且还未commit,则执行完这次commit之后不会清掉本次写操作的数据,只会clear以前xml文件中的所有数据

mClear = false;

}

//mModified是调运Editor的setXXX零时存储的map

for (Map.Entry<String, Object> e : mModified.entrySet()) {

String k = e.getKey();

Object v = e.getValue();

// “this” is the magic value for a removal mutation. In addition,

// setting a value to “null” for a given key is specified to be

// equivalent to calling remove on that key.

//删除需要删除的key-value

if (v == this || v == null) {

if (!mMap.containsKey(k)) {

continue;

}

mMap.remove(k);

} else {

if (mMap.containsKey(k)) {

Object existingValue = mMap.get(k);

if (existingValue != null && existingValue.equals(v)) {

continue;

}

}

//把变化和新加的数据更新到SharePreferenceImpl的mMap中

mMap.put(k, v);

}

//设置数据结构变化标记

mcr.changesMade = true;

if (hasListeners) {

//设置监听

mcr.keysModified.add(k);

}

}

//清空Editor中零时存储的数据

mModified.clear();

}

}

//返回重新更新过mMap值封装的数据结构

return mcr;

}

到此我们Editor的commit方法的第一步已经完成,根据写操作组织内存数据,返回组织后的数据结构。接下来我们继续回到commit方法看下第二步—-写到文件中,其核心是调运SharedPreferencesImpl类的enqueueDiskWrite方法实现。具体如下:

//按照队列把内存数据写入磁盘,commit时postWriteRunnable为null,apply时不为null

private void enqueueDiskWrite(final MemoryCommitResult mcr,

final Runnable postWriteRunnable) {

//创建一个writeToDiskRunnable的Runnable对象

final Runnable writeToDiskRunnable = new Runnable() {

public void run() {

synchronized (mWritingToDiskLock) {

//真正的写文件操作

writeToFile(mcr);

}

synchronized (SharedPreferencesImpl.this) {

//写完一个计数器-1

mDiskWritesInFlight–;

}

if (postWriteRunnable != null) {

//等会apply分析

postWriteRunnable.run();

}

}

};

//判断是同步写还是异步

final boolean isFromSyncCommit = (postWriteRunnable == null);

// Typical #commit() path with fewer allocations, doing a write on

// the current thread.

//commit方式走这里

if (isFromSyncCommit) {

boolean wasEmpty = false;

synchronized (SharedPreferencesImpl.this) {

//如果当前只有一个写操作

wasEmpty = mDiskWritesInFlight == 1;

}

if (wasEmpty) {

//一个写操作就直接在当前线程中写文件,不用另起线程

writeToDiskRunnable.run();

//写完文件就返回

return;

}

}

//如果是apply就在线程池中执行

QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);

}

可以发现,commit从内存写文件是在当前调运线程中直接执行的。那我们再来看看这个写内存到磁盘方法中真正的写方法writeToFile,如下:

// Note: must hold mWritingToDiskLock

private void writeToFile(MemoryCommitResult mcr) {

if (mFile.exists()) {

if (!mcr.changesMade) {

//如果文件存在且没有改变的数据则直接返回写OK

mcr.setDiskWriteResult(true);

return;

}

if (!mBackupFile.exists()) {

//如果要写入的文件已经存在,并且备份文件不存在时就先把当前文件备份一份,因为如果本次写操作失败时数据可能已经乱了,所以下次实例化load数据时可以从备份文件中恢复

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

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

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

img

img

img

img

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

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

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

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

接下来是资料清单:(敲黑板!!!


1.数据结构和算法

2.设计模式

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

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

54)]

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

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

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

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

[外链图片转存中…(img-KGQJFggY-1712421415655)]

接下来是资料清单:(敲黑板!!!


1.数据结构和算法

[外链图片转存中…(img-iKaK6DIr-1712421415655)]

2.设计模式

[外链图片转存中…(img-NZqhollX-1712421415655)]

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

[外链图片转存中…(img-djQ7jYsF-1712421415655)]

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

[外链图片转存中…(img-7M7ktOnZ-1712421415655)]

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-yEy36MPC-1712421415656)]

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值