这里主要使用small框架实现插件化的,为啥我选择使用small
-
它目前作者还在进行维护
-
功能强大(请看下图你就会明白)
开始一步步实现插件化
- _*********_**首先你需要引入small**********************
需要在根目录下的build.gradle脚本里引入:
buildscript {
dependencies {
classpath ‘net.wequick.tools.build:gradle-small:1.3.0-beta3’
}
}
apply plugin: ‘net.wequick.small’
small {
aarVersion = ‘1.3.0-beta3’
buildToAssets = false
android {
compileSdkVersion = 26
buildToolsVersion = “25.0.2”
supportVersion = “25.1.0”
}
}
- _***********新建插件模块******************_
File->New->Module来创建插件模块,需要满足:
模块名形如:app.*, lib.或者web.
包名包含:.app., .lib.或者.web.
为什么要这样?因为Small会根据包名对插件进行归类,特殊的域名空间如:“.app.” 会让这变得容易。
对lib.*模块选择Android Library,其他模块选择Phone & Tablet Module。
创建一个插件模块,比如app.main:
修改Application/Library name为App.main
修改Package name为com.example.mysmall.app.main
如果你不理解,请看我的目录结构
上面是模块名称,同时你要注意一下包名:
在你新建一个Module的时候as自动命名的应该是:tsou.cn.appchat
因此你在创建的时候要手动修改一下,可能你开始经常不注意直接下一步直接生成了。
进行如下图点击edit 修改一下就可以了
- _***********_**创建bundle.json****************
在你的宿主模块下(通常是app)创建bundle.json如下图
bundle.json的内容格式如下
{
“version”: “1.0.0”,
“bundles”: [
{
“uri”: “lib.data”,
“pkg”: “tsou.cn.lib.data”
},
{
“uri”: “lib.utils”,
“pkg”: “tsou.cn.lib.utils”
},
{
“uri”: “lib.style”,
“pkg”: “tsou.cn.lib.style”
},
{
“uri”: “lib.layout”,
“pkg”: “tsou.cn.lib.layout”
},
{
“uri”: “lib.icon”,
“pkg”: “tsou.cn.lib.icon”
},
{
“uri”: “home”,
“pkg”: “tsou.cn.app.home”
},
{
“uri”: “chat”,
“pkg”: “tsou.cn.app.chat”,
“rules”: {
“FromHomeActivity”: “tsou.cn.app.chat.activity.FromHomeActivity”
}
},
{
“uri”: “recom”,
“pkg”: “tsou.cn.app.recom”
},
{
“uri”: “me”,
“pkg”: “tsou.cn.app.me”
}
]
}
特别注意:
- 在这里你要注意一下不管是你的插件模块,还是你的依赖模块都需要在这里进行注册,不然你的程序运行会出现错误
例如:我在这里使用公用的lib.style样式,如果没有注册lib.style那么这个样式编译可以通过,但是运行就会找不到的错误。
- 你的宿主(app)moudle不能引入依赖模块(lib),只能是你的插件模块引用你的依赖模块,如下:
这是我的app宿主:
这是我的app.chat插件模块:
- 列表内容你需要进行编译:
我这里直接使用的是as来编译:依次为cleanLib->cleanBundle->buildLib->buildBundle
- 选择你的app主模块进行运行。
- _**************_**初始化small****************
在你的宿主app模块中:
package tsou.cn.mysmalltest;
import android.app.Application;
import net.wequick.small.Small;
/**
- Created by Administrator on 2017/11/27 0027.
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Small.preSetUp(this);
}
}
- _*********显示主页面************_
主页面主要是一个viewpager来填充各个插件模块的fragment,代码如下:
package tsou.cn.mysmalltest;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import net.wequick.small.Small;
/**
-
使用style等lib的时候切记在bundle.json上继续配置,不然会找不到
-
-
例如:提示AppTheme找不到让你进行配置问题
*/
public class MainActivity extends AppCompatActivity {
private ViewPager mMViewPager;
private TabLayout mToolbarTab;
/**
- 图标
*/
private int[] tabIcons = {
R.drawable.tab_home,
R.drawable.tab_weichat,
R.drawable.tab_recommend,
R.drawable.tab_user
};
private static String[] fragments = new String[]{“home”, “chat”, “recom”, “me”};
private String[] tab_array;
private DemandAdapter mDemandAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
// 给viewpager设置适配器
setViewPagerAdapter();
setTabBindViewPager();
setItem();
}
private void initData() {
tab_array = getResources().getStringArray(R.array.tab_main);
}
private void initView() {
mMViewPager = (ViewPager) findViewById(R.id.mViewPager);
mToolbarTab = (TabLayout) findViewById(R.id.toolbar_tab);
}
private void setViewPagerAdapter() {
mDemandAdapter = new DemandAdapter(getSupportFragmentManager());
mMViewPager.setAdapter(mDemandAdapter);
}
private void setTabBindViewPager() {
mToolbarTab.setupWithViewPager(mMViewPager);
}
private void setItem() {
/**
- 一定要在设置适配器之后设置Icon
*/
for (int i = 0; i < mToolbarTab.getTabCount(); i++) {
mToolbarTab.getTabAt(i).setCustomView(getTabView(i));
}
}
public View getTabView(int position) {
View view = LayoutInflater.from(this).inflate(R.layout.item_tab, null);
ImageView tab_image = view.findViewById(R.id.tab_image);
TextView tab_text = view.findViewById(R.id.tab_text);
tab_image.setImageResource(tabIcons[position]);
tab_text.setText(tab_array[position]);
return view;
}
/**
- 适配器
*/
public class DemandAdapter extends FragmentStatePagerAdapter {
public DemandAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
/**
-
在使用createObject前,需要再此页面之前已经存在activity,初始化Small,
-
否则获取不到Fragment,都是null
*/
Fragment fragment = Small.createObject(“fragment-v4”, fragments[position],
MainActivity.this);
return fragment;
}
@Override
public int getCount() {
return tabIcons.length;
}
}
}
特别要注意:
- 使用Small.createObject(“fragment-v4”, fragments[position],
MainActivity.this);来获取fragment时,各个插件模块的fragment命名一定要是MainFragment不然会出现空指针异常,这是Small框架的问题,在反射调用时已经写死了,同时需要在bundle.json中配置,一般直接创建在跟包名下面:
- 在使用createObject前,需要再此页面之前已经存在activity,初始化Small,例如我这里已经有了个启动页面LaunchActivity,不然也会出现空指针
-
如果这个时候要运行代码看效果,记得要执行as编译依次为cleanLib->cleanBundle->buildLib->buildBundle,、
-
记住在每次写完代码,运行到手机的时候都要进行此4部,否则新写的代码无效!
- _********插件之间的数据传递********_
一、跳转到Chat模块主页
在app.chat创建MainActivity,其实插件模块是一个可以独立运行的应用,和平时项目中的MainActivity没有区别,可看做独立的应用看待。
切记,在bundle.json中配置,我上面已经配置好了,包括后面要用的FromHomeActivity。并且运行时别忘了上面的那4部,后面不在说明。
{
“uri”: “chat”,
“pkg”: “tsou.cn.app.chat”,
“rules”: {
“FromHomeActivity”: “tsou.cn.app.chat.activity.FromHomeActivity”
}
}
执行代码如下:
Small.setUp(getContext(), new Small.OnCompleteListener() {
@Override
public void onComplete() {
Small.openUri(“chat”, getContext());
}
});
注意:Small.setUp是验证插件是否加载完成, Small.openUri是执行跳转
如果确定插件已经加载过了,或者这当前module内部进行跳转可以直接使用 Small.openUri
二、不带参数跳转到Chat模块指定Activity(FromHomeActivity)
执行代码如下:
Small.setUp(getContext(), new Small.OnCompleteListener() {
@Override
public void onComplete() {
/**
-
Small.openUri(“chat”, getContext());
-
直接跳转chat模块的主页。
-
Small.openUri(“chat/FromHomeActivity”, getContext());
-
跳转指定页面
*/
Small.openUri(“chat/FromHomeActivity”, getContext());
}
});
三、带参数跳转到Chat模块指定Activity(FromHomeActivity)
执行代码如下:
Small.setUp(getContext(), new Small.OnCompleteListener() {
@Override
public void onComplete() {
Small.openUri(“chat/FromHomeActivity?name=huangxiaoguo&age=25”, getContext());
}
});
FromHomeActivity中接收数据并使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_from_home);
initData();
initView();
}
private void initData() {
Uri uri = Small.getUri(this);
if (uri != null) {
name = uri.getQueryParameter(“name”);
age = uri.getQueryParameter(“age”);
}
}
private void initView() {
mTextview = (TextView) findViewById(R.id.textview);
if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(age)) {
mTextview.setText(“name=” + name + “,age=” + age);
// UIUtils.showToast(“name=” + name + “,age=” + age);
}
四、small支持本地网页组件
small可以直接支持本地网页,我们不用再自己写一个webview页面了!
但是这里的本地网页组件功能很有限,只能显示比较简单的网页,复杂的暂时不支持,会崩溃,具体你可以试试就知道了。
//Small.openUri(“http://www.baidu.com”, getContext());
Small.openUri(“https://github.com/wequick/Small/issues”, getContext());
break;
五、使用eventBus数据传输
原理不说了,直接上代码
compile ‘org.simple:androideventbus:1.0.5.1’
public interface EvenBusTag {
String EVENT_GET_DATA = “evevt_get_data”;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
EventBus.getDefault().register(this);
View view = inflater.inflate(R.layout.fragment_home, null);
initView(view);
return view;
}
@Override
public void onDestroyView() {
EventBus.getDefault().unregister(this);
ThreadUtils.cancelLongBackThread();
super.onDestroyView();
}
@Subscriber(tag = EvenBusTag.EVENT_GET_DATA)
public void onEvent(String s) {
UIUtils.showToast(s);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
default:
break;
case R.id.btn_fack_eventbus:
EventBus.getDefault().post(“eventBus从FromHomeActivity中返回数据了”, EvenBusTag.EVENT_GET_DATA);
finish();
break;
}
}
友好提示:我的这里的所有公用compile引入和一些自己定义的字段都放在lib.data中,因为我这里只有这个依赖库是专门提供给当前产品使用的,在开发新的产品时别的依赖库方便直接使用,我个人感觉方便点,当然看你自己想法!
六、插件化更新
- 修改代码
我修改的是app.chat中FromHomeActivity
在参数传递过来的时候,弹个吐司:
UIUtils.showToast(“name=” + name + “,age=” + age);
- 记得修改版本号:
versionCode 2 1——>2
versionName “1.1” 1.0->1.1
注意你修改的哪个插件修改哪个插件的版本即可,不是宿主app模块,我这里修改的是app.chat
-
两步:buildLib->buildBundle
-
查找so,在你的宿主模块中
- 部署服务器,我直接在我的电脑上部署一个tomcat
- 服务器的bundle.json
{
“manifest”: {
“bundles”: [
{
“pkg”: “tsou.cn.lib.data”,
“uri”: “lib.data”
},
{
“pkg”: “tsou.cn.lib.utils”,
“uri”: “lib.utils”
},
{
“pkg”: “tsou.cn.lib.style”,
“uri”: “lib.style”
},
{
“pkg”: “tsou.cn.lib.layout”,
“uri”: “lib.layout”
},
{
“pkg”: “tsou.cn.lib.icon”,
“uri”: “lib.icon”
},
{
“pkg”: “tsou.cn.app.home”,
“uri”: “home”
},
{
“pkg”: “tsou.cn.app.chat”,
“rules”: {
“FromHomeActivity”: “tsou.cn.app.chat.activity.FromHomeActivity”
},
“uri”: “chat”
},
{
“pkg”: “tsou.cn.app.recom”,
“uri”: “recom”
},
{
“pkg”: “tsou.cn.app.me”,
“uri”: “me”
}
],
“version”: “1.0.0”
},
“updates”: [
{
“pkg”: “tsou.cn.app.chat”,
“url”: “http://192.168.19.125:8080/json/libtsou_cn_app_chat.so”
}
]
}
- 实现插件化更新
/**
- 插件化更新
*/
private void checkUpgrade() {
new UpgradeManager(getContext()).checkUpgrade();
}
private static class UpgradeManager {
private static class UpdateInfo {
public String packageName;
public String downloadUrl;
}
private static class UpgradeInfo {
public JSONObject manifest;
public List updates;
}
private interface OnResponseListener {
void onResponse(UpgradeInfo info);
}
private interface OnUpgradeListener {
void onUpgrade(boolean succeed);
}
private static class ResponseHandler extends Handler {
private OnResponseListener mListener;
public ResponseHandler(OnResponseListener listener) {
mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
mListener.onResponse((UpgradeInfo) msg.obj);
break;
}
}
}
private ResponseHandler mResponseHandler;
private Context mContext;
private ProgressDialog mProgressDlg;
public UpgradeManager(Context context) {
mContext = context;
}
public void checkUpgrade() {
mProgressDlg = ProgressDialog.show(mContext, “Small”, “检查更新…”, false, true);
requestUpgradeInfo(Small.getBundleVersions(), new OnResponseListener() {
@Override
public void onResponse(UpgradeInfo info) {
mProgressDlg.setMessage(“升级中…”);
upgradeBundles(info,
new OnUpgradeListener() {
@Override
public void onUpgrade(boolean succeed) {
mProgressDlg.dismiss();
mProgressDlg = null;
String text = succeed ?
-
“升级成功!切换到后台并返回到前台来查看更改”
- “升级失败!”;
UIUtils.showToast(text);
}
});
}
});
}
/**
-
@param versions
-
@param listener
*/
private void requestUpgradeInfo(Map versions, OnResponseListener listener) {
System.out.println(versions); // this should be passed as HTTP parameters
mResponseHandler = new ResponseHandler(listener);
ThreadUtils.runOnLongBackThread(new Runnable() {
@Override
public void run() {
try {
// Example HTTP request to get the upgrade bundles information.
// Json format see http://wequick.github.io/small/upgrade/bundles.json
URL url = new URL(“http://192.168.19.125:8080/json/bundle.json”);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
StringBuilder sb = new StringBuilder();
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
sb.append(new String(buffer, 0, length));
}
// Parse json
JSONObject jo = new JSONObject(sb.toString());
JSONObject mf = jo.has(“manifest”) ? jo.getJSONObject(“manifest”) : null;
JSONArray updates = jo.getJSONArray(“updates”);
int N = updates.length();
List infos = new ArrayList(N);
for (int i = 0; i < N; i++) {
JSONObject o = updates.getJSONObject(i);
UpdateInfo info = new UpdateInfo();
info.packageName = o.getString(“pkg”);
info.downloadUrl = o.getString(“url”);
infos.add(info);
}
// Post message
UpgradeInfo ui = new UpgradeInfo();
ui.manifest = mf;
ui.updates = infos;
Message.obtain(mResponseHandler, 1, ui).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
mProgressDlg.dismiss();
mProgressDlg = null;
UIUtils.showToast(“更新失败”);
}
});
}
}
});
}
private static class DownloadHandler extends Handler {
private OnUpgradeListener mListener;
public DownloadHandler(OnUpgradeListener listener) {
mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
mListener.onUpgrade((Boolean) msg.obj);
break;
}
}
}
private DownloadHandler mHandler;
private void upgradeBundles(final UpgradeInfo info,
final OnUpgradeListener listener) {
// Just for example, you can do this by OkHttp or something.
mHandler = new DownloadHandler(listener);
ThreadUtils.runOnLongBackThread(new Runnable() {
@Override
public void run() {
try {
// Update manifest
if (info.manifest != null) {
if (!Small.updateManifest(info.manifest, false)) {
Message.obtain(mHandler, 1, false).sendToTarget();
return;
}
}
// Download bundles
List updates = info.updates;
for (UpdateInfo u : updates) {
// Get the patch file for downloading
net.wequick.small.Bundle bundle = Small.getBundle(u.packageName);
File file = bundle.getPatchFile();
// Download
URL url = new URL(u.downloadUrl);
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
InputStream is = urlConn.getInputStream();
OutputStream os = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
os.write(buffer, 0, length);
}
os.flush();
os.close();
is.close();
// Upgrade
bundle.upgrade();
}
Message.obtain(mHandler, 1, true).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
Message.obtain(mHandler, 1, false).sendToTarget();
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
mProgressDlg.dismiss();
mProgressDlg = null;
UIUtils.showToast(“更新失败”);
}
});
}
}
});
}
}
到此你就可以使用small进行插件化开发了………
热更新
热更新其实来源于插件化,如果你使用了上面的Small进行插件化开发,就可以直接进行插件化更新了!如果你没有使用插件化开发,你就可以只有热更新实现代码的热修复。
这里主要使用的是腾讯的Tinker。
- 在peoject的build中配置如下:
dependencies {
classpath ‘com.android.tools.build:gradle:2.3.3’
classpath “com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}”
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
TINKER_VERSION需要在gradle.properties中进行配置
TINKER_VERSION=1.7.7
- 接下来是配置app的build
这是我的,可以直接使用:
apply plugin: ‘com.android.application’
//配置java版本
def javaVersion = JavaVersion.VERSION_1_7
android {
compileSdkVersion 26
buildToolsVersion “26.0.2”
compileOptions {
sourceCompatibility javaVersion
targetCompatibility javaVersion
}
//recommend
dexOptions {
jumboMode = true
}
signingConfigs {
release {
keyAlias ‘tsou’
keyPassword ‘tsou123’
storeFile file(‘D:/study/TinkerTest/app/keystore/tinkertest.jks’)
storePassword ‘tsou123’
}
debug {
keyAlias ‘tsou’
keyPassword ‘tsou123’
storeFile file(‘D:/study/TinkerTest/app/keystore/tinkertest.jks’)
storePassword ‘tsou123’
}
}
defaultConfig {
applicationId “tsou.cn.tinkertest”
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName “1.0”
testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
multiDexEnabled true
buildConfigField “String”, “MESSAGE”, ““I am the base apk””
//客户端版本更新补丁
buildConfigField “String”, “TINKER_ID”, "“2.0"”
buildConfigField “String”, “PLATFORM”, ““all””
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/80f015686e26841986b9a92f55bffa3e.jpeg)
最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
u.cn.tinkertest"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName “1.0”
testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
multiDexEnabled true
buildConfigField “String”, “MESSAGE”, ““I am the base apk””
//客户端版本更新补丁
buildConfigField “String”, “TINKER_ID”, "“2.0"”
buildConfigField “String”, “PLATFORM”, ““all””
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-MH68UjQS-1712249482491)]
[外链图片转存中…(img-IGpUwm9d-1712249482492)]
[外链图片转存中…(img-T06qZTvK-1712249482492)]
[外链图片转存中…(img-hy8rMIqK-1712249482492)]
[外链图片转存中…(img-7M4lxnT4-1712249482493)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/80f015686e26841986b9a92f55bffa3e.jpeg)
最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-6YZybbKO-1712249482493)]