Skip to content

Commit

Permalink
chore: init project
Browse files Browse the repository at this point in the history
  • Loading branch information
7hens committed Oct 11, 2019
0 parents commit 7a4dfe3
Show file tree
Hide file tree
Showing 57 changed files with 1,568 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.gradle/
.idea/
build/
nocode/
captures/
*.iml
.DS_Store
local.properties
.externalNativeBuild
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Thens Wong

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
35 changes: 35 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# AndroidX2J

[![jitpack](https://jitpack.io/v/7hens/android-x2j.svg)](https://jitpack.io/#7hens/android-x2j)
[![license](https://img.shields.io/github/license/7hens/android-x2j.svg)](https://github.com/7hens/android-x2j/blob/master/LICENSE)
[![stars](https://img.shields.io/github/stars/7hens/android-x2j.svg?style=social)](https://github.com/7hens/android-x2j)

The missing gradle plugin for [X2C](https://github.com/iReaderAndroid/X2C).

## Setting up the dependency

in build/gradle

```groovy
buildscript {
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.github.7hens:android-x2j:<last_version>'
}
}
```

in app/build.gradle

```groovy
android {
}
// make sure this line comes AFTER android block
apply plugin: 'android-x2j'
```


76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Android X2J

[![jitpack](https://jitpack.io/v/7hens/android-x2j.svg)](https://jitpack.io/#7hens/android-x2j)
[![license](https://img.shields.io/github/license/7hens/android-x2j.svg)](https://github.com/7hens/android-x2j/blob/master/LICENSE)
[![stars](https://img.shields.io/github/stars/7hens/android-x2j.svg?style=social)](https://github.com/7hens/android-x2j)

Android X2J(XML to Java)是一个 gradle 插件,可以让你零成本使用 [X2C](https://github.com/iReaderAndroid/X2C) (在编译时将你 XML 布局翻译成的 Java 代码)。

Android X2J 使用了 gradle transform api,会在 APP 构建期间自动将 java 字节码做如下转换。

```plain
activity.setContentView(R.layout.activity_main)
=>> X2J.setContentView(activity, R.layout.activity_main)
layoutInflater.inflate(R.layout.view_item, parent)
=>> X2J.inflate(layoutInflater, R.layout.view_item, parent)
View.inflate(context, R.layout.view_item, parent)
=>> X2J.inflate(context, R.layout.view_item, parent)
```

> 实际上,`X2J.setContentView` 直接调用了 `X2C.setContentView`。因为 `X2C` 类本身有 bug,所以才有了 `X2J` 类。
!> 注意,因为 X2C 自身的原因,目前 Android X2J 并不支持 kotlin-kapt。

## 使用方法

1) 配置根目录的 build/gradle。

```groovy
buildscript {
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.github.7hens:android-x2j:<last_version>'
}
}
```

2) 在 app/build.gradle 中使用插件。

```groovy
// 必须在 android {} 代码块之后添加
apply plugin: 'android-x2j'
```

3) 好了,接下来 Android X2J 会自动把 XML 布局翻译成 Java 代码(当然,这归功于 X2C),并打包到 APK 中。

下面是 sample 模块里面的 MainActivity 的源码:

```java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater.from(this).inflate(R.layout.fragmetn_layout, null);
LayoutInflater.from(this).inflate(R.layout.fragmetn_layout, null, false);
}
}
```

而下面则是使用了 X2J 插件打包后的 apk 反编译后的对应代码。

```java
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
X2J.setContentView(this, R.layout.activity_main);
X2J.inflate(LayoutInflater.from(this), (int) R.layout.fragmetn_layout, null);
X2J.inflate(LayoutInflater.from(this), (int) R.layout.fragmetn_layout, null, false);
}
}
```
1 change: 1 addition & 0 deletions android-x2j/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
45 changes: 45 additions & 0 deletions android-x2j/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
`kotlin-dsl`
id("com.github.dcendents.android-maven")
id("maven-publish")
}

group = "com.github.7hens"
version = "-SNAPSHOT"

repositories {
google()
jcenter()
mavenLocal()
}

//gradlePlugin {
// plugins {
// register("android-x2j") {
// id = "android-x2j"
// implementationClass = "androidx2j.X2JPlugin"
// }
// }
//}

dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
compileOnly(gradleApi())
implementation(localGroovy())
implementation("com.android.tools.build:gradle:3.2.1")
implementation("org.javassist:javassist:3.22.0-GA")
implementation("com.squareup:javapoet:1.11.1")
implementation("commons-io:commons-io:2.5")
implementation("com.zhangyue.we:x2c-apt:1.1.2")
}

afterEvaluate {
val x2jFile = rootProject.file("x2c-compat/src/main/java/androidx2j/X2J.java")
x2jFile.inputStream().reader().use { reader ->
val text = reader.readText()
file("src/main/kotlin/androidx2j/X2J.kt").outputStream().writer().use { writer ->
writer.write("package androidx2j\n\nconst val X2J_CODE = \"\"\"$text\"\"\"")
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Tencent is pleased to support the open source community by making Tencent Shadow available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.
*
*/

package androidx2j

import javassist.*
import javassist.bytecode.*
import javassist.convert.TransformCall
import javassist.convert.Transformer

class ObjectInvokeCodeConverter : CodeConverter() {

/**
* 将一个方法调用改为静态方法调用,并将原被调用对象作为静态方法的第一个参数
* @param origMethod 原方法
* @param substMethod 新方法。必须为静态方法,且该方法签名与原方法一样,仅在第一个参数处多一个参数,类型为原方法的被调用类型。
*/
fun redirectMethodInvokeToStatic(origMethod: CtMethod, substMethod: CtMethod) {
try {
if (Modifier.isStatic(origMethod.modifiers)) {
transformers = TransformCall(transformers, origMethod, substMethod)
} else {
transformers = StaticTransform(transformers, origMethod, substMethod)
}
} catch (e: NotFoundException) {
throw CannotCompileException(e)
}

}

private class StaticTransform(next: Transformer?, origMethod: CtMethod, substMethod: CtMethod)
: TransformCall(next, origMethod, substMethod) {
init {
methodDescriptor = origMethod.methodInfo2.descriptor
}

@Throws(BadBytecode::class)
override fun match(c: Int, pos: Int, iterator: CodeIterator, typedesc: Int, cp: ConstPool): Int {
if (newIndex == 0) {
val desc = Descriptor.insertParameter(classname, methodDescriptor)
val nt = cp.addNameAndTypeInfo(newMethodname, desc)
val ci = cp.addClassInfo(newClassname)
newIndex = cp.addMethodrefInfo(ci, nt)
constPool = cp
}
iterator.writeByte(Opcode.INVOKESTATIC, pos)
iterator.write16bit(newIndex, pos + 1)
return pos
}
}
}
44 changes: 44 additions & 0 deletions android-x2j/src/main/kotlin/androidx2j/X2J.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package androidx2j

const val X2J_CODE = """package androidx2j;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.zhangyue.we.x2c.X2C;
import com.zhangyue.we.x2c.ano.Xml;
@Xml(layouts = {"o_0_layout"})
public final class X2J {
public static void setContentView(Activity activity, int layoutId) {
X2C.setContentView(activity, layoutId);
}
public static View inflate(Context context, int layoutId, ViewGroup parent) {
return inflate(context, layoutId, parent, parent != null);
}
public static View inflate(Context context, int layoutId, ViewGroup parent, boolean attach) {
return inflate(LayoutInflater.from(context), layoutId, parent, attach);
}
public static View inflate(LayoutInflater inflater, int layoutId, ViewGroup parent) {
return inflate(inflater, layoutId, parent, parent != null);
}
public static View inflate(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attach) {
View view = X2C.getView(inflater.getContext(), layoutId);
if (view != null) {
if (parent != null && attach) {
parent.addView(view);
}
return view;
} else {
return inflater.inflate(layoutId, parent, attach);
}
}
}
"""
66 changes: 66 additions & 0 deletions android-x2j/src/main/kotlin/androidx2j/X2JClassConverter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package androidx2j

import javassist.ClassPool
import java.io.DataOutputStream
import java.io.File
import java.io.InputStream
import java.io.OutputStream

/**
* @author 7hens
*/
class X2JClassConverter(private val classPool: ClassPool) {
private val hookedClassNames = arrayOf(
"android.app.Activity",
"android.view.LayoutInflater",
"android.view.View")

private val codeConverter by lazy {
ObjectInvokeCodeConverter().apply {
val cInt = classPool.get("int")
val cBoolean = classPool.get("boolean")
val cContext = classPool.get("android.content.Context")
val cActivity = classPool.get("android.app.Activity")
val cView = classPool.get("android.view.View")
val cViewGroup = classPool.get("android.view.ViewGroup")
val cLayoutInflater = classPool.get("android.view.LayoutInflater")
val xX2J = classPool.get("androidx2j.X2J")

redirectMethodInvokeToStatic(
cActivity.getDeclaredMethod("setContentView", arrayOf(cInt)),
xX2J.getDeclaredMethod("setContentView", arrayOf(cActivity, cInt)))
redirectMethodInvokeToStatic(
cLayoutInflater.getDeclaredMethod("inflate", arrayOf(cInt, cViewGroup, cBoolean)),
xX2J.getDeclaredMethod("inflate", arrayOf(cLayoutInflater, cInt, cViewGroup, cBoolean)))
redirectMethodInvokeToStatic(
cLayoutInflater.getDeclaredMethod("inflate", arrayOf(cInt, cViewGroup)),
xX2J.getDeclaredMethod("inflate", arrayOf(cLayoutInflater, cInt, cViewGroup)))
redirectMethodInvokeToStatic(
cView.getDeclaredMethod("inflate", arrayOf(cContext, cInt, cViewGroup)),
xX2J.getDeclaredMethod("inflate", arrayOf(cContext, cInt, cViewGroup)))
}
}

fun convert(input: InputStream, output: OutputStream) {
val ctClass = classPool.makeClassIfNew(input)
if (ctClass.name !in arrayOf("com.zhangyue.we.x2c.X2C", "androidx2j.X2J")
&& ctClass.refClasses.any { it in hookedClassNames }) {
if (ctClass.isFrozen) {
ctClass.defrost()
}
ctClass.instrument(codeConverter)
ctClass.toBytecode(DataOutputStream(output))
ctClass.detach()
} else {
ctClass.toBytecode(DataOutputStream(output))
}
}

fun convert(input: File, output: File) {
input.inputStream().use { inputStream ->
output.outputStream().use { outputStream ->
convert(inputStream, outputStream)
}
}
}
}
Loading

0 comments on commit 7a4dfe3

Please sign in to comment.