Android13 PMS是如何启动的 _ config sdcardfs(1),2024年最新阿里p7面试要求

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

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

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

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

  • packages.xml: PKMS 扫描完目标文件夹后会创建该文件。当系统进行程序安装、卸载和更新等操作时,均会更新该文件。该文件保存了系统中与 package 相关的一些信息。
  • packages.list:描述系统中存在的所有非系统自带的 APK 的信息。当这些程序有变动时,PKMS 就会更新该文件。

//frameworks/base/services/core/java/com/android/server/pm/Settings.java
boolean readLPw(@NonNull Computer computer, @NonNull List users) {
FileInputStream str = null;
final ArrayMap<String, Long> originalFirstInstallTimes = new ArrayMap<>();

try {
if (str == null) {
if (!mSettingsFilename.exists()) {//如果不存在就返回false,也就是第一次创建
mReadMessages.append(“No settings file found\n”);
PackageManagerService.reportSettingsProblem(Log.INFO,
“No settings file; creating initial state”);
// It’s enough to just touch version details to create them
// with default values
findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
return false;
}
str = new FileInputStream(mSettingsFilename);
}
final TypedXmlPullParser parser = Xml.resolvePullParser(str);
//…如果存在这些xml文件就通过TypedXmlPullParser解析这些xml文件,保存在Settings中


return true;
}

BOOT_PROGRESS_PMS_SYSTEM_SCAN_START

EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
startTime);
//获取bootClassPath和systemServerClassPath下的环境变量
final String bootClassPath = System.getenv(“BOOTCLASSPATH”);
final String systemServerClassPath = System.getenv(“SYSTEMSERVERCLASSPATH”);

final VersionInfo ver = mSettings.getInternalVersion();
//判断是否升级了
mIsUpgrade =
!buildFingerprint.equals(ver.fingerprint);
if (mIsUpgrade) {
PackageManagerServiceUtils.logCriticalInfo(Log.INFO, "Upgrading from "

  • ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
    }
    //初始化app的类
    mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
    mInjector.getSystemPartitions());

    //如果是android M升级上来的需要修改为运行时权限
    mPromoteSystemApps =
    mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;

    // 对于Android N之前版本升级上来的情况,需像首次启动一样处理package
    mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;
    mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
    mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q;

    final WatchedArrayMap<String, PackageSetting> packageSettings =
    mSettings.getPackagesLocked();

    //扫描之前就存在的系统package的名称,这样就可以确定哪些更新的
    if (isDeviceUpgrading()) {
    mExistingPackages = new ArraySet<>(packageSettings.size());
    for (PackageSetting ps : packageSettings.values()) {
    mExistingPackages.add(ps.getPackageName());
    }
    }
    //获取缓存目录
    mCacheDir = PackageManagerServiceUtils.preparePackageParserCache(
    mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);

    final int[] userIds = mUserManager.getUserIds();
    //获取PackageParser2
    PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
    //扫描系统app
    mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,startTime);
    //系统app以外的app
    mInitAppsHelper.initNonSystemApps(packageParser, userIds, startTime);
    packageParser.close();

    // Resolve the storage manager.
    mStorageManagerPackage = getStorageManagerPackageName(computer);

    // Resolve protected action filters. Only the setup wizard is allowed to
    // have a high priority filter for these actions.
    //只允许安装向导这个应用拥有高级别的action
    mSetupWizardPackage = getSetupWizardPackageNameImpl(computer);
    mComponentResolver.fixProtectedFilterPriorities(mSetupWizardPackage);
    //获取了几个系统默认应用的包名
    mDefaultTextClassifierPackage = ensureSystemPackageName(computer,
    mContext.getString(R.string.config_servicesExtensionPackage));
    mSystemTextClassifierPackageName = ensureSystemPackageName(computer,
    mContext.getString(R.string.config_defaultTextClassifierPackage));
    mConfiguratorPackage = ensureSystemPackageName(computer,
    mContext.getString(R.string.config_deviceConfiguratorPackageName));
    mAppPredictionServicePackage = ensureSystemPackageName(computer,
    getPackageFromComponentString(R.string.config_defaultAppPredictionService));
    mIncidentReportApproverPackage = ensureSystemPackageName(computer,
    mContext.getString(R.string.config_incidentReportApproverPackage));
    mRetailDemoPackage = getRetailDemoPackageName();
    mOverlayConfigSignaturePackage = ensureSystemPackageName(computer,
    mInjector.getSystemConfig().getOverlayConfigSignaturePackage());
    mRecentsPackage = ensureSystemPackageName(computer,
    getPackageFromComponentString(R.string.config_recentsComponentName));
    mAmbientContextDetectionPackage = ensureSystemPackageName(computer,
    getPackageFromComponentString(
    R.string.config_defaultAmbientContextDetectionService));

    // Now that we know all of the shared libraries, update all clients to have
    // the correct library paths.
    // 更新客户端以确保持有正确的共享库路径
    mSharedLibraries.updateAllSharedLibrariesLPw(
    null, null, Collections.unmodifiableMap(mPackages));

    for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
    // NOTE: We ignore potential failures here during a system scan (like
    // the rest of the commands above) because there’s precious little we
    // can do about it. A settings error is reported, though.
    //如果升级导致abi发生了变化,那么就卸载原来api的代码
    final List changedAbiCodePath =
    ScanPackageUtils.applyAdjustedAbiToSharedUser(
    setting, null /scannedPackage/,
    mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
    setting.getPackageStates(), null /scannedPackage/));
    if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
    for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
    final String codePathString = changedAbiCodePath.get(i);
    try {
    mInstaller.rmdex(codePathString,
    getDexCodeInstructionSet(getPreferredInstructionSet()));
    } catch (InstallerException ignored) {
    }
    }
    }
    // Adjust seInfo to ensure apps which share a sharedUserId are placed in the same
    // SELinux domain.
    setting.fixSeInfoLocked();
    setting.updateProcesses();
    }

    // Now that we know all the packages we are keeping,
    // read and update their last usage times.
    // 读取并更新保留的package的上次使用时间
    mPackageUsage.read(packageSettings);
    mCompilerStats.read();

这里最重要的就是通过mInitAppsHelper获取了系统apk和其他的apk的信息。

//frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java
public OverlayConfig initSystemApps(PackageParser2 packageParser,
WatchedArrayMap<String, PackageSetting> packageSettings,
int[] userIds, long startTime) {

scanSystemDirs(packageParser, mExecutorService);
// Parse overlay configuration files to set default enable state, mutability, and
// priority of system overlays.
final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
}
}

return overlayConfig;
}


private void scanSystemDirs(PackageParser2 packageParser, ExecutorService executorService) {
///system/framework路径下
File frameworkDir = new File(Environment.getRootDirectory(), “framework”);

scanDirTracedLI(frameworkDir, null,
mSystemParseFlags,
mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
packageParser, executorService);
//…其他系统app路径也一样就不展示了
}

scanSystemDirs中就是通过scanDirTracedLI扫描各种路径下的apk。

private void scanDirTracedLI(File scanDir, List frameworkSplits,
int parseFlags, int scanFlags,
PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, “scanDir [” + scanDir.getAbsolutePath() + “]”);
try {
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
// when scanning apk in apexes, we want to check the maxSdkVersion
parseFlags |= PARSE_APK_IN_APEX;
}
mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
scanFlags, packageParser, executorService);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}

scanDirTracedLI调用了InstallPackageHelper.installPackagesFromDir来安装

//frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
public void installPackagesFromDir(File scanDir, List frameworkSplits, int parseFlags,
int scanFlags, PackageParser2 packageParser,
ExecutorService executorService) {
final File[] files = scanDir.listFiles();

ParallelPackageParser parallelPackageParser =
new ParallelPackageParser(packageParser, executorService, frameworkSplits);

// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
if ((scanFlags & SCAN_DROP_CACHE) != 0) {
final PackageCacher cacher = new PackageCacher(mPm.getCacheDir());
Log.w(TAG, "Dropping cache of " + file.getAbsolutePath());
cacher.cleanCachedResult(file);
}
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}

// Process results one by one
for (; fileCount > 0; fileCount–) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
String errorMsg = null;

if (throwable == null) {
// TODO(b/194319951): move lower in the scan chain
// Static shared libraries have synthetic package names
if (parseResult.parsedPackage.isStaticSharedLibrary()) {
PackageManagerService.renameStaticSharedLibraryPackage(
parseResult.parsedPackage);
}
try {
addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
null);
} catch (PackageManagerException e) {
errorCode = e.error;
errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
Slog.w(TAG, errorMsg);
}
} else if (throwable instanceof PackageManagerException) {
PackageManagerException e = (PackageManagerException) throwable;
errorCode = e.error;
errorMsg = "Failed to parse " + parseResult.scanFile + ": " + e.getMessage();
Slog.w(TAG, errorMsg);
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "

  • parseResult.scanFile, throwable);
    }

}
}

通过ParallelPackageParser.submit提交安装任务,ParallelPackageParser.submit.take获取任务结果。

//frameworks/base/services/core/java/com/android/server/pm/ParallelPackageParser.java
public void submit(File scanFile, int parseFlags) {
mExecutorService.submit(() -> {
ParseResult pr = new ParseResult();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, “parallel parsePackage [” + scanFile + “]”);
try {
pr.scanFile = scanFile;
pr.parsedPackage = parsePackage(scanFile, parseFlags);
} catch (Throwable e) {
pr.throwable = e;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
try {
mQueue.put(pr);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
mInterruptedInThread = Thread.currentThread().getName();
}
});
}
public ParseResult take() {
try {
if (mInterruptedInThread != null) {
throw new InterruptedException("Interrupted in " + mInterruptedInThread);
}
return mQueue.take();
} catch (InterruptedException e) {
// We cannot recover from interrupt here
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
}

submit和take就是通过一个 BlockingQueue实现解析和获取任务。解析是parsePackage来实现的。

protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
}

这里的mPackageParser就是前面的参数PackageParser2

//frameworks/base/services/core/java/com/android/server/pm/parsing/PackageParser2.java
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
List frameworkSplits) throws PackageManagerException {
//判断缓存是否存在,存在就直接使用缓存
if (useCaches && mCacher != null) {
ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
if (parsed != null) {
return parsed;
}
}

long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
ParseInput input = mSharedResult.get().reset();
//解析apk文件
ParseResult result = parsingUtils.parsePackage(input, packageFile, flags,
frameworkSplits);
ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();
long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
//添加到缓存
if (mCacher != null) {
mCacher.cacheResult(packageFile, flags, parsed);
}
return parsed;
}

又到了parsingUtils.parsePackage

//frameworks/base/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
public ParseResult parsePackage(ParseInput input, File packageFile, int flags,
List frameworkSplits) {
if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
&& frameworkSplits.size() > 0
&& packageFile.getAbsolutePath().endsWith(“/framework-res.apk”)) {
return parseClusterPackage(input, packageFile, frameworkSplits, flags);
} else if (packageFile.isDirectory()) {//是一个目录就使用parseClusterPackage
return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
} else {//是单独一个apk就使用parseMonolithicPackage
return parseMonolithicPackage(input, packageFile, flags);
}
}

终于到了解析看下简单的parseMonolithicPackage

private ParseResult parseMonolithicPackage(ParseInput input, File apkFile,
int flags) {
//先通过parseMonolithicPackageLite初步解析这个apk,这里会解析出minsdkversion,versionCode 这些基本信息
final ParseResult liteResult =
ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
if (liteResult.isError()) {
return input.error(liteResult);
}

final PackageLite lite = liteResult.getResult();
if (mOnlyCoreApps && !lite.isCoreApp()) {
return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
"Not a coreApp: " + apkFile);
}
//AssetManager和文件信息的封装,用于后面使用AssetManager加载资源
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
//真正解析apk
final ParseResult result = parseBaseApk(input,
apkFile,
apkFile.getCanonicalPath(),
assetLoader, flags);
if (result.isError()) {
return input.error(result);
}

return input.success(result.getResult()
.setUse32BitAbi(lite.isUse32bitAbi()));
} catch (IOException e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}

parseMonolithicPackage先通过parseMonolithicPackageLite来初步处理apk,获取PackageLite。然后调用了parseBaseApk详细处理apk。

private ParseResult parseBaseApk(ParseInput input, File apkFile,
String codePath, SplitAssetLoader assetLoader, int flags) {
//获取apk文件
final String apkPath = apkFile.getAbsolutePath();
final AssetManager assets;
try {
assets = assetLoader.getBaseAssetManager();
} catch (IllegalArgumentException e) {
return input.error(e.getCause() instanceof IOException ? INSTALL_FAILED_INVALID_APK
: INSTALL_PARSE_FAILED_NOT_APK, e.getMessage(), e);
}
final int cookie = assets.findCookieForPath(apkPath);
//创建xml解析对象
try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
ANDROID_MANIFEST_FILENAME)) {
final Resources res = new Resources(assets, mDisplayMetrics, null);

ParseResult result = parseBaseApk(input, apkPath, codePath, res,
parser, flags);

final ParsingPackage pkg = result.getResult();



return input.success(pkg);
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
}
}
private ParseResult parseBaseApk(ParseInput input, String apkPath,
String codePath, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {

final ParseResult result =
parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);

}

层层调用到了parseBaseApkTags来解析androidmanifest中的各个标签。

private ParseResult parseBaseApkTags(ParseInput input, ParsingPackage pkg,
TypedArray sa, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {

boolean foundApp = false;
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}

String tagName = parser.getName();
final ParseResult result;

// has special logic, so it’s handled outside the general method
if (TAG_APPLICATION.equals(tagName)) {
if (foundApp) {
if (RIGID_PARSER) {
result = input.error(“ has more than one ”);
} else {
Slog.w(TAG, “ has more than one ”);
result = input.success(null);
}
} else {
foundApp = true;
result = parseBaseApplication(input, pkg, res, parser, flags);
}
} else {
result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
}

return input.success(pkg);
}

如果是application标签,那么就调用parseBaseApplication方法,因为四大组件等都在这个标签下面,其他通parseBaseApkTag解析。

private ParseResult parseBaseApplication(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {

while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}

final ParseResult result;
String tagName = parser.getName();
boolean isActivity = false;
switch (tagName) {
case “activity”:
isActivity = true;
// fall-through
case “receiver”:
ParseResult activityResult =
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
res, parser, flags, sUseRoundIcon, null /defaultSplitName/,
input);

if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
if (isActivity) {
hasActivityOrder |= (activity.getOrder() != 0);
pkg.addActivity(activity);
} else {
hasReceiverOrder |= (activity.getOrder() != 0);
pkg.addReceiver(activity);
}
}

result = activityResult;
break;

}

这里就通过解析了标签,根据标签调用不同的解析方法。四大组件的信息PMS就都获取到了。
BOOT_PROGRESS_PMS_SCAN_END

EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
mPermissionManager.onStorageVolumeMounted(
StorageManager.UUID_PRIVATE_INTERNAL, mIsUpgrade);
ver.sdkVersion = mSdkVersion;

// If this is the first boot or an update from pre-M, and it is a normal
// boot, then we need to initialize the default preferred apps across
// all defined users.
//如果是从M升级或者第一次启动,初始化默认应用
if (!mOnlyCore && (mPromoteSystemApps || mFirstBoot)) {
for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) {
mSettings.applyDefaultPreferredAppsLPw(user.id);
}
}

//如果是第一次OTA升级之后的启动,那就删除代码缓存的目录
if (mIsUpgrade && !mOnlyCore) {
Slog.i(TAG, “Build fingerprint changed; clearing code caches”);
for (int i = 0; i < packageSettings.size(); i++) {
final PackageSetting ps = packageSettings.valueAt(i);
if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.getVolumeUuid())) {
// No apps are running this early, so no need to freeze
mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY
| Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES);
}
}
ver.fingerprint = PackagePartitions.FINGERPRINT;
}

// Defer the app data fixup until we are done with app data clearing above.
mPrepareAppDataFuture = mAppDataHelper.fixAppsDataOnBoot();

// Legacy existing (installed before Q) non-system apps to hide
// their icons in launcher.
//安装Q前的非系统应用程序在Launcher中隐藏他们的图标
if (!mOnlyCore && mIsPreQUpgrade) {
Slog.i(TAG, “Allowlisting all existing apps to hide their icons”);
int size = packageSettings.size();
for (int i = 0; i < size; i++) {
final PackageSetting ps = packageSettings.valueAt(i);
if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0) {
continue;
}
ps.disableComponentLPw(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME,
UserHandle.USER_SYSTEM);
}
}

// clear only after permissions and other defaults have been updated
mPromoteSystemApps = false;

// All the changes are done during package scanning.
ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;

// can downgrade to reader
t.traceBegin(“write settings”);
writeSettingsLPrTEMP();
t.traceEnd();

BOOT_PROGRESS_PMS_READY

EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());


//权限控制器
mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(computer);
mSettings.setPermissionControllerVersion(
computer.getPackageInfo(mRequiredPermissionControllerPackage, 0,
UserHandle.USER_SYSTEM).getLongVersionCode());

结尾

最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

[外链图片转存中…(img-ldpP95LJ-1713699414388)]

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

[外链图片转存中…(img-Mf43vPCh-1713699414388)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-qMcVEsp1-1713699414388)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值