Merge "Prototype XML view compiler" am: 956791cee8

am: dcdd224bc0

Change-Id: I1920a587ab1dfb66f78123f68932fb7c7969fa40
This commit is contained in:
Eric Holk
2018-10-01 18:31:53 -07:00
committed by android-build-merger
9 changed files with 449 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
//
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
cc_library_host_static {
name: "libviewcompiler",
srcs: [
"java_lang_builder.cc",
"util.cc",
],
static_libs: [
"libbase"
]
}
cc_binary_host {
name: "viewcompiler",
srcs: [
"main.cc",
],
static_libs: [
"libbase",
"libtinyxml2",
"libgflags",
"libviewcompiler",
],
}
cc_test_host {
name: "view-compiler-tests",
srcs: [
"util_test.cc",
],
static_libs: [
"libviewcompiler",
]
}

View File

@@ -0,0 +1,25 @@
# View Compiler
This directory contains an experimental compiler for layout files.
It will take a layout XML file and produce a CompiledLayout.java file with a
specialized layout inflation function.
To use it, let's assume you had a layout in `my_layout.xml` and your app was in
the Java language package `com.example.myapp`. Run the following command:
viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java
This will produce a `CompiledView.java`, which can then be compiled into your
Android app. Then to use it, in places where you would have inflated
`R.layouts.my_layout`, instead call `CompiledView.inflate`.
Precompiling views like this generally improves the time needed to inflate them.
This tool is still in its early stages and has a number of limitations.
* Currently only one layout can be compiled at a time.
* `merge` and `include` nodes are not supported.
* View compilation is a manual process that requires code changes in the
application.
* This only works for apps that do not use a custom layout inflater.
* Other limitations yet to be discovered.

View File

@@ -0,0 +1,7 @@
{
"presubmit": [
{
"name": "view-compiler-tests"
}
]
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "java_lang_builder.h"
#include "android-base/stringprintf.h"
using android::base::StringPrintf;
using std::string;
void JavaLangViewBuilder::Start() const {
out_ << StringPrintf("package %s;\n", package_.c_str())
<< "import android.content.Context;\n"
"import android.content.res.Resources;\n"
"import android.content.res.XmlResourceParser;\n"
"import android.util.AttributeSet;\n"
"import android.util.Xml;\n"
"import android.view.*;\n"
"import android.widget.*;\n"
"\n"
"public final class CompiledView {\n"
"\n"
"static <T extends View> T createView(Context context, AttributeSet attrs, View parent, "
"String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {"
"\n"
" if (factory2 != null) {\n"
" return (T)factory2.onCreateView(parent, name, context, attrs);\n"
" } else if (factory != null) {\n"
" return (T)factory.onCreateView(name, context, attrs);\n"
" }\n"
// TODO: find a way to call the private factory
" return null;\n"
"}\n"
"\n"
" public static View inflate(Context context) {\n"
" try {\n"
" LayoutInflater inflater = LayoutInflater.from(context);\n"
" LayoutInflater.Factory factory = inflater.getFactory();\n"
" LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n"
" Resources res = context.getResources();\n"
<< StringPrintf(" XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n",
package_.c_str(),
layout_name_.c_str())
<< " AttributeSet attrs = Xml.asAttributeSet(xml);\n"
// The Java-language XmlPullParser needs a call to next to find the start document tag.
" xml.next(); // start document\n";
}
void JavaLangViewBuilder::Finish() const {
out_ << " } catch (Exception e) {\n"
" return null;\n"
" }\n" // end try
" }\n" // end inflate
"}\n"; // end CompiledView
}
void JavaLangViewBuilder::StartView(const string& class_name) {
const string view_var = MakeVar("view");
const string layout_var = MakeVar("layout");
std::string parent = "null";
if (!view_stack_.empty()) {
const StackEntry& parent_entry = view_stack_.back();
parent = parent_entry.view_var;
}
out_ << " xml.next(); // <" << class_name << ">\n"
<< StringPrintf(" %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n",
class_name.c_str(),
view_var.c_str(),
parent.c_str(),
class_name.c_str())
<< StringPrintf(" if (%s == null) %s = new %s(context, attrs);\n",
view_var.c_str(),
view_var.c_str(),
class_name.c_str());
if (!view_stack_.empty()) {
out_ << StringPrintf(" ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n",
layout_var.c_str(),
parent.c_str());
}
view_stack_.push_back({class_name, view_var, layout_var});
}
void JavaLangViewBuilder::FinishView() {
const StackEntry var = view_stack_.back();
view_stack_.pop_back();
if (!view_stack_.empty()) {
const string& parent = view_stack_.back().view_var;
out_ << StringPrintf(" xml.next(); // </%s>\n", var.class_name.c_str())
<< StringPrintf(" %s.addView(%s, %s);\n",
parent.c_str(),
var.view_var.c_str(),
var.layout_params_var.c_str());
} else {
out_ << StringPrintf(" return %s;\n", var.view_var.c_str());
}
}
const std::string JavaLangViewBuilder::MakeVar(std::string prefix) {
std::stringstream v;
v << prefix << view_id_++;
return v.str();
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef JAVA_LANG_BUILDER_H_
#define JAVA_LANG_BUILDER_H_
#include <iostream>
#include <sstream>
#include <vector>
// Build Java language code to instantiate views.
//
// This has a very small interface to make it easier to generate additional
// backends, such as a direct-to-DEX version.
class JavaLangViewBuilder {
public:
JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout)
: package_(package), layout_name_(layout_name), out_(out) {}
// Begin generating a class. Adds the package boilerplate, etc.
void Start() const;
// Finish generating a class, closing off any open curly braces, etc.
void Finish() const;
// Begin creating a view (i.e. process the opening tag)
void StartView(const std::string& class_name);
// Finish a view, after all of its child nodes have been processed.
void FinishView();
private:
const std::string MakeVar(std::string prefix);
std::string const package_;
std::string const layout_name_;
std::ostream& out_;
size_t view_id_ = 0;
struct StackEntry {
// The class name for this view object
const std::string class_name;
// The variable name that is holding the view object
const std::string view_var;
// The variable name that holds the object's layout parameters
const std::string layout_params_var;
};
std::vector<StackEntry> view_stack_;
};
#endif // JAVA_LANG_BUILDER_H_

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gflags/gflags.h"
#include "java_lang_builder.h"
#include "util.h"
#include "tinyxml2.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace tinyxml2;
using std::string;
constexpr char kStdoutFilename[]{"stdout"};
DEFINE_string(package, "", "The package name for the generated class (required)");
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
namespace {
class ViewCompilerXmlVisitor : public XMLVisitor {
public:
ViewCompilerXmlVisitor(JavaLangViewBuilder* builder) : builder_(builder) {}
bool VisitEnter(const XMLDocument& /*doc*/) override {
builder_->Start();
return true;
}
bool VisitExit(const XMLDocument& /*doc*/) override {
builder_->Finish();
return true;
}
bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override {
builder_->StartView(element.Name());
return true;
}
bool VisitExit(const XMLElement& /*element*/) override {
builder_->FinishView();
return true;
}
private:
JavaLangViewBuilder* builder_;
};
} // end namespace
int main(int argc, char** argv) {
constexpr size_t kProgramName = 0;
constexpr size_t kFileNameParam = 1;
constexpr size_t kNumRequiredArgs = 2;
gflags::SetUsageMessage(
"Compile XML layout files into equivalent Java language code\n"
"\n"
" example usage: viewcompiler layout.xml --package com.example.androidapp");
gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true);
gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package");
if (argc != kNumRequiredArgs || cmd.is_default) {
gflags::ShowUsageWithFlags(argv[kProgramName]);
return 1;
}
const char* const filename = argv[kFileNameParam];
const string layout_name = FindLayoutNameFromFilename(filename);
// We want to generate Java language code to inflate exactly this layout. This means
// generating code to walk the resource XML too.
XMLDocument xml;
xml.LoadFile(filename);
std::ofstream outfile;
if (FLAGS_out != kStdoutFilename) {
outfile.open(FLAGS_out);
}
JavaLangViewBuilder builder{
FLAGS_package, layout_name, FLAGS_out == kStdoutFilename ? std::cout : outfile};
ViewCompilerXmlVisitor visitor{&builder};
xml.Accept(&visitor);
return 0;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util.h"
using std::string;
// TODO: see if we can borrow this from somewhere else, like aapt2.
string FindLayoutNameFromFilename(const string& filename) {
size_t start = filename.rfind("/");
if (start == string::npos) {
start = 0;
} else {
start++; // advance past '/' character
}
size_t end = filename.find(".", start);
return filename.substr(start, end - start);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTIL_H_
#define UTIL_H_
#include <string>
std::string FindLayoutNameFromFilename(const std::string& filename);
#endif // UTIL_H_

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util.h"
#include "gtest/gtest.h"
using std::string;
TEST(UtilTest, FindLayoutNameFromFilename) {
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("foo/bar.xml"));
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("bar.xml"));
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("./foo/bar.xml"));
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("/foo/bar.xml"));
}