mWifiAutoCloseDelegate.onLocateFail(getApplicationContext() , aMapLocation.getErrorCode() , PowerManagerUtil.getInstance().isScreenOn(getApplicationContext()), NetUtil.getInstance().isWifiCon(getApplicationContext()));

}

}

private void sendLocationBroadcast(AMapLocation aMapLocation) {

if (null != aMapLocation) {

Intent mIntent = new Intent(LocationChangBroadcastReceiver.RECEIVER_ACTION);

mIntent.putExtra(LocationChangBroadcastReceiver.RECEIVER_DATA, aMapLocation);

sendBroadcast(mIntent);

ToastUtils.show(“获取到定位信息”);

String string = System.currentTimeMillis() + “,”+aMapLocation.getLatitude() + “,” + aMapLocation.getLongitude();

Utils.saveFile(string, “backlocation.txt”, true);

}

}

};

}

定位服务的基类

/**

  • 利用双service进行notification绑定,进而将Service的OOM_ADJ提高到1

  • 同时利用LocationHelperService充当守护进程,在NotificationService被关闭后,重启他。

  • 如果LocationHelperService被停止,NotificationService不负责唤醒

*/

public class NotificationService extends Service {

/**

  • startForeground的 noti_id

*/

private static int NOTI_ID = 123321;

private Utils.CloseServiceReceiver mCloseReceiver;

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

Log.e(“background location”, “远程服务调用成功”);

mCloseReceiver = new Utils.CloseServiceReceiver(this);

registerReceiver(mCloseReceiver, Utils.getCloseServiceFilter());

return START_STICKY;

}

@Override

public void onDestroy() {

if (mCloseReceiver != null) {

unregisterReceiver(mCloseReceiver);

mCloseReceiver = null;

}

super.onDestroy();

}

private final String mHelperServiceName = “com.hdsx.background.locationservice.LocationHelperService”;

/**

  • 触发利用notification增加进程优先级

*/

protected void applyNotiKeepMech() {

startForeground(NOTI_ID, Utils.buildNotification(getBaseContext()));

startBindHelperService();

}

public void unApplyNotiKeepMech() {

stopForeground(true);

}

public Binder mBinder;

public class LocationServiceBinder extends ILocationServiceAIDL.Stub {

@Override

public void onFinishBind(){

}

}

private ILocationHelperServiceAIDL mHelperAIDL;

private void startBindHelperService() {

connection = new ServiceConnection() {

@Override

public void onServiceDisconnected(ComponentName name) {

//doing nothing

}

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

ILocationHelperServiceAIDL l = ILocationHelperServiceAIDL.Stub.asInterface(service);

mHelperAIDL = l;

try {

l.onFinishBind(NOTI_ID);

} catch (RemoteException e) {

e.printStackTrace();

}

}

};

Intent intent = new Intent();

intent.setAction(mHelperServiceName);

bindService(Utils.getExplicitIntent(getApplicationContext(), intent), connection, Service.BIND_AUTO_CREATE);

}

private ServiceConnection connection;

@Nullable

@Override

public IBinder onBind(Intent intent) {

if (mBinder == null) {

mBinder = new LocationServiceBinder();

}

return mBinder;

}

}

另外一个服务:

public class LocationHelperService extends Service {

private Utils.CloseServiceReceiver mCloseReceiver;

@Override

public void onCreate() {

super.onCreate();

startBind();

mCloseReceiver = new Utils.CloseServiceReceiver(this);

registerReceiver(mCloseReceiver, Utils.getCloseServiceFilter());

}

@Override

public void onDestroy() {

if (mInnerConnection != null) {

unbindService(mInnerConnection);

mInnerConnection = null;

}

if (mCloseReceiver != null) {

unregisterReceiver(mCloseReceiver);

mCloseReceiver = null;

}

super.onDestroy();

}

private ServiceConnection mInnerConnection;

private void startBind() {

final String locationServiceName = “com.hdsx.background.locationservice.LocationService”;

mInnerConnection = new ServiceConnection() {

@Override

public void onServiceDisconnected(ComponentName name) {

Intent intent = new Intent();

intent.setAction(locationServiceName);

startService(Utils.getExplicitIntent(getApplicationContext(), intent));

}

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

ILocationServiceAIDL l = ILocationServiceAIDL.Stub.asInterface(service);

try {

l.onFinishBind();

} catch (RemoteException e) {

e.printStackTrace();

}

}

};

Intent intent = new Intent();

intent.setAction(locationServiceName);

bindService(Utils.getExplicitIntent(getApplicationContext(), intent), mInnerConnection, Service.BIND_AUTO_CREATE);

}

private HelperBinder mBinder;

private class HelperBinder extends ILocationHelperServiceAIDL.Stub {

@Override

public void onFinishBind(int notiId) throws RemoteException {

startForeground(notiId, Utils.buildNotification(LocationHelperService.this.getApplicationContext()));

stopForeground(true);

}

}

@Nullable

@Override

public IBinder onBind(Intent intent) {

if (mBinder == null) {

mBinder = new HelperBinder();

}

return mBinder;

}

}

双服务进行捆绑, 之前测试的过程中,发现这个powermanager配合服务他是可实现部分手机的,这边也做一个power的封装。

/**

  • 获得PARTIAL_WAKE_LOCK , 保证在息屏状体下,CPU可以正常运行

*/

public class PowerManagerUtil {

private static class Holder {

public static PowerManagerUtil instance = new PowerManagerUtil();

}

private PowerManager pm = null;

private PowerManager.WakeLock pmLock = null;

/**

  • 上次唤醒屏幕的触发时间

*/

private long mLastWakupTime = System.currentTimeMillis();

/**

  • 最小的唤醒时间间隔,防止频繁唤醒。默认5分钟

*/

private long mMinWakupInterval = 10 * 1000;

private InnerThreadFactory mInnerThreadFactory = null;

public static PowerManagerUtil getInstance() {

return Holder.instance;

}

/**

  • 判断屏幕是否处于点亮状态

  • @param context

*/

public boolean isScreenOn(final Context context) {

try {

Method isScreenMethod = PowerManager.class.getMethod(“isScreenOn”,

new Class[]{});

if (pm == null) {

pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

}

boolean screenState = (Boolean) isScreenMethod.invoke(pm);

return screenState;

} catch (Exception e) {

return true;

}

}

/**

  • 唤醒屏幕

*/

public void wakeUpScreen(final Context context) {

try {

acquirePowerLock(context, PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK);

String string = System.currentTimeMillis() +“唤醒”;

Utils.saveFile(string, “huanxinglocation.txt”, true);

} catch (Exception e) {

throw e;

}

}

/**

  • 根据levelAndFlags,获得PowerManager的WaveLock

  • 利用worker thread去获得锁,以免阻塞主线程

  • @param context

  • @param levelAndFlags

*/

private void acquirePowerLock(final Context context, final int levelAndFlags) {

if (context == null) {

throw new NullPointerException(“when invoke aquirePowerLock , context is null which is unacceptable”);

}

long currentMills = System.currentTimeMillis();

if (currentMills - mLastWakupTime < mMinWakupInterval) {

return;

}

mLastWakupTime = currentMills;

if (mInnerThreadFactory == null) {

mInnerThreadFactory = new InnerThreadFactory();

}

mInnerThreadFactory.newThread(new Runnable() {

@Override

public void run() {

if (pm == null) {

pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

}

if (pmLock != null) {

// release

pmLock.release();

pmLock = null;

}

pmLock = pm.newWakeLock(levelAndFlags, “MyTag”);

pmLock.acquire();

pmLock.release();

}

}).start();

}

private class InnerThreadFactory implements ThreadFactory {

@Override

public Thread newThread(Runnable runnable) {

return new Thread(runnable);

}

}

}

整体的逻辑呢就是说, 启动LocationService开启定位,定位成功后在手机添加一个前台的通知,让通知的优先级尽可能的提高。在锁屏后,powermanager判断获取如果定位失败唤醒服务。

public void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {

//如果屏幕点亮情况下,因为断网失败,则表示不是屏幕点亮造成的断网失败,并修改参照值

if (isScreenOn && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && !isWifiable) {

LocationStatusManager.getInstance().resetToInit(context);

return;

}

if (!LocationStatusManager.getInstance().isFailOnScreenOff(context, errorCode, isScreenOn, isWifiable)) {

return;

}

PowerManagerUtil.getInstance().wakeUpScreen(context);

}

代码有点多,就不一一介绍了,下面把我包含的工具类都发出来。

  • LocationStatusManager

/**

  • 在定位失败的情况下,用于判断当前定位错误是否是由于息屏导致的网络关闭引起的。

  • 判断逻辑仅限于处理设备仅有wifi信号的情况下

*/

public class LocationStatusManager {

/**

  • 上一次的定位是否成功

*/

private boolean mPriorSuccLocated = false;

/**

  • 屏幕亮时可以定位

*/

private boolean mPirorLocatableOnScreen = false;

static class Holder {

public static LocationStatusManager instance = new LocationStatusManager();

}

public static LocationStatusManager getInstance() {

return Holder.instance;

}

/**

  • 由于仅仅处理只有wifi连接的情况下,如果用户手机网络可连接,那么忽略。

  • 定位成功时,重置为定位成功的状态

  • @param isScreenOn 当前屏幕是否为点亮状态

  • @param isMobileable 是否有手机信号

*/

public void onLocationSuccess(Context context, boolean isScreenOn, boolean isMobileable) {

if (isMobileable) {

return;

}

mPriorSuccLocated = true;

if (isScreenOn) {

mPirorLocatableOnScreen = true;

saveStateInner(context, true);

}

}

/**

  • reset到默认状态

  • @param context

*/

public void resetToInit(Context context) {

this.mPirorLocatableOnScreen = false;

this.mPriorSuccLocated = false;

saveStateInner(context, false);

}

/**

  • 由preference初始化。特别是在定位服务重启的时候会进行初始化

*/

public void initStateFromPreference(Context context) {

if (!isLocableOnScreenOn(context)) {

return;

}

this.mPriorSuccLocated = true;

this.mPirorLocatableOnScreen = true;

}

/**

  • 判断是否由屏幕关闭导致的定位失败。

  • 只有在 网络可访问&&errorCode==4&&(priorLocated&&locatableOnScreen) && !isScreenOn 才认为是有息屏引起的定位失败

  • 如果判断条件较为严格,请按需要适当修改

  • @param errorCode 定位错误码, 0=成功, 4=因为网络原因造成的失败

  • @param isScreenOn 当前屏幕是否为点亮状态

*/

public boolean isFailOnScreenOff(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {

return !isWifiable && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && (mPriorSuccLocated && mPirorLocatableOnScreen) && !isScreenOn;

}

/**

  • 是否存在屏幕亮而且可以定位的情况的key

*/

private String IS_LOCABLE_KEY = “is_locable_key”;

/**

  • IS_LOCABLE_KEY 的过期时间

*/

private String LOCALBLE_KEY_EXPIRE_TIME_KEY = “localble_key_expire_time_key”;

/**

  • 过期时间为10分钟

*/

private static final long MINIMAL_EXPIRE_TIME = 30 * 60 * 1000;

private static final String PREFER_NAME = LocationStatusManager.class.getSimpleName();

private static final long DEF_PRIOR_TIME_VAL = -1;

/**

  • 如果isLocable,则存入正确的过期时间,否则存默认值

  • @param context

  • @param isLocable

*/

public void saveStateInner(Context context, boolean isLocable) {

SharedPreferences sharedPreferences = context.getSharedPreferences(PREFER_NAME, MODE_PRIVATE);

SharedPreferences.Editor editor = sharedPreferences.edit();

editor.putBoolean(IS_LOCABLE_KEY, isLocable);

editor.putLong(LOCALBLE_KEY_EXPIRE_TIME_KEY, isLocable ? System.currentTimeMillis() : DEF_PRIOR_TIME_VAL);

editor.commit();

}

/**

  • 从preference读取,判断是否存在网络状况ok,而且亮屏情况下,可以定位的情况

*/

public boolean isLocableOnScreenOn(Context context) {

SharedPreferences sharedPreferences = context.getSharedPreferences(PREFER_NAME, MODE_PRIVATE);

boolean res = sharedPreferences.getBoolean(IS_LOCABLE_KEY, false);

long priorTime = sharedPreferences.getLong(LOCALBLE_KEY_EXPIRE_TIME_KEY, DEF_PRIOR_TIME_VAL);

if (System.currentTimeMillis() - priorTime > MINIMAL_EXPIRE_TIME) {

saveStateInner(context, false);

return false;

}

return res;

}

}

  • LocationChangBroadcastReceiver

做最后定位的结果,保存到数据库里

public class LocationChangBroadcastReceiver extends BroadcastReceiver {

public static final String RECEIVER_ACTION = “location_in_background”;

public static final String RECEIVER_DATA = “location_data”;

@Override

public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

if (action.equals(RECEIVER_ACTION)) {

AMapLocation location = (AMapLocation) intent.getParcelableExtra(RECEIVER_DATA);

if (null != location) {

String string = System.currentTimeMillis() + “,”+location.getLatitude() + “,” + location.getLongitude();

Utils.saveFile(string, “broadcastlocation.txt”, true);

Log.v(“定位数据”, “经度:” + location.getLongitude() + " 纬度:" + location.getLatitude());

SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

java.util.Date date = new Date(location.getTime());

String tracktime = sdf.format(date);

Map map = new HashMap();

String userid = SpUtils.getString(USER_ID);

map.put(“userid”, userid);

double[] loc = CoordinateTransformUtil.gcj02towgs84(location.getLongitude(),location.getLatitude());

map.put(“tracktime”, tracktime);

map.put(“latitude”, loc[1]);

map.put(“lontitude”, loc[0]);

Frame.getInstance().getDao().insert(“trackbean.insert_track”, map);

}

}

}

}

  • NetUtil

用于判断设备是否可以访问网络。

public class NetUtil {

private static class Holder {

public static NetUtil instance = new NetUtil();

}

public static NetUtil getInstance() {

return Holder.instance;

}

/**

  • 是否手机信号可连接

  • @param context

  • @return

*/

public boolean isMobileAva(Context context) {

boolean hasMobileCon = false;

ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);

NetworkInfo[] netInfos = cm.getAllNetworkInfo();

for (NetworkInfo net : netInfos) {

String type = net.getTypeName();

if (type.equalsIgnoreCase(“MOBILE”)) {

if (net.isConnected()) {

hasMobileCon = true;

}

}

}

return hasMobileCon;

}

/**

  • 是否wifi可连接

  • @param context

  • @return

*/

public boolean isWifiCon(Context context) {

boolean hasWifoCon = false;

ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);

NetworkInfo[] netInfos = cm.getAllNetworkInfo();

for (NetworkInfo net : netInfos) {

String type = net.getTypeName();

if (type.equalsIgnoreCase(“WIFI”)) {

if (net.isConnected()) {

hasWifoCon = true;

}

}

}

return hasWifoCon;

}

}

  • Utils

辅助工具类

public class Utils {

private static String CLOSE_BRODECAST_INTENT_ACTION_NAME=“com.hdsx.background.locationservice.CloseService”;

/**

  • 开始定位

*/

public final static int MSG_LOCATION_START = 0;

/**

  • 定位完成

*/

public final static int MSG_LOCATION_FINISH = 1;

/**

  • 停止定位

*/

public final static int MSG_LOCATION_STOP = 2;

public final static String KEY_URL = “URL”;

public final static String URL_H5LOCATION = “file:///android_asset/location.html”;

private static SimpleDateFormat sdf = null;

private static NotificationManager mNotificationManager;

private final static String PRIMARY_CHANNEL = “default”;

/**

  • 根据定位结果返回定位信息的字符串

  • @param location

  • @return

*/

public synchronized static String getLocationStr(AMapLocation location) {

if (null == location) {

return null;

}

StringBuffer sb = new StringBuffer();

//errCode等于0代表定位成功,其他的为定位失败,具体的可以参照官网定位错误码说明

if (location.getErrorCode() == 0) {

sb.append(“定位成功” + “\n”);

sb.append("定位类型: " + location.getLocationType() + “\n”);

sb.append("经 度 : " + location.getLongitude() + “\n”);

sb.append("纬 度 : " + location.getLatitude() + “\n”);

sb.append("精 度 : " + location.getAccuracy() + “米” + “\n”);

sb.append("提供者 : " + location.getProvider() + “\n”);

sb.append("海 拔 : " + location.getAltitude() + “米” + “\n”);

sb.append("速 度 : " + location.getSpeed() + “米/秒” + “\n”);

sb.append("角 度 : " + location.getBearing() + “\n”);

if (location.getProvider().equalsIgnoreCase(

android.location.LocationManager.GPS_PROVIDER)) {

// 以下信息只有提供者是GPS时才会有

// 获取当前提供定位服务的卫星个数

sb.append("星 数 : "

  • location.getSatellites() + “\n”);

}

//逆地理信息

sb.append("国 家 : " + location.getCountry() + “\n”);

sb.append("省 : " + location.getProvince() + “\n”);

sb.append("市 : " + location.getCity() + “\n”);

sb.append("城市编码 : " + location.getCityCode() + “\n”);

sb.append("区 : " + location.getDistrict() + “\n”);

sb.append("区域 码 : " + location.getAdCode() + “\n”);

sb.append("地 址 : " + location.getAddress() + “\n”);

sb.append("兴趣点 : " + location.getPoiName() + “\n”);

//定位完成的时间

sb.append("定位时间: " + formatUTC(location.getTime(), “yyyy-MM-dd HH:mm:ss”) + “\n”);

} else {

//定位失败

sb.append(“定位失败” + “\n”);

sb.append(“错误码:” + location.getErrorCode() + “\n”);

sb.append(“错误信息:” + location.getErrorInfo() + “\n”);

sb.append(“错误描述:” + location.getLocationDetail() + “\n”);

}

//定位之后的回调时间

sb.append("回调时间: " + formatUTC(System.currentTimeMillis(), “yyyy-MM-dd HH:mm:ss”) + “\n”);

return sb.toString();

}

public synchronized static String formatUTC(long l, String strPattern) {

if (TextUtils.isEmpty(strPattern)) {

strPattern = “yyyy-MM-dd HH:mm:ss”;

}

if (sdf == null) {

try {

sdf = new SimpleDateFormat(strPattern, Locale.CHINA);

} catch (Throwable e) {

}

} else {

sdf.applyPattern(strPattern);

}

return sdf == null ? “NULL” : sdf.format(l);

}

public static Intent getExplicitIntent(Context context, Intent implicitIntent) {

if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {

return implicitIntent;

}

// Retrieve all services that can match the given intent

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

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

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

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

img-02pifDag-1711625881035)]
[外链图片转存中…(img-uhrjauW3-1711625881036)]
[外链图片转存中…(img-AJasp0OO-1711625881036)]
[外链图片转存中…(img-isxfaETg-1711625881037)]
[外链图片转存中…(img-bGH1mWpo-1711625881037)]
[外链图片转存中…(img-SQBSuEJj-1711625881038)]
img

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-TN9Yw2yY-1711625881038)]

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-9X2seDTx-1711625881038)]

【算法合集】

[外链图片转存中…(img-uasvZOTo-1711625881039)]

【延伸Android必备知识点】

[外链图片转存中…(img-woJ566jw-1711625881039)]

【Android部分高级架构视频学习资源】

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐