android:使用small一步步实现插件化与热更新

这里主要使用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”

}

]

}

特别注意:

  1. 在这里你要注意一下不管是你的插件模块,还是你的依赖模块都需要在这里进行注册,不然你的程序运行会出现错误

例如:我在这里使用公用的lib.style样式,如果没有注册lib.style那么这个样式编译可以通过,但是运行就会找不到的错误。

  1. 你的宿主(app)moudle不能引入依赖模块(lib),只能是你的插件模块引用你的依赖模块,如下:

这是我的app宿主:

这里写图片描述

这是我的app.chat插件模块:

这里写图片描述

  1. 列表内容你需要进行编译:

我这里直接使用的是as来编译:依次为cleanLib->cleanBundle->buildLib->buildBundle

这里写图片描述

  1. 选择你的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;

}

}

}

特别要注意:

  1. 使用Small.createObject(“fragment-v4”, fragments[position],

MainActivity.this);来获取fragment时,各个插件模块的fragment命名一定要是MainFragment不然会出现空指针异常,这是Small框架的问题,在反射调用时已经写死了,同时需要在bundle.json中配置,一般直接创建在跟包名下面:

这里写图片描述

  1. 在使用createObject前,需要再此页面之前已经存在activity,初始化Small,例如我这里已经有了个启动页面LaunchActivity,不然也会出现空指针

这里写图片描述

  1. 如果这个时候要运行代码看效果,记得要执行as编译依次为cleanLib->cleanBundle->buildLib->buildBundle,、

  2. 记住在每次写完代码,运行到手机的时候都要进行此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中,因为我这里只有这个依赖库是专门提供给当前产品使用的,在开发新的产品时别的依赖库方便直接使用,我个人感觉方便点,当然看你自己想法!

六、插件化更新

  1. 修改代码

我修改的是app.chat中FromHomeActivity

在参数传递过来的时候,弹个吐司:

UIUtils.showToast(“name=” + name + “,age=” + age);

  1. 记得修改版本号:

versionCode 2 1——>2

versionName “1.1” 1.0->1.1

注意你修改的哪个插件修改哪个插件的版本即可,不是宿主app模块,我这里修改的是app.chat

  1. 两步:buildLib->buildBundle

  2. 查找so,在你的宿主模块中

这里写图片描述

  1. 部署服务器,我直接在我的电脑上部署一个tomcat

这里写图片描述

  1. 服务器的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”

}

]

}

  1. 实现插件化更新

/**

  • 插件化更新

*/

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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

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

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

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

最后

希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 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)

最后

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

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

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

[外链图片转存中…(img-6YZybbKO-1712249482493)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值