From 9077e4928d9423f5e20355e8ea51950e5c1e8e4b Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Tue, 24 Oct 2017 15:49:45 +0800 Subject: [PATCH] add: build apk from js file --- app/build.gradle | 6 +- .../autojs/build/AutoJsApkBuilder.java | 104 ++++++++++++++ .../scriptdroid/ui/build/BuildActivity.java | 128 +++++++++++++++++- app/src/main/res/layout/activity_build.xml | 9 +- app/src/main/res/values/strings.xml | 11 +- .../runtime/api/ui/ConvertLayoutInflater.java | 15 +- gradle.properties | 2 +- inrt/src/main/AndroidManifest.xml | 7 +- .../com/stardust/auojs/inrt/MainActivity.java | 9 ++ 9 files changed, 275 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/stardust/scriptdroid/autojs/build/AutoJsApkBuilder.java diff --git a/app/build.gradle b/app/build.gradle index 8c368da5..35dee1c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/com/stardust/scriptdroid/autojs/build/AutoJsApkBuilder.java b/app/src/main/java/com/stardust/scriptdroid/autojs/build/AutoJsApkBuilder.java new file mode 100644 index 00000000..567dca3d --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/autojs/build/AutoJsApkBuilder.java @@ -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(); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/build/BuildActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/build/BuildActivity.java index 0a18d6d6..69cc5923 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/build/BuildActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/build/BuildActivity.java @@ -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); } } diff --git a/app/src/main/res/layout/activity_build.xml b/app/src/main/res/layout/activity_build.xml index 78631eb0..95769116 100644 --- a/app/src/main/res/layout/activity_build.xml +++ b/app/src/main/res/layout/activity_build.xml @@ -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"/> @@ -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"/> @@ -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"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 09001da3..e9f67f5a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -247,12 +247,21 @@ 选择要导入的文件 打包应用 选择 - 打包应用保存路径 + 打包应用保存位置 配置 应用名称 包名 版本名称 版本号 + 打包成功 + 安装 + 打包失败,错误信息: + apk文件已保存到 %s + 准备文件 + 构建中 + 打包中 + 清理临时文件 + 脚本文件路径 diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/ui/ConvertLayoutInflater.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/ui/ConvertLayoutInflater.java index 93e52e96..02b87194 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/ui/ConvertLayoutInflater.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/ui/ConvertLayoutInflater.java @@ -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,10 +43,10 @@ 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 + @Override public void apply(View view, String value, ViewGroup parent, Map attrs) { if (!(view instanceof TextView)) return; @@ -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); diff --git a/gradle.properties b/gradle.properties index f4c0978f..d85e1728 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/inrt/src/main/AndroidManifest.xml b/inrt/src/main/AndroidManifest.xml index 8e9e2b04..9d3358ec 100644 --- a/inrt/src/main/AndroidManifest.xml +++ b/inrt/src/main/AndroidManifest.xml @@ -1,15 +1,18 @@ + + android:theme="@style/AppTheme" + tools:replace="android:label"> diff --git a/inrt/src/main/java/com/stardust/auojs/inrt/MainActivity.java b/inrt/src/main/java/com/stardust/auojs/inrt/MainActivity.java index a023eeee..ef05d41f 100644 --- a/inrt/src/main/java/com/stardust/auojs/inrt/MainActivity.java +++ b/inrt/src/main/java/com/stardust/auojs/inrt/MainActivity.java @@ -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); + } + } }