:app:checkDebugClasspath UP-TO-DATE
:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:checkDebugManifest UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:prepareLintJar UP-TO-DATE
:app:mainApkListPersistenceDebug UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:createDebugCompatibleScreenManifests UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:splitsDiscoveryTaskDebug UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:javaPreCompileDebug UP-TO-DATE
:app:compileDebugJavaWithJavac UP-TO-DATE
:app:compileDebugNdk NO-SOURCE
:app:compileDebugSources UP-TO-DATE
:app:mergeDebugShaders UP-TO-DATE
:app:compileDebugShaders UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:transformClassesWithDexBuilderForDebug UP-TO-DATE
:app:transformDexArchiveWithExternalLibsDexMergerForDebug UP-TO-DATE
:app:transformDexArchiveWithDexMergerForDebug UP-TO-DATE
:app:mergeDebugJniLibFolders UP-TO-DATE
:app:transformNativeLibsWithMergeJniLibsForDebug UP-TO-DATE
:app:checkDebugLibraries UP-TO-DATE
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE
:app:validateSigningDebug UP-TO-DATE
:app:packageDebug UP-TO-DATE
:app:assembleDebug UP-TO-DATE
为了更清楚观察各任务,我们可以对每个任务的输入和输出添加日志打印,在demo中放开build.gradle中任务打印区域代码
可以通过taskname来查找,一般任务路径大部分都是在
com.android.build.gradle.internal.tasks
com.android.build.gradle.tasks
目录下,任务名称基本和类名一致
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/appcompat-v7-26.1.0.aar/2774ea4f1cf1e83a6ad8e8d3c8b463b6/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.3.aar/f43c0ba95b6494825ed940fc4f04662b/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/animated-vector-drawable-26.1.0.aar/559112320064089dfaf6780e71d5b44f/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-vector-drawable-26.1.0.aar/c2c3ad4abfd49316f6769b8238b0f010/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-v4-26.1.0.aar/9ac5f97e8ccb24c52b7cbb6202c12ad0/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-media-compat-26.1.0.aar/53ab5ad72634f3497309a8788f3ca200/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-fragment-26.1.0.aar/7e6a4ce6591d722d47aafc36d980f8b4/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-core-utils-26.1.0.aar/4c474caa9ac1f01c4936bd96905ecacd/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-core-ui-26.1.0.aar/868eaa7e0c620cd85d72ad4f340e8bb1/jars/classes.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-compat-26.1.0.aar/4ec3c1c46e5bad9ac3b91f45a2afec3e/jars/classes.jar
input:/Users/apple/.gradle/caches/modules-2/files-2.1/com.android.support/support-annotations/26.1.0/814258103cf26a15fcc26ecce35f5b7d24b73f8/support-annotations-26.1.0.jar
input:/Users/apple/.gradle/caches/modules-2/files-2.1/com.android.support.constraint/constraint-layout-solver/1.1.3/bde0667d7414c16ed62d3cfe993cff7f9d732373/constraint-layout-solver-1.1.3.jar
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/runtime-1.0.0.aar/5b2333922ba05b1f174de51739b24d14/jars/classes.jar
input:/Users/apple/.gradle/caches/modules-2/files-2.1/android.arch.lifecycle/common/1.0.0/e414a4cb28434e25c4f6aa71426eb20cf4874ae9/common-1.0.0.jar
input:/Users/apple/.gradle/caches/modules-2/files-2.1/android.arch.core/common/1.0.0/a2d487452376193fc8c103dd2b9bd5f2b1b44563/common-1.0.0.jar
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/checkDebugClasspath/debug
从task字面可以猜测出该任务是对app classpath做校验,具体是在那个类呢?
我们找到到AppClasspathCheckTask.java,如何确定是正确的呢?
我们发现该类中有个ConfigAction.getName方法
其实这个方法的返回字符串就是执行的任务名(checkDebugClasspath),具体怎么流程大家直接看代码就知道了
ConfigAction被调用的地方
核心入口代码
@Override
protected Task createVariantPreBuildTask(@NonNull VariantScope scope) {
final VariantType variantType = scope.getVariantConfiguration().getType();
if (variantType.isApk()) {
AppClasspathCheckTask classpathCheck =
taskFactory.create(new AppClasspathCheckTask.ConfigAction(scope));
return (variantType.isTestComponent()
? taskFactory.create(new TestPreBuildTask.ConfigAction(scope))
: taskFactory.create(new AppPreBuildTask.ConfigAction(scope)))
.dependsOn(classpathCheck);
return super.createVariantPreBuildTask(scope);
有人会问中间的debug怎么多出来?追踪getName方法内部实现发现最终调用BaseVariantData.getTaskName方法
public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
return StringHelper.appendCapitalized(prefix, variantConfiguration.getFullName(), suffix);
public String getFullName() {
if (mFullName == null) {
mFullName =
computeFullName(
getFlavorName(),
mBuildType,
mType,
mTestedConfig == null ? null : mTestedConfig.getType());
return mFullName;
* Returns the full, unique name of the variant in camel case (starting with a lower case),
* including BuildType, Flavors and Test (if applicable).
* @param flavorName the flavor name, as computed by {@link #computeFlavorName(List)}
* @param buildType the build type
* @param type the variant type
* @return the name of the variant
public static <B extends BuildType> String computeFullName(
@NonNull String flavorName,
@NonNull B buildType,
@NonNull VariantType type,
@Nullable VariantType testedType) {
StringBuilder sb = new StringBuilder();
if (!flavorName.isEmpty()) {
sb.append(flavorName);
StringHelper.appendCapitalized(sb, buildType.getName());
} else {
sb.append(buildType.getName());
if (type.isHybrid()) {
sb.append("Feature");
if (type.isTestComponent()) {
if (testedType != null && testedType.isHybrid()) {
sb.append("Feature");
sb.append(type.getSuffix());
return sb.toString();
可以看到computeFullName是返回variant相关的名字,和我们输入的assembleDebug相匹配,所以我们后期直接通过任务名就能准确找到对应ConfigAction类了这样真正的Task类名也就找到了
确定任务名 -> 在所有TaskConfigAction的子类中寻找getName返回值是否与其task名匹配 -> 对应Task实现类
@TaskAction
void run() {
compareClasspaths();
void compareClasspaths() {
Set<ResolvedArtifactResult> runtimeArtifacts = runtimeClasspath.getArtifacts();
Set<ResolvedArtifactResult> compileArtifacts = compileClasspath.getArtifacts();
Map<String, Map<String, String>> runtimeIds =
Maps.newHashMapWithExpectedSize(runtimeArtifacts.size());
for (ResolvedArtifactResult artifact : runtimeArtifacts) {
final ComponentIdentifier componentIdentifier =
artifact.getId().getComponentIdentifier();
if (componentIdentifier instanceof ModuleComponentIdentifier) {
ModuleComponentIdentifier moduleId =
(ModuleComponentIdentifier) componentIdentifier;
Map<String, String> subMap =
runtimeIds.computeIfAbsent(moduleId.getGroup(), s -> new HashMap<>());
subMap.put(moduleId.getModule(), moduleId.getVersion());
for (ResolvedArtifactResult artifact : compileArtifacts) {
final ComponentIdentifier componentIdentifier =
artifact.getId().getComponentIdentifier();
if (componentIdentifier instanceof ModuleComponentIdentifier) {
ModuleComponentIdentifier moduleId =
(ModuleComponentIdentifier) componentIdentifier;
Map<String, String> subMap = runtimeIds.get(moduleId.getGroup());
if (subMap == null) {
continue;
String runtimeVersion = subMap.get(moduleId.getModule());
if (runtimeVersion == null) {
continue;
if (runtimeVersion.equals(moduleId.getVersion())) {
continue;
onDifferentVersionsFound(
moduleId.getGroup(),
moduleId.getModule(),
runtimeVersion,
moduleId.getVersion());
@Override
void onDifferentVersionsFound(
@NonNull String group,
@NonNull String module,
@NonNull String runtimeVersion,
@NonNull String compileVersion) {
String suggestedVersion;
try {
GradleVersion runtime = GradleVersion.parse(runtimeVersion);
GradleVersion compile = GradleVersion.parse(compileVersion);
if (runtime.compareTo(compile) > 0) {
suggestedVersion = runtimeVersion;
} else {
suggestedVersion = compileVersion;
} catch (Throwable e) {
suggestedVersion = runtimeVersion;
String message =
String.format(
"Conflict with dependency '%1$s:%2$s' in project '%3$s'. Resolved versions for "
+ "runtime classpath (%4$s) and compile classpath (%5$s) differ. This "
+ "can lead to runtime crashes. To resolve this issue follow "
+ "advice at https://developer.android.com/studio/build/gradle-tips#configure-project-wide-properties. "
+ "Alternatively, you can try to fix the problem "
+ "by adding this snippet to %6$s:\n"
+ "dependencies {\n"
+ " implementation(\"%1$s:%2$s:%7$s\")\n"
+ "}\n",
group,
module,
getProject().getPath(),
runtimeVersion,
compileVersion,
getProject().getBuildFile(),
suggestedVersion);
reporter.reportWarning(EvalIssueReporter.Type.GENERIC, message);
简单总结:
该任务就是对编译类路径和运行时类路径进行校验,如果相同的group.module中存在不同version,则提示用户依赖有冲突,会导致运行是crash
冲突解决方案参见官网
taskName:preDebugBuild
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/appcompat-v7-26.1.0.aar/2774ea4f1cf1e83a6ad8e8d3c8b463b6/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.3.aar/f43c0ba95b6494825ed940fc4f04662b/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/animated-vector-drawable-26.1.0.aar/559112320064089dfaf6780e71d5b44f/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-vector-drawable-26.1.0.aar/c2c3ad4abfd49316f6769b8238b0f010/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-v4-26.1.0.aar/9ac5f97e8ccb24c52b7cbb6202c12ad0/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-media-compat-26.1.0.aar/53ab5ad72634f3497309a8788f3ca200/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-fragment-26.1.0.aar/7e6a4ce6591d722d47aafc36d980f8b4/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-core-utils-26.1.0.aar/4c474caa9ac1f01c4936bd96905ecacd/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-core-ui-26.1.0.aar/868eaa7e0c620cd85d72ad4f340e8bb1/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-compat-26.1.0.aar/4ec3c1c46e5bad9ac3b91f45a2afec3e/AndroidManifest.xml
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/runtime-1.0.0.aar/5b2333922ba05b1f174de51739b24d14/AndroidManifest.xml
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/prebuild/debug
输入都为依赖库的清单文件,输出为空;这个任务简单说也是对应用变体做校验
@TaskAction
void run() {
Set<ResolvedArtifactResult> compileArtifacts = new HashSet<>();
compileArtifacts.addAll(compileManifests.getArtifacts());
compileArtifacts.addAll(compileNonNamespacedManifests.getArtifacts());
Set<ResolvedArtifactResult> runtimeArtifacts = new HashSet<>();
runtimeArtifacts.addAll(runtimeManifests.getArtifacts());
runtimeArtifacts.addAll(runtimeNonNamespacedManifests.getArtifacts());
Map<String, String> runtimeIds = Maps.newHashMapWithExpectedSize(runtimeArtifacts.size());
for (ResolvedArtifactResult artifact : runtimeArtifacts) {
handleArtifact(artifact.getId().getComponentIdentifier(), runtimeIds::put);
for (ResolvedArtifactResult artifact : compileArtifacts) {
final ComponentIdentifier compileId = artifact.getId().getComponentIdentifier();
handleArtifact(
compileId,
(key, value) -> {
String runtimeVersion = runtimeIds.get(key);
if (runtimeVersion == null) {
if (isBaseModule) {
String display = compileId.getDisplayName();
throw new RuntimeException(
"Android dependency '"
+ display
+ "' is set to compileOnly/provided which is not supported");
} else if (!runtimeVersion.isEmpty()) {
if (!runtimeVersion.equals(value)) {
throw new RuntimeException(
String.format(
"Android dependency '%s' has different version for the compile (%s) and runtime (%s) classpath. You should manually set the same version via DependencyResolution",
key, value, runtimeVersion));
});
private void handleArtifact(
@NonNull ComponentIdentifier id, @NonNull BiConsumer<String, String> consumer) {
if (id instanceof ProjectComponentIdentifier) {
consumer.accept(((ProjectComponentIdentifier) id).getProjectPath().intern(), "");
} else if (id instanceof ModuleComponentIdentifier) {
ModuleComponentIdentifier moduleComponentId = (ModuleComponentIdentifier) id;
consumer.accept(
moduleComponentId.getGroup() + ":" + moduleComponentId.getModule(),
moduleComponentId.getVersion());
} else if (id instanceof OpaqueComponentArtifactIdentifier) {
} else {
getLogger()
.warn(
"Unknown ComponentIdentifier type: "
+ id.getClass().getCanonicalName());
如果用compileOnly、provider修饰aar则会失败
验证下我们的想法,add 如下代码到app.build.gradle中
compileOnly 'com.facebook.stetho:stetho:1.5.0'
./gradlew preDebugBuild
执行结果
可以反推compileOnly不支持修饰aar只支持jar
补充下AppClasspathCheckTask是AppPreBuildTask任务前置条件,在👇的图可以体现出来
taskName:compileDebugAidl
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-media-compat-26.1.0.aar/53ab5ad72634f3497309a8788f3ca200/aidl
input:/Users/apple/.gradle/caches/transforms-1/files-1.1/support-compat-26.1.0.aar/4ec3c1c46e5bad9ac3b91f45a2afec3e/aidl
input:/Users/apple/work/project/AndroidGradleTaskDemo/app/src/main/aidl/com/gradle/task/demo/IHelloAidlInterface.aidl
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/incremental/compileDebugAidl
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/generated/source/aidl/debug
- 可以看出该任务是扫描工程下依赖的所有aidl文件,并生成对应的java文件
- 在生成dependency.store文件
input | output | 备注 |
---|
*.aidl | build/*.java | java源文件 |
- | dependency.store | .aidl -> .java映射文件表 |
先来看下该类继承关系
AidlCompile -> IncrementalTask(abstract)
* Gradle's entry-point into this task. Determines whether or not it's possible to do this task
* incrementally and calls either doIncrementalTaskAction() if an incremental build is possible,
* and doFullTaskAction() if not.
@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
if (!isIncremental() || !inputs.isIncremental()) {
getProject().getLogger().info("Unable do incremental execution: full task run");
doFullTaskAction();
return;
doIncrementalTaskAction(getChangedInputs(inputs));
private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) {
final Map<File, FileStatus> changedInputs = Maps.newHashMap();
inputs.outOfDate(
change -> {
FileStatus status = change.isAdded() ? FileStatus.NEW : FileStatus.CHANGED;
changedInputs.put(change.getFile(), status);
});
inputs.removed(change -> changedInputs.put(change.getFile(), FileStatus.REMOVED));
return changedInputs;
可以看到该任务主流程逻辑还是比较简单,如果是全量动作,走doFullTaskAction方法,否则走doIncrementalTaskAction增量操作,我们先来看下全量操作
doFullTaskAction
@CacheableTask
public class AidlCompile extends IncrementalTask {
private static final String DEPENDENCY_STORE = "dependency.store";
private static final PatternSet PATTERN_SET = new PatternSet().include("**/*.aidl");
@Override
protected void doFullTaskAction() throws IOException {
// this is full run, clean the previous output
File destinationDir = getSourceOutputDir();
File parcelableDir = getPackagedDir();
FileUtils.cleanOutputDir(destinationDir);
if (parcelableDir != null) {
FileUtils.cleanOutputDir(parcelableDir);
//2. 编译所有aidl文件
DepFileProcessor processor = new DepFileProcessor();
try {
compileAllFiles(processor);
} catch (Exception e) {
throw new RuntimeException(e);
//3. 生成aid->java映射文件
List<DependencyData> dataList = processor.getDependencyDataList();
DependencyDataStore store = new DependencyDataStore();
store.addData(dataList);
try {
store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE));
} catch (IOException e) {
throw new RuntimeException(e);
@InputFiles
@SkipWhenEmpty
@PathSensitive(PathSensitivity.RELATIVE)
public FileTree getSourceFiles() {
// this is because aidl may be in the same folder as Java and we want to restrict to
// .aidl files and not java files.
return getProject().files(sourceDirs.get()).getAsFileTree().matching(PATTERN_SET);
@InputFiles
@SkipWhenEmpty
@PathSensitive(PathSensitivity.RELATIVE)
public FileTree getSourceFiles() {
// this is because aidl may be in the same folder as Java and we want to restrict to
// .aidl files and not java files.
return getProject().files(sourceDirs.get()).getAsFileTree().matching(PATTERN_SET);
可以看到关键方法是compileAllFiles它是调用aidl工具生成了java源文件,进去看下
public void compileAllAidlFiles(
@NonNull Collection<File> sourceFolders,
@NonNull File sourceOutputDir,
@Nullable File packagedOutputDir,
@Nullable Collection<String> packageWhiteList,
@NonNull Collection<File> importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor,
@NonNull ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {
checkNotNull(sourceFolders, "sourceFolders cannot be null.");
checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
checkNotNull(importFolders, "importFolders cannot be null.");
checkState(mTargetInfo != null,
"Cannot call compileAllAidlFiles() before setTargetInfo() is called.");
IAndroidTarget target = mTargetInfo.getTarget();
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
if (aidl == null || !new File(aidl).isFile()) {
throw new IllegalStateException("aidl is missing from '" + aidl + "'");
List<File> fullImportList = Lists.newArrayListWithCapacity(
sourceFolders.size() + importFolders.size());
fullImportList.addAll(sourceFolders);
fullImportList.addAll(importFolders);
AidlProcessor processor = new AidlProcessor(
aidl,
target.getPath(IAndroidTarget.ANDROID_AIDL),
fullImportList,
sourceOutputDir,
packagedOutputDir,
packageWhiteList,
dependencyFileProcessor != null ?
dependencyFileProcessor : DependencyFileProcessor.NO_OP,
mProcessExecutor,
processOutputHandler);
for (File dir : sourceFolders) {
DirectoryWalker.builder()
.root(dir.toPath())
.extensions("aidl")
.action(processor)
.build()
.walk();
DirectoryWalker.walk -> AidlProcessor.call -> GradleProcessExecutor.execute
贴下AidlProcessor.call部分代码
public void call(@NonNull Path startDir, @NonNull Path path) throws IOException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(mAidlExecutable);
builder.addArgs("-p" + mFrameworkLocation);
builder.addArgs("-o" + mSourceOutputDir.getAbsolutePath());
for (File f : mImportFolders) {
builder.addArgs("-I" + f.getAbsolutePath());
File depFile = File.createTempFile("aidl", ".d");
builder.addArgs("-d" + depFile.getAbsolutePath());
builder.addArgs(path.toAbsolutePath().toString());
ProcessResult result = mProcessExecutor.execute(
builder.createProcess(), mProcessOutputHandler);
...
简单梳理下,第三部就是生成dependency.store文件了,这个就过了,大家有兴趣自己看好啦。
增量操作这里简单说下主要分几步操作
- 读取dependency.store文件,如果读取失败,直接走全量操作,并删除dependency.store文件
- 对changedInputs文件进行遍历判断
- 文件新增 -> 直接进行编译处理
- 文件是删除 -> 直接进行清理操作
- 文件修改 -> 读取文件依赖的所有选项,并对其进行编译处理
依赖任务:preBuildTask
public AidlCompile createAidlTask(@NonNull VariantScope scope) {
AidlCompile aidlCompileTask = taskFactory.create(new AidlCompile.ConfigAction(scope));
scope.getTaskContainer().setAidlCompileTask(aidlCompileTask);
scope.getTaskContainer().getSourceGenTask().dependsOn(aidlCompileTask);
aidlCompileTask.dependsOn(scope.getTaskContainer().getPreBuildTask());
return aidlCompileTask;
RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。RenderScript 主要用于数据并行计算,不过串行工作负载也可以从中受益。RenderScript 运行时可在设备上提供的多个处理器(如多核 CPU 和 GPU)间并行调度工作。这样您就能够专注于表达算法而不是调度工作。RenderScript 对于执行图像处理、计算摄影或计算机视觉的应用来说尤其有用。
taskName:compileDebugRenderscript
input:/Users/apple/work/project/AndroidGradleTaskDemo/app/src/debug/rs
input:/Users/apple/work/project/AndroidGradleTaskDemo/app/src/main/rs
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/rs/debug/lib
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/rs/debug/obj
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/generated/res/rs/debug
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/generated/source/rs/debug
套路和任务3差不多,调用工具(llvm-rs-cc)生成源文件
@TaskAction
void taskAction() throws IOException, InterruptedException, ProcessException {
File sourceDestDir = getSourceOutputDir();
FileUtils.cleanOutputDir(sourceDestDir);
File resDestDir = getResOutputDir();
FileUtils.cleanOutputDir(resDestDir);
File objDestDir = getObjOutputDir();
FileUtils.cleanOutputDir(objDestDir);
File libDestDir = getLibOutputDir();
FileUtils.cleanOutputDir(libDestDir);
Set<File> sourceDirectories = sourceDirs.getFiles();
getBuilder()
.compileAllRenderscriptFiles(
sourceDirectories,
getImportFolders(),
sourceDestDir,
resDestDir,
objDestDir,
libDestDir,
getTargetApi(),
isDebugBuild(),
getOptimLevel(),
isNdkMode(),
isSupportMode(),
useAndroidX(),
getNdkConfig() == null ? null : getNdkConfig().getAbiFilters(),
new LoggedProcessOutputHandler(getILogger()));
编译关键代码
具体细节不再阐述了,大家有兴趣自己看源码
👇
Android构建流程——上篇
Android构建流程——下篇
https://developer.android.com/studio/build/dependencies#resolution_errors
compileOnly的限制
Android新配置说明
https://developer.android.com/guide/topics/renderscript/compute?hl=zh-cn
Android构建流程——篇二预操作任务列表预操作为说明Android构建过程中gradle执行的各task,写了简单demogit clone https://github.com/xiaobaoyihao/AndroidGradleTaskDemo.git任务列表
17.6.1 案例运行效果 488
17.6.2 加载物体类——loadedobjectvertexnormal 488
17.6.3 加载物体刚体类——loadrigidbody 490
17.7 铰链关节 491
17.7.1 铰链关节的基本知识 491
17.7.2 案例的运行效果 492
17.7.3 铰链关节旋转角速度的计算 493
17.7.4 3d界面渲染类——mysurfaceview 494
17.8 滑动关节 496
17.8.1 滑动关节的基本知识 496
17.8.2 案例效果图 497
17.8.3 3d界面渲染类——mysurfaceview 498
17.9 六自由度关节 502
17.9.1 六自由度关节的基本知识 502
17.9.2 案例的运行效果 503
17.9.3 人偶类——doll 503
17.9.4 拾取时采用的点对点关节 505
17.10 本章小结 509
第18章 传感器应用的开发 510
18.1 基本的开发流程 510
18.2 加速度传感器 512
18.2.1 加速度传感器简介 513
18.2.2 案例的开发 514
18.3 磁场传感器 514
18.3.1 磁场传感器简介 514
18.3.2 案例的开发 514
18.4 光传感器 516
18.4.1 光传感器简介 516
18.4.2 案例的开发 516
18.5 温度传感器 518
18.5.1 温度传感器简介 518
18.5.2 案例的开发 518
18.6 接近传感器 519
18.6.1 接近传感器简介 519
18.6.2 案例的开发 520
18.7 姿态传感器 521
18.7.1 姿态传感器简介 521
18.7.2 案例的开发 522
18.8 本章小结 528
第19章 游戏开发小贴士 529
19.1 3d拾取技术 529
19.1.1 案例效果与基本原理 529
19.1.2 开发步骤 531
19.2 多点触控 537
19.2.1 案例效果与基本原理 537
19.2.2 开发步骤 538
19.3 多键监听 541
19.3.1 案例效果与基本原理 541
19.3.2 开发步骤 542
19.4 本章小结 544
第20章 bn赛艇 545
20.1 游戏背景及功能概述 545
20.1.1 背景概述 545
20.1.2 功能介绍 545
20.2 游戏的策划及准备工作 547
20.2.1 游戏的策划 547
20.2.2 android平台下游戏的准备工作 548
20.3 游戏的架构 552
20.3.1 各个类简要介绍 553
20.3.2 游戏框架简介 558
20.4 主控制类myactivity 559
20.5 2d界面相关类 563
20.5.1 欢迎界面类welcomeview 563
20.5.2 2d界面父类mysfview 565
20.5.3 主菜单类menuview 565
20.5.4 2d界面绘制类viewfordraw 566
20.5.5 数据库工具类dbutil 568
20.5.6 android系统版本对话框androidversiondialog 570
20.6 选船界面相关类 570
20.6.1 着色器管理类shadermanager 571
20.6.2 围墙类colorlightrect 571
20.6.3 选船房间类housefordraw 573
20.6.4 展台类displaystation 575
20.6.5 赛艇类boat 576
20.6.6 选船界面xcsurfaceview 576
20.7 游戏界面相关类 582
20.7.1 进度条类process 583
20.7.2 3d物体父类bndrawer 584
20.7.3 3d物体控制类tdobjectforcontrol 584
20.7.4 赛艇类boat 585
20.7.5 可碰撞物体父类kzbjdrawer 586
20.7.6 可碰撞物体控制类kzbjforcontr
本书共分两篇,第一篇介绍了Android 3D游戏开发的基础知识,主要对OpenGL ES的相关内容进行了介绍。
章 名主 要 内 容
第1章 英雄还看今朝—Android简介本章介绍了市场上主流的手机平台,同时也分析了未来手机平台的发展趋势及Android平台的前景
第2章 数风流人物—当前流行游戏类型简介本章以分类的方式简要地介绍了当前流行的游戏的玩法,游戏的视觉效果,游戏的设计及《仙剑》等著名游戏的历史
第3章 不积跬步,无以至千里—游戏开发基础知识本章初步介绍了游戏开发的基础知识
第4章 千里之行,始于足下—3D开发基础知识本章介绍了3D开发中的基础知识,包括OpenGL ES的介绍及OpenGL ES中绘制模型的原理,并通过点、线和三角形的绘制介绍了OpenGL ES中模型的几种绘制方式。最后介绍了3D场景中常用的两种投影方式,并通过例子比较了这两种投影的区别
第5章 愿君多采撷,此物最相思—光照效果的开发本章介绍了光照的基础知识,包括环境光、散射光及镜面光
第6章 为伊消得人憔悴——纹理映射本章主要介绍了纹理的基础知识,以及纹理的不同拉伸方式和纹理过滤高级技术,从绘制三角形开始到绘制地月系,可能会经历很长时间,但是这对以后的学习是有帮助的
第7章 海阔凭鱼跃,天高任鸟飞—3D基本形状的构建在本章中介绍了圆柱体、圆锥体、圆环、抛物面、双曲面和螺旋面在OpenGL ES中的渲染方法。这些基本形状在3D世界中应用广泛,在构造一些复杂物体时,经常会运用这些基本形状来进行拼装组合
第8章 执子之手,与子偕老—坐标变换本章介绍了坐标变换的应用。绘制3D场景的过程,主要是旋转和平移操作的组合,通过合理的堆栈操作,就比较容易绘制出所需的3D场景
第9章 孤帆远影碧空尽—摄像机与雾特效在本章中,首先对摄像机及其配置做了介绍。摄像机在3D编程中至关重要,没有正确的配置,摄像机可能不能获得想要的场景效果。然后对雾特效做了具体介绍,应用雾特效可以使场景更加逼真,并且可以减少场景渲染量来提高性能
第10章 假作真时真亦假—混合本章主要为读者介绍了混合,从混合的背景知识到如何配置源因子和目标因子。在介绍源因子和目标因子的时候,向读者介绍了一些预定义常量和一些常用的组合方式,以及如何启用混合
第11章 蓦然回首,那人却在灯火阑珊处—3D高级技术本章主要为读者介绍了3D的一部分高级技术。每一项技术通过讲解其原理和案例,使读者对3D高级技术有一定的了解
第12章 心有灵犀一点通—传感器在本章中,向读者介绍了Android中传感器的相关知识。包括传感器的种类、配置,并且着重介绍了姿态传感器的应用
第13章 千锤万凿出深山—游戏中的数学与物理在本章中对3D游戏中可能会用到的数学及物理知识进行了简单的介绍,这在3D游戏开发中是相当重要的。游戏中的核心算法,基本上都要用到数学和物理知识。一款游戏的性能很大程度上取决于游戏设计的算法
第14章 山舞银蛇,原驰蜡象—AI基本理念本章主要介绍了AI、AI引擎的基本组成与设计,以及游戏AI中图的搜索和模糊逻辑,其中游戏AI中图的搜索为本章的重点。在本章中详细介绍了5种算法的原理与实现
第15章 独上高楼,望尽天涯路—开发小秘籍本章介绍了地图设计器、多键技术、虚拟键盘、查找表技术、状态机、AABB边界框、穿透效应、拾取技术,以及天空盒和天空穹在OpenGL ES中的应用
第二篇以7个比较大的案例来说明Android平台下3D游戏的开发流程,通过这7个案例的讲解,读者对3D游戏的开发将会有更深层次的理解。
章 名主 要 内 容
第16章 体育类游戏——《疯狂投篮》本章介绍了Android 3D游戏《疯狂投篮》的开发。通过该案例向读者介绍了在Android平台下进行3D游戏开发的相关知识和基本流程,并对游戏开发中的编程技巧进行了介绍,并主要介绍了篮球与地面、墙面及篮框的碰撞检测及运动动画的实现方法
第17章 益智类游戏——《旋转积木》本章介绍了Android 3D游戏《旋转积木》的开发。主要介绍了积木旋转的不同状态的实现方法和地图设计器的应用
第18章 休闲类游戏——《摩天大楼》本章介绍了Android 3D游戏《摩天大楼》的开发。主要介绍了楼层与楼层之间的衔接与碰撞及掉落后翻转动画的实现
第19章 动作类游戏——《3D空战》本章介绍了Android 3D游戏《3D空战》的开发。主要介绍了飞机的构造方法和我方战机与敌方战机的操控及动画实现
第20章 桌面类游戏——《激情台球》本章介绍了Android 3D游戏《激情台球》的开发。主要介绍了台球与台球的碰撞检测实现、台球与球桌的碰撞检测实现和进球的判定实现
第21章 射击类游戏——《抢滩登陆》本章介绍了Android 3D游戏《抢滩登陆》的开发。主要运用了灰度图生成技术并且主要介绍了坦克运动的实现方法及炮弹碰撞检测的实现
第22章 竞技类游戏——《乡村飙车》本章介绍了Android 3D游戏《乡村飙车》的开发。主要介绍了运用分层绘制和拼接绘制的策略进行场景的优化绘制,并且对场景部件进行了分类控制
本书面向的读者
本书的内容详细,且几乎涵盖了Android 3D游戏开发所有相关的技术,并向读者介绍了真实项目的开发流程,主要面向以下读者。
Android的初学者
本书详细介绍了OpenGL ES的基础知识,并对Android 3D游戏程序的开发进行了介绍。作为一名Android的初学者,通过本书的学习可以快速全面地掌握Android 3D游戏开发的相关知识,稳健地步入Android 3D游戏开发人员的行列。
有一定Android基础且希望学习Android 3D游戏开发的读者
有一定Android基础的读者通过阅读本书的前半部分便可快速掌握OpenGL ES的基础知识,然后通过7个真实案例的学习迅速掌握Android平台下应用程序的开发。
在职的开发人员
书名:《Android底层开发技术实战详解——内核、移植和驱动》(电子工业出版社.王振丽)。本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入系统、振动器系统、音频系统、视频输出系统的驱动,openmax多媒体、多媒体插件框架,传感器、照相机、wi-fi、蓝牙、gps和电话系统等。在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。
本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。
全书压缩打包成3部分,这是第1部分。
第1章 Android底层开发基础 1
1.1 什么是驱动 1
1.1.1 驱动程序的魅力 1
1.1.2 电脑中的驱动 2
1.1.3 手机中的驱动程序 2
1.2 开源还是不开源的问题 3
1.2.1 雾里看花的开源 3
1.2.2 从为什么选择java谈为什么不开源驱动程序 3
1.2.3 对驱动开发者来说是一把双刃剑 4
1.3 Android和Linux 4
1.3.1 Linux简介 5
1.3.2 Android和Linux的关系 5
1.4 简析Linux内核 8
1.4.1 内核的体系结构 8
1.4.2 和Android密切相关的Linux内核知识 10
1.5 分析Linux内核源代码很有必要 14
1.5.1 源代码目录结构 14
1.5.2 浏览源代码的工具 16
1.5.3 为什么用汇编语言编写内核代码 17
1.5.4 Linux内核的显著特性 18
1.5.5 学习Linux内核的方法 26
第2章 分析Android源代码 31
2.1 搭建Linux开发环境和工具 31
2.1.1 搭建Linux开发环境 31
2.1.2 设置环境变量 32
2.1.3 安装编译工具 32
2.2 获取Android源代码 33
2.3 分析并编译Android源代码 35
2.3.1 Android源代码的结构 35
2.3.2 编译Android源代码 40
2.3.3 运行Android源代码 42
2.3.4 实践演练——演示编译Android程序的两种方法 43
2.4 编译Android kernel 47
2.4.1 获取goldfish内核代码 47
2.4.2 获取msm内核代码 50
2.4.3 获取omap内核代码 50
2.4.4 编译Android的Linux内核 50
2.5 运行模拟器 52
2.5.1 Linux环境下运行模拟器的方法 53
2.5.2 模拟器辅助工具——adb 54
第3章 驱动需要移植 57
3.1 驱动开发需要做的工作 57
3.2 Android移植 59
3.2.1 移植的任务 60
3.2.2 移植的内容 60
3.2.3 驱动开发的任务 61
3.3 Android对Linux的改造 61
3.3.1 Android对Linux内核文件的改动 62
3.3.2 为Android构建 Linux的操作系统 63
3.4 内核空间和用户空间接口是一个媒介 64
3.4.1 内核空间和用户空间的相互作用 64
3.4.2 系统和硬件之间的交互 64
3.4.3 使用relay实现内核到用户空间的数据传输 66
3.5 三类驱动程序 70
3.5.1 字符设备驱动程序 70
3.5.2 块设备驱动程序 79
3.5.3 网络设备驱动程序 82
第4章 hal层深入分析 84
4.1 认识hal层 84
4.1.1 hal层的发展 84
4.1.2 过去和现在的区别 86
4.2 分析hal层源代码 86
4.2.1 分析hal moudle 86
4.2.2 分析mokoid工程 89
4.3 总结hal层的使用方法 98
4.4 传感器在hal层的表现 101
4.4.1 hal层的sensor代码 102
4.4.2 总结sensor编程的流程 104
4.4.3 分析sensor源代码看Android api 与硬件平台的衔接 104
4.5 移植总结 116
4.5.1 移植各个Android部件的方式 116
4.5.2 移植技巧之一——不得不说的辅助工作 117
第5章 goldfish下的驱动解析 125
5.1 staging驱动 125
5.1.1 staging驱动概述 125
5.1.2 binder驱动程序 126
5.1.3 logger驱动程序 135
5.1.4 lowmemorykiller组件 136
5.1.5 timed output驱动程序 137
5.1.6 timed gpio驱动程序 139
5.1.7 ram console驱动程序 139
5.2 wakelock和early_suspend 140
5.2.1 wakelock和early_suspend的原理 140
5.2.2 Android休眠 141
5.2.3 Android唤醒 144
5.3 ashmem驱动程序 145
5.4 pmem驱动程序 148
5.5 alarm驱动程序 149
5.5.1 alarm简析 149
5.5.2 alarm驱动程序的实现 150
5.6 usb gadget驱动程序151
5.7 Android paranoid驱动程序153
5.8 goldfish设备驱动154
5.8.1 framebuffer驱动155
5.8.2 键盘驱动159
5.8.3 实时时钟驱动程序160
5.8.4 tty终端驱动程序161
5.8.5 nandflash驱动程序162
5.8.6 mmc驱动程序162
5.8.7 电池驱动程序162
第6章 msm内核和驱动解析164
6.1 msm基础164
6.1.1 常见msm处理器产品164
6.1.2 snapdragon内核介绍165
6.2 移植msm内核简介166
6.3 移植msm168
6.3.1 makefile文件168
6.3.2 驱动和组件170
6.3.3 设备驱动172
6.3.4 高通特有的组件174
第7章 omap内核和驱动解析177
7.1 omap基础177
7.1.1 omap简析177
7.1.2 常见omap处理器产品177
7.1.3 开发平台178
7.2 omap内核178
7.3 移植omap体系结构180
7.3.1 移植omap平台180
7.3.2 移植omap处理器183
7.4 移植Android专用驱动和组件188
7.5 omap的设备驱动190
第8章 显示系统驱动应用195
8.1 显示系统介绍195
8.1.1 Android的版本195
8.1.2 不同版本的显示系统195
8.2 移植和调试前的准备196
8.2.1 framebuffer驱动程序196
8.2.2 硬件抽象层198
8.3 实现显示系统的驱动程序210
8.3.1 goldfish中的framebuffer驱动程序210
8.3.2 使用gralloc模块的驱动程序214
8.4 msm高通处理器中的显示驱动实现224
8.4.1 msm中的framebuffer驱动程序225
8.4.2 msm中的gralloc驱动程序227
8.5 omap处理器中的显示驱动实现235
第9章 输入系统驱动应用239
9.1 输入系统介绍239
9.1.1 Android输入系统结构元素介绍239
9.1.2 移植Android输入系统时的工作240
9.2 input(输入)驱动241
9.3 模拟器的输入驱动256
9.4 msm高通处理器中的输入驱动实现257
9.4.1 触摸屏驱动257
9.4.2 按键和轨迹球驱动264
9.5 omap处理器平台中的输入驱动实现266
9.5.1 触摸屏驱动267
9.5.2 键盘驱动267
第10章 振动器系统驱动269
10.1 振动器系统结构269
10.1.1 硬件抽象层271
10.1.2 jni框架部分272
10.2 开始移植273
10.2.1 移植振动器驱动程序273
10.2.2 实现硬件抽象层274
10.3 在msm平台实现振动器驱动275
第11章 音频系统驱动279
11.1 音频系统结构279
11.2 分析音频系统的层次280
11.2.1 层次说明280
11.2.2 media库中的audio框架281
11.2.3 本地代码284
11.2.4 jni代码288
11.2.5 java代码289
11.3 移植audio系统的必备技术289
11.3.1 移植audio系统所要做的工作289
11.3.2 分析硬件抽象层290
11.3.3 分析audioflinger中的audio硬件抽象层的实现291
11.4 真正实现audio硬件抽象层298
11.5 msm平台实现audio驱动系统298
11.5.1 实现audio驱动程序298
11.5.2 实现硬件抽象层299
11.6 oss平台实现audio驱动系统304
11.6.1 oss驱动程序介绍304
11.6.2 mixer305
11.7 alsa平台实现audio系统312
11.7.1 注册音频设备和音频驱动312
11.7.2 在Android中使用alsa声卡313
11.7.3 在omap平台移植Android的alsa声卡驱动322
第12章 视频输出系统驱动326
12.1 视频输出系统结构326
12.2 需要移植的部分328
12.3 分析硬件抽象层328
12.3.1 overlay系统硬件抽象层的接口328
12.3.2 实现overlay系统的硬件抽象层331
12.3.3 实现接口332
12.4 实现overlay硬件抽象层333
12.5 在omap平台实现overlay系统335
12.5.1 实现输出视频驱动程序335
12.5.2 实现overlay硬件抽象层337
12.6 系统层调用overlay hal的架构342
12.6.1 调用overlay hal的架构的流程342
12.6.2 s3c6410 Android overlay的测试代码346
第13章 openmax多媒体框架349
13.1 openmax基本层次结构349
13.2 分析openmax框架构成350
13.2.1 openmax总体层次结构350
13.2.2 openmax il层的结构351
13.2.3 Android中的openmax354
13.3 实现openmax il层接口354
13.3.1 openmax il层的接口354
13.3.2 在openmax il层中需要做什么361
13.3.3 研究Android中的openmax适配层361
13.4 在omap平台实现openmax il363
13.4.1 实现文件364
13.4.2 分析ti openmax il的核心365
13.4.3 实现ti openmax il组件实例368
第14章 多媒体插件框架373
14.1 Android多媒体插件373
14.2 需要移植的内容374
14.3 opencore引擎375
14.3.1 opencore层次结构375
14.3.2 opencore代码结构376
14.3.3 opencore编译结构377
14.3.4 opencore oscl381
14.3.5 实现opencore中的openmax部分383
14.3.6 opencore的扩展398
14.4 stagefright引擎404
14.4.1 stagefright代码结构404
14.4.2 stagefright实现openmax接口405
14.4.3 video buffer传输流程409
第15章 传感器系统415
15.1 传感器系统的结构415
15.2 需要移植的内容417
15.2.1 移植驱动程序417
15.2.2 移植硬件抽象层418
15.2.3 实现上层部分419
15.3 在模拟器中实现传感器424
第16章 照相机系统430
16.1 camera系统的结构430
16.2 需要移植的内容433
16.3 移植和调试433
16.3.1 v4l2驱动程序433
16.3.2 硬件抽象层441
16.4 实现camera系统的硬件抽象层446
16.4.1 java程序部分446
16.4.2 camera的java本地调用部分447
16.4.3 camera的本地库libui.so448
16.4.4 camera服务libcameraservice.so449
16.5 msm平台实现camera系统454
16.6 omap平台实现camera系统457
第17章 wi-fi系统、蓝牙系统和gps系统459
17.1 wi-fi系统459
17.1.1 wi-fi系统的结构459
17.1.2 需要移植的内容461
17.1.3 移植和调试461
17.1.4 omap平台实现wi-fi469
17.1.5 配置wi-fi的流程471
17.1.6 具体演练——在Android下实现ethernet473
17.2 蓝牙系统475
17.2.1 蓝牙系统的结构475
17.2.2 需要移植的内容477
17.2.3 具体移植478
17.2.4 msm平台的蓝牙驱动480
17.3 定位系统482
17.3.1 定位系统的结构483
17.3.2 需要移植的内容484
17.3.3 移植和调试484
第18章 电话系统498
18.1 电话系统基础498
18.1.1 电话系统简介498
18.1.2 电话系统结构500
18.2 需要移植的内容501
18.3 移植和调试502
18.3.1 驱动程序502
18.3.2 ril接口504
18.4 电话系统实现流程分析507
18.4.1 初始启动流程507
18.4.2 request流程509
18.4.3 response流程512
第19章 其他系统514
19.1 alarm警报器系统514
19.1.1 alarm系统的结构514
19.1.2 需要移植的内容515
19.1.3 移植和调试516
19.1.4 模拟器环境的具体实现518
19.1.5 msm平台实现alarm518
19.2 lights光系统519
19.2.1 lights光系统的结构520
19.2.2 需要移植的内容521
19.2.3 移植和调试521
19.2.4 msm平台实现光系统523
19.3 battery电池系统524
19.3.1 battery系统的结构524
19.3.2 需要移植的内容526
19.3.3 移植和调试526
19.3.4 在模拟器中实现电池系统529
android studio安装
这学期选修了移动应用开发学安卓。
因为自己搭了梯子,干脆就用android studio作为学习的ide(其实是因为认识的小姐姐在eclipse上踩坑了)。
再加上上学期jsp的环境mac本机碰到很多问题,决定还是用as来安装。
没有想到安装a...
Execution failed for task ':app:checkDebugClasspath'.
> Could not resolve all files for configuration ':app:debugCompileClasspath'.
> Failed to transform file 'xxx.jar' to match attributes {artifactType=proce
今天用android studio新建了一个项目,发现gradle这里转了半天没完成,后面还报错了。
ERROR: Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve androidx.appcompat:appcompat:1.1.0.
我以为是不是没连VPN所以下载很慢,但是发现...
怀揣着对未来对希望,来到新公司当了一次接盘侠!
首先这个项目用到的友盟和微信全是不好用的,找的我沸腾了!最后发现开发平台的包名是错误的,或者包名和签名是反着的,具体因为什么我就不说了,改完就算了!
然后我前几天一直在改累积项目的问题。突然有一天bug改完了,写新需求,一引包发现app@debug/compileClasspath错误,然后开始的时候寻思是包冲突了也不太像啊,然后百度说我被墙了...
Android应用程序进程(Application Process)是指Android应用程序从开发到上架的整个流程,它涉及从概念到设计,从开发到发布的各个步骤。 首先,Android应用程序开发者需要设计应用程序的功能,并确定程序的目标,然后制定应用程序的架构,并设计用户界面。其次,Android应用程序开发者需要使用Android SDK开发应用程序,并使用Android NDK实现更多的功能。 接着,Android应用程序开发者需要编译应用程序,并使用Android Debug Bridge(ADB)进行调试,以确保应用程序的稳定性和可用性。 最后,Android应用程序开发者需要将应用程序上传到Google Play,并等待Google审核,一旦审核通过,应用程序就可以开始在Google Play上销售了。