add: build apk from js file

This commit is contained in:
hyb1996
2017-10-24 15:49:45 +08:00
parent 448d39b7e8
commit 9077e4928d
9 changed files with 275 additions and 16 deletions

View File

@@ -10,8 +10,8 @@ android {
applicationId "com.stardust.scriptdroid"
minSdkVersion 17
targetSdkVersion 23
versionCode 210
versionName "3.0.0 Alpha11"
versionCode 211
versionName "3.0.0 Alpha12"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
@@ -121,6 +121,8 @@ dependencies {
compile 'com.wang.avi:library:2.1.3'
compile 'org.apache.commons:commons-lang3:3.5'
compile 'com.github.hyb1996:FloatingCircularActionMenu:0.0.2'
compile(name: 'apkbuilder-release', ext: 'aar')
// RxJava
compile "io.reactivex.rxjava2:rxjava:2.1.0"
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

View File

@@ -0,0 +1,104 @@
package com.stardust.scriptdroid.autojs.build;
import com.stardust.autojs.apkbuilder.ApkBuilder;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.ui.build.BuildActivity;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by Stardust on 2017/10/24.
*/
public class AutoJsApkBuilder extends ApkBuilder {
public interface ProgressCallback {
void onPrepare(AutoJsApkBuilder builder);
void onBuild(AutoJsApkBuilder builder);
void onSign(AutoJsApkBuilder builder);
void onClean(AutoJsApkBuilder builder);
}
public static class AppConfig {
String appName;
String versionName;
int versionCode;
String jsPath;
public AppConfig(String appName, String versionName, int versionCode, String jsPath) {
this.appName = appName;
this.versionName = versionName;
this.versionCode = versionCode;
this.jsPath = jsPath;
}
}
private ProgressCallback mProgressCallback;
public AutoJsApkBuilder(InputStream apkInputStream, File outApkFile, String workspacePath) {
super(apkInputStream, outApkFile, workspacePath);
}
public AutoJsApkBuilder(File inFile, File outFile, String workspacePath) throws FileNotFoundException {
super(inFile, outFile, workspacePath);
}
public AutoJsApkBuilder setProgressCallback(ProgressCallback callback) {
mProgressCallback = callback;
return this;
}
@Override
public AutoJsApkBuilder prepare() throws IOException {
if (mProgressCallback != null) {
App.getApp().getUiHandler().post(() -> mProgressCallback.onPrepare(AutoJsApkBuilder.this));
}
return (AutoJsApkBuilder) super.prepare();
}
public AutoJsApkBuilder setScriptFile(String path) throws IOException {
replaceFile("assets/script.js", path);
return this;
}
public AutoJsApkBuilder withConfig(AppConfig config) throws IOException {
editManifest().setAppName(config.appName)
.setVersionName(config.versionName)
.setVersionCode(config.versionCode)
.commit();
setScriptFile(config.jsPath);
return this;
}
@Override
public AutoJsApkBuilder build() throws IOException {
if (mProgressCallback != null) {
App.getApp().getUiHandler().post(() -> mProgressCallback.onBuild(AutoJsApkBuilder.this));
}
return (AutoJsApkBuilder) super.build();
}
@Override
public AutoJsApkBuilder sign() throws Exception {
if (mProgressCallback != null) {
App.getApp().getUiHandler().post(() -> mProgressCallback.onSign(AutoJsApkBuilder.this));
}
return (AutoJsApkBuilder) super.sign();
}
@Override
public AutoJsApkBuilder cleanWorkspace() {
if (mProgressCallback != null) {
App.getApp().getUiHandler().post(() -> mProgressCallback.onClean(AutoJsApkBuilder.this));
}
return (AutoJsApkBuilder) super.cleanWorkspace();
}
}

View File

@@ -1,24 +1,46 @@
package com.stardust.scriptdroid.ui.build;
import android.annotation.SuppressLint;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.autojs.apkbuilder.ApkBuilder;
import com.stardust.pio.PFiles;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.build.AutoJsApkBuilder;
import com.stardust.scriptdroid.io.StorageFileProvider;
import com.stardust.scriptdroid.model.script.ScriptFile;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.ui.filechooser.FileChooserDialogBuilder;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.IntentUtil;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
import java.io.File;
import java.io.FileOutputStream;
import java.util.concurrent.Callable;
import butterknife.OnClick;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* Created by Stardust on 2017/10/22.
*/
@EActivity(R.layout.activity_build)
public class BuildActivity extends BaseActivity {
public class BuildActivity extends BaseActivity implements AutoJsApkBuilder.ProgressCallback {
public static final String EXTRA_SOURCE_FILE = BuildActivity.class.getName() + ".extra_source_file";
private static final String LOG_TAG = "BuildActivity";
@ViewById(R.id.source_path)
EditText mSourcePath;
@@ -36,6 +58,7 @@ public class BuildActivity extends BaseActivity {
@ViewById(R.id.version_code)
EditText mVersionCode;
private MaterialDialog mProgressDialog;
@AfterViews
void setupViews() {
@@ -47,11 +70,110 @@ public class BuildActivity extends BaseActivity {
}
@SuppressLint("SetTextI18n")
private void setupWithSourceFile(ScriptFile file) {
mSourcePath.setText(file.getPath());
mOutputPath.setText(file.getParent());
mAppName.setText(file.getSimplifiedName());
mPackageName.setText("org.autojs.example_" + file.getSimplifiedName());
}
@Click(R.id.select_source)
void selectSourceFilePath() {
String initialDir = new File(mSourcePath.getText().toString()).getParent();
new FileChooserDialogBuilder(this)
.title(R.string.text_source_file_path)
.dir(initialDir == null ? StorageFileProvider.DEFAULT_DIRECTORY_PATH : initialDir)
.justScriptFile()
.singleChoice(file -> mSourcePath.setText(file.getPath()))
.show();
}
@Click(R.id.select_output)
void selectOutputDirPath() {
String initialDir = new File(mOutputPath.getText().toString()).exists() ?
mOutputPath.getText().toString() : StorageFileProvider.DEFAULT_DIRECTORY_PATH;
new FileChooserDialogBuilder(this)
.title(R.string.text_source_file_path)
.dir(initialDir)
.chooseDir()
.singleChoice(dir -> mOutputPath.setText(dir.getPath()))
.show();
}
@Click(R.id.fab)
void buildApk() {
String jsPath = mSourcePath.getText().toString();
String versionName = mVersionName.getText().toString();
int versionCode = Integer.parseInt(mVersionCode.getText().toString());
String appName = mAppName.getText().toString();
File tmpDir = new File(getCacheDir(), "build/");
File outApk = new File(mOutputPath.getText().toString(),
String.format("%s_v%s.apk", appName, versionName));
showProgressDialog();
Observable.fromCallable(() ->
new AutoJsApkBuilder(getAssets().open("template.apk"), outApk, tmpDir.getPath())
.setProgressCallback(BuildActivity.this)
.prepare()
.withConfig(new AutoJsApkBuilder.AppConfig(appName, versionName, versionCode, jsPath))
.build()
.sign()
.cleanWorkspace()
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(apkBuilder -> onBuildSuccessful(outApk),
this::onBuildFailed);
}
private void showProgressDialog() {
mProgressDialog = new MaterialDialog.Builder(this)
.progress(true, 100)
.content(R.string.text_on_progress)
.show();
}
private void onBuildFailed(Throwable error) {
mProgressDialog.dismiss();
mProgressDialog = null;
Toast.makeText(this, getString(R.string.text_build_failed) + error.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(LOG_TAG, "Build failed", error);
}
private void onBuildSuccessful(File outApk) {
mProgressDialog.dismiss();
mProgressDialog = null;
new MaterialDialog.Builder(this)
.title(R.string.text_build_successfully)
.content(getString(R.string.format_build_successfully, outApk.getPath()))
.positiveText(R.string.text_install)
.negativeText(R.string.cancel)
.onPositive((dialog, which) ->
IntentUtil.installApk(BuildActivity.this, outApk.getPath())
)
.show();
}
@Override
public void onPrepare(AutoJsApkBuilder builder) {
mProgressDialog.setContent(R.string.apk_builder_prepare);
}
@Override
public void onBuild(AutoJsApkBuilder builder) {
mProgressDialog.setContent(R.string.apk_builder_build);
}
@Override
public void onSign(AutoJsApkBuilder builder) {
mProgressDialog.setContent(R.string.apk_builder_package);
}
@Override
public void onClean(AutoJsApkBuilder builder) {
mProgressDialog.setContent(R.string.apk_builder_clean);
}
}

View File

@@ -51,7 +51,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="文件"
android:text="@string/text_file"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?android:textColorSecondary"/>
@@ -69,8 +69,7 @@
android:id="@+id/source_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="脚本文件路径"
android:maxLines="1"/>
android:hint="@string/text_source_file_path"/>
</android.support.design.widget.TextInputLayout>
@@ -99,8 +98,7 @@
android:id="@+id/output_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_output_apk_path"
android:maxLines="1"/>
android:hint="@string/text_output_apk_path"/>
</android.support.design.widget.TextInputLayout>
@@ -185,6 +183,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_version_code"
android:inputType="number"
android:text="1"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout>

View File

@@ -247,12 +247,21 @@
<string name="text_select_file_to_import">选择要导入的文件</string>
<string name="text_build_apk">打包应用</string>
<string name="text_select">选择</string>
<string name="text_output_apk_path">打包应用保存路径</string>
<string name="text_output_apk_path">打包应用保存位置</string>
<string name="text_config">配置</string>
<string name="text_app_name">应用名称</string>
<string name="text_package_name">包名</string>
<string name="text_version_name">版本名称</string>
<string name="text_version_code">版本号</string>
<string name="text_build_successfully">打包成功</string>
<string name="text_install">安装</string>
<string name="text_build_failed">打包失败,错误信息:</string>
<string name="format_build_successfully" formatted="true">apk文件已保存到 %s</string>
<string name="apk_builder_prepare">准备文件</string>
<string name="apk_builder_build">构建中</string>
<string name="apk_builder_package">打包中</string>
<string name="apk_builder_clean">清理临时文件</string>
<string name="text_source_file_path">脚本文件路径</string>
<string-array name="ad_showing_mode_keys">

View File

@@ -2,6 +2,7 @@ package com.stardust.autojs.runtime.api.ui;
import android.content.Context;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -11,10 +12,19 @@ import com.stardust.autojs.runtime.api.ui.widget.JsFrameLayout;
import com.stardust.autojs.runtime.api.ui.xml.XmlConverter;
import com.stardust.util.MapEntries;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
/**
* Created by Stardust on 2017/5/14.
*/
@@ -33,7 +43,7 @@ public class ConvertLayoutInflater implements JsLayoutInflater {
.entry("password", InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_CLASS_TEXT)
.entry("email", InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_CLASS_TEXT)
.entry("date", InputType.TYPE_DATETIME_VARIATION_DATE | InputType.TYPE_CLASS_DATETIME)
.entry("time", InputType.TYPE_DATETIME_VARIATION_TIME| InputType.TYPE_CLASS_DATETIME)
.entry("time", InputType.TYPE_DATETIME_VARIATION_TIME | InputType.TYPE_CLASS_DATETIME)
.map();
@Override
@@ -56,6 +66,7 @@ public class ConvertLayoutInflater implements JsLayoutInflater {
@Override
public View inflate(Context context, String xml) {
try {
String androidLayoutXml = XmlConverter.convertToAndroidLayout(xml);
JsFrameLayout root = new JsFrameLayout(context);

View File

@@ -15,6 +15,6 @@
#Mon Jan 23 10:16:46 CST 2017
systemProp.https.proxyPort=1080
systemProp.http.proxyHost=127.0.0.1
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xms8192m -Xmx8192m -Dfile.encoding=UTF-8
systemProp.https.proxyHost=127.0.0.1
systemProp.http.proxyPort=1080

View File

@@ -1,15 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.stardust.auojs.inrt">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="inrt"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:replace="android:label">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

View File

@@ -1,5 +1,7 @@
package com.stardust.auojs.inrt;
import android.Manifest;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
@@ -14,6 +16,7 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkPermissions();
try {
InputStream is = getAssets().open("script.js");
byte[] data = new byte[is.available()];
@@ -25,4 +28,10 @@ public class MainActivity extends AppCompatActivity {
e.printStackTrace();
}
}
private void checkPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 11124);
}
}
}