/*
* Created by LuaView.
* Copyright (c) 2017, Alibaba Group. All rights reserved.
*
* This source code is licensed under the MIT.
* For the full copyright and license information,please view the LICENSE file in the root directory of this source tree.
*/
package com.taobao.luaview.global;
import android.content.Context;
import android.os.StrictMode;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.URLUtil;
import com.taobao.luaview.debug.DebugConnection;
import com.taobao.luaview.fun.binder.ui.UICustomPanelBinder;
import com.taobao.luaview.fun.mapper.ui.UIViewGroupMethodMapper;
import com.taobao.luaview.provider.ImageProvider;
import com.taobao.luaview.receiver.ConnectionStateChangeBroadcastReceiver;
import com.taobao.luaview.scriptbundle.LuaScriptManager;
import com.taobao.luaview.scriptbundle.ScriptBundle;
import com.taobao.luaview.scriptbundle.ScriptFile;
import com.taobao.luaview.scriptbundle.asynctask.SimpleTask1;
import com.taobao.luaview.userdata.ui.UDView;
import com.taobao.luaview.util.EncryptUtil;
import com.taobao.luaview.util.LogUtil;
import com.taobao.luaview.util.LuaUtil;
import com.taobao.luaview.util.NetworkUtil;
import com.taobao.luaview.view.LVCustomPanel;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Prototype;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import java.io.IOException;
import java.io.InputStream;
/**
* Core of LuaView functions
*
* @author song
* @date 17/2/22
* 主要功能描述
* 修改描述
* 下午5:04 song XXX
*/
public class LuaViewCore implements ConnectionStateChangeBroadcastReceiver.OnConnectionChangeListener {
//window luavalue
private static final String LV_WINDOW = "window";
//image provider class
private static Class<? extends ImageProvider> mImageProviderClazz;
//provider for loading image
private static ImageProvider mImageProvider;
//Context
private Context mContext;
//globals
public Globals mGlobals;
//userdata for render target
private UDView mWindowUserdata;
public interface CreatedCallback {
void onCreated(LuaViewCore luaViewCore);
}
//---------------------------------------静态方法------------------------------------------------
/**
* create a LuaViewCore
*
* @param context
* @return
*/
public static LuaViewCore create(final Context context) {
final Globals globals = LuaViewManager.createGlobals();
return createLuaViewCore(context, globals);
}
/**
* create LuaViewCore async (带返回值)
*
* @param context
*/
public static LuaViewCore createAsync(final Context context) {
final Globals globals = LuaViewManager.createGlobalsAsync();
return createLuaViewCore(context, globals);
}
/**
* create LuaViewCore async(兼容老的,没必要包装一层SimpleTask)
*
* @param context
* @param createdCallback
*/
public static void createAsync(final Context context, final LuaViewCore.CreatedCallback createdCallback) {
new SimpleTask1<LuaViewCore>() {
@Override
protected LuaViewCore doInBackground(Object... params) {
final Globals globals = LuaViewManager.createGlobalsAsync();
return createLuaViewCore(context, globals);
}
@Override
protected void onPostExecute(LuaViewCore luaViewCore) {
if (createdCallback != null) {
createdCallback.onCreated(luaViewCore);
}
}
}.executeInPool();
}
/**
* create LuaViewCore and setup everything
*
* @param context
* @param globals
* @return
*/
private static LuaViewCore createLuaViewCore(final Context context, final Globals globals) {
final LuaViewCore core = new LuaViewCore(context, globals);
if (LuaViewConfig.isOpenDebugger()) {//如果是debug,支持ide调试
core.openDebugger();
}
return core;
}
//-----------------------------------------load script------------------------------------------
/**
* 加载,可能是url,可能是Asset,可能是文件,也可能是脚本
* url : http or https, http://[xxx] or https://[xxx]
* TODO asset : folder or file, file://android_asset/[xxx]
* TODO file : folder or file, file://[xxx]
* TODO script: content://[xxx]
*
* @param urlOrFileOrScript
* @return
*/
public LuaViewCore load(final String urlOrFileOrScript) {
return load(urlOrFileOrScript, null, null);
}
public LuaViewCore load(final String urlOrFileOrScript, final LuaScriptLoader.ScriptExecuteCallback callback) {
return load(urlOrFileOrScript, null, callback);
}
public LuaViewCore load(final String urlOrFileOrScript, final String sha256) {
return load(urlOrFileOrScript, sha256, null);
}
public LuaViewCore load(final String urlOrFileOrScript, final String sha256, final LuaScriptLoader.ScriptExecuteCallback callback) {
if (!TextUtils.isEmpty(urlOrFileOrScript)) {
if (URLUtil.isNetworkUrl(urlOrFileOrScript)) {//url, http:// or https://
loadUrl(urlOrFileOrScript, sha256, callback);
} else {
loadFile(urlOrFileOrScript, callback);
}
//TODO other schema
} else if (callback != null) {
callback.onScriptExecuted(null, false);
}
return this;
}
/**
* 直接加载网络脚本
*
* @param url http://[xxx] or https://[xxx]
* @return
*/
public LuaViewCore loadUrl(final String url, final String sha256) {
return loadUrl(url, sha256, null);
}
public LuaViewCore loadUrl(final String url, final String sha256, final LuaScriptLoader.ScriptExecuteCallback callback) {
updateUri(url);
if (!TextUtils.isEmpty(url)) {
new LuaScriptLoader(mContext).load(url, sha256, new LuaScriptLoader.ScriptLoaderCallback() {
@Override
public void onScriptLoaded(ScriptBundle bundle) {
if (callback == null || callback.onScriptPrepared(bundle) == false) {//脚本准备完成,且不第三方自己执行
loadScriptBundle(bundle, callback);
} else if (callback != null) {
callback.onScriptExecuted(url, false);
}
}
});
} else if (callback != null) {
callback.onScriptExecuted(null, false);
}
return this;
}
/**
* 加载 Asset 路径下的脚本
*
* @param assetPath folder path or file path
* @return
*/
public LuaViewCore loadAsset(final String assetPath) {
return loadAsset(assetPath, null);
}
public LuaViewCore loadAsset(final String assetPath, final LuaScriptLoader.ScriptExecuteCallback callback) {
//TODO
return this;
}
/**
* 加载脚本库,必须在主进程中执行,先判断asset下是否存在,再去文件系统中查找
*
* @param luaFileName plain file name or file://[xxx]
* @return
*/
public LuaViewCore loadFile(final String luaFileName) {
return loadFile(luaFileName, null);
}
public LuaViewCore loadFile(final String luaFileName, final LuaScriptLoader.ScriptExecuteCallback callback) {
updateUri(luaFileName);
if (!TextUtils.isEmpty(luaFileName)) {
this.loadFileInternal(luaFileName, callback);//加载文件
} else {
if (callback != null) {
callback.onScriptExecuted(getUri(), false);
}
}
return this;
}
/**
* 加载脚本
*
* @param script
* @return
*/
public LuaViewCore loadScript(final String script) {
return loadScript(script, null);
}
public LuaViewCore loadScript(final String script, final LuaScriptLoader.ScriptExecuteCallback callback) {
if (!TextUtils.isEmpty(script)) {
this.loadScriptInternal(new ScriptFile(script, EncryptUtil.md5Hex(script)), callback);
} else {
if (callback != null) {
callback.onScriptExecuted(getUri(), false);
}
}
return this;
}
/**
* 加载 Script File
*
* @param scriptFile
* @return
*/
public LuaViewCore loadScript(final ScriptFile scriptFile) {
return loadScript(scriptFile, null);
}
public LuaViewCore loadScript(final ScriptFile scriptFile, final LuaScriptLoader.ScriptExecuteCallback callback) {
if (scriptFile != null) {
this.loadScriptInternal(scriptFile, callback);
} else if (callback != null) {
callback.onScriptExecuted(getUri(), false);
}
return this;
}
/**
* 加载 Script Bundle
*
* @param scriptBundle
* @return
*/
public LuaViewCore loadScriptBundle(final ScriptBundle scriptBundle) {
return loadScriptBundle(scriptBundle, null);
}
public LuaViewCore loadScriptBundle(final ScriptBundle scriptBundle, final LuaScriptLoader.ScriptExecuteCallback callback) {
loadScriptBundle(scriptBundle, LuaResourceFinder.DEFAULT_MAIN_ENTRY, callback);
return this;
}
public LuaViewCore loadScriptBundle(final ScriptBundle scriptBundle, final String mainScriptFileName, final LuaScriptLoader.ScriptExecuteCallback callback) {
if (scriptBundle != null) {
if (mGlobals != null && mGlobals.getLuaResourceFinder() != null) {
mGlobals.getLuaResourceFinder().setScriptBundle(scriptBundle);
}
if (scriptBundle.containsKey(mainScriptFileName)) {
final ScriptFile scriptFile = scriptBundle.getScriptFile(mainScriptFileName);
loadScript(scriptFile, callback);
return this;
}
}
if (callback != null) {
callback.onScriptExecuted(getUri(), false);
}
return this;
}
/**
* load prototype (lua bytecode or sourcecode)
*
* @param inputStream
* @return
*/
public LuaViewCore loadPrototype(final InputStream inputStream, final String name, final LuaScriptLoader.ScriptExecuteCallback callback) {
new SimpleTask1<LuaValue>() {
@Override
protected LuaValue doInBackground(Object... params) {
try {
if (mGlobals != null) {
Prototype prototype = mGlobals.loadPrototype(inputStream, name, "bt");
if (prototype != null) {
LuaValue result = mGlobals.load(prototype, name);
return result;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(LuaValue value) {
final LuaValue activity = CoerceJavaToLua.coerce(mContext);
final LuaValue viewObj = CoerceJavaToLua.coerce(LuaViewCore.this);
if (callback == null || callback.onScriptCompiled(value, activity, viewObj) == false) {
executeScript(value, activity, viewObj, callback);
}
}
}.executeInPool();
return this;
}
//---------------------------------------注册函数----------------------------------------------
/**
* 加载一个binder,可以用作覆盖老功能
* Lib 必须注解上 LuaViewLib
*
* @param binders
* @return
*/
public LuaViewCore registerLibs(LuaValue... binders) {
if (mGlobals != null && binders != null) {
for (LuaValue binder : binders) {
mGlobals.tryLazyLoad(binder);
}
}
return this;
}
/**
* 注册一个名称到该lua对象的命名空间中
*
* @param luaName
* @param obj
* @return
*/
public LuaViewCore register(final String luaName, final Object obj) {
if (mGlobals != null && !TextUtils.isEmpty(luaName)) {
final LuaValue value = mGlobals.get(luaName);
if (obj != value) {
mGlobals.set(luaName, CoerceJavaToLua.coerce(obj));
}
} else {
LogUtil.e("name " + luaName + " is invalid!");
}
return this;
}
/**
* 注册一个名称到该lua对象的命名空间中
*
* @param clazz
* @return
*/
public LuaViewCore registerPanel(final Class<? extends LVCustomPanel> clazz) {
return registerPanel(clazz != null ? clazz.getSimpleName() : null, clazz);
}
/**
* 注册一个名称到该lua对象的命名空间中
*
* @param luaName
* @param clazz
* @return
*/
public LuaViewCore registerPanel(final String luaName, final Class<? extends LVCustomPanel> clazz) {
if (mGlobals != null && !TextUtils.isEmpty(luaName) && (clazz != null && clazz.getSuperclass() == LVCustomPanel.class)) {
final LuaValue value = mGlobals.get(luaName);
if (value == null || value.isnil()) {
mGlobals.tryLazyLoad(new UICustomPanelBinder(clazz, luaName));
} else {
LogUtil.e("panel name " + luaName + " is already registered!");
}
} else {
LogUtil.e("name " + luaName + " is invalid or Class " + clazz + " is not subclass of " + LVCustomPanel.class.getSimpleName());
}
return this;
}
/**
* 解注册一个命名空间中的名字
*
* @param luaName
* @return
*/
public LuaViewCore unregister(final String luaName) {
if (mGlobals != null && !TextUtils.isEmpty(luaName)) {
mGlobals.set(luaName, LuaValue.NIL);
}
return this;
}
//----------------------------------------call lua function-------------------------------------
/**
* 调用lua的某个全局函数
*
* @param funName
* @param objs
* @return
*/
public Object callLuaFunction(String funName, Object... objs) {
if (mGlobals != null && funName != null) {
final LuaValue callback = mGlobals.get(funName);
return LuaUtil.callFunction(callback, objs);
}
return LuaValue.NIL;
}
//----------------------------------------Image Provider----------------------------------------
/**
* 注册ImageProvider
*/
public static void registerImageProvider(final Class<? extends ImageProvider> clazz) {
mImageProviderClazz = clazz;
}
/**
* 获取ImageProvider
*
* @return
*/
public static ImageProvider getImageProvider() {
if (mImageProvider == null && mImageProviderClazz != null) {
try {
mImageProvider = mImageProviderClazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return mImageProvider;
}
//----------------------------------------setup functions---------------------------------------
/**
* 设置使用标准语法
*
* @param standardSyntax
*/
public void setUseStandardSyntax(boolean standardSyntax) {
if (mGlobals != null) {
mGlobals.setUseStandardSyntax(standardSyntax);
}
}
/**
* 刷新容器是否可以刷新(用在RefreshCollectionView初始化的地方)
*
* @param enable
*/
public void setRefreshContainerEnable(boolean enable) {
if (this.mGlobals != null) {
this.mGlobals.isRefreshContainerEnable = enable;
}
}
/**
* 刷新容器是否可以刷新(用在RefreshCollectionView初始化的地方)
*/
public boolean isRefreshContainerEnable() {
return this.mGlobals != null ? this.mGlobals.isRefreshContainerEnable : true;
}
public String getUri() {
if (mGlobals != null && mGlobals.getLuaResourceFinder() != null) {
return mGlobals.getLuaResourceFinder().getUri();
}
return null;
}
public Globals getGlobals() {
return mGlobals;
}
//-------------------------------------------私有------------------------------------------------
/**
* TODO 优化
* 创建LuaView的methods,这里可以优化,实现更加优雅,其实就是将window注册成一个userdata,并且userdata是UDViewGroup
*
* @return
*/
private static LuaTable createMetaTableForLuaView() {
return LuaViewManager.createMetatable(UIViewGroupMethodMapper.class);
}
/**
* @param globals
*/
private LuaViewCore(Context context, Globals globals) {
init(context);
this.mContext = context;
this.mGlobals = globals;
}
private void init(Context context) {
//常量初始化
Constants.init(context);
//初始化脚本管理
LuaScriptManager.init(context);
}
/**
* 开启debugger
*/
public void openDebugger() {
loadFile("debug.lua", new LuaScriptLoader.ScriptExecuteCallback() {
@Override
public boolean onScriptPrepared(ScriptBundle bundle) {
return false;
}
@Override
public boolean onScriptCompiled(LuaValue value, LuaValue activity, LuaValue obj) {
return false;
}
@Override
public void onScriptExecuted(String uri, boolean executedSuccess) {
if (executedSuccess && mGlobals != null) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectNetwork() // or .detectAll() for all detectable problems,主线程执行socket
.build());
mGlobals.debugConnection = DebugConnection.create();
}
}
});
}
//-----------------------------------------私有load函数------------------------------------------
private void updateUri(String uri) {
if (mGlobals != null && mGlobals.getLuaResourceFinder() != null) {
mGlobals.getLuaResourceFinder().setUri(uri);
}
}
/**
* 初始化
*
* @param luaFileName
*/
private LuaViewCore loadFileInternal(final String luaFileName, final LuaScriptLoader.ScriptExecuteCallback callback) {
new SimpleTask1<LuaValue>() {
@Override
protected LuaValue doInBackground(Object... params) {
if (mGlobals != null) {
if (mGlobals.isInited) {
try {
return mGlobals.loadfile(luaFileName);
} catch (Exception e) {
e.printStackTrace();
LogUtil.e("[Load Script Failed]", luaFileName, e);
}
} else {
try {
Thread.sleep(16);
return doInBackground(params);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onPostExecute(LuaValue value) {
final LuaValue activity = CoerceJavaToLua.coerce(mContext);
final LuaValue viewObj = CoerceJavaToLua.coerce(LuaViewCore.this);
if (callback == null || callback.onScriptCompiled(value, activity, viewObj) == false) {
//执行脚本,在主线程
executeScript(value, activity, viewObj, callback);
}
}
}.executeInPool();//TODO 这里使用execute,而不是executeInPoll,与createGlobalAsync保持一致
return this;
}
/**
* 加载纯脚本
*
* @param scriptFile
*/
private LuaViewCore loadScriptInternal(final ScriptFile scriptFile, final LuaScriptLoader.ScriptExecuteCallback callback) {
new SimpleTask1<LuaValue>() {//load async
@Override
protected LuaValue doInBackground(Object... params) {
if (mGlobals != null) {
if (mGlobals.isInited) {
if (scriptFile != null) {//prototype
String filePath = scriptFile.getFilePath();
if (scriptFile.prototype != null) {//prototype
return mGlobals.load(scriptFile.prototype, filePath);
} else {//source code
try {
return mGlobals.load(scriptFile.getScriptString(), filePath);
} catch (Exception e) {
e.printStackTrace();
LogUtil.e("[Load Script Failed]", filePath, e);
}
}
}
} else {
try {
Thread.sleep(16);
return doInBackground(params);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onPostExecute(LuaValue value) {
final LuaValue activity = CoerceJavaToLua.coerce(mContext);
final LuaValue viewObj = CoerceJavaToLua.coerce(LuaViewCore.this);
if (callback == null || callback.onScriptCompiled(value, activity, viewObj) == false) {
//执行脚本,在主线程
executeScript(value, activity, viewObj, callback);
}
}
}.executeInPool();//TODO 这里使用execute,而不是executeInPoll,与createGlobalAsync保持一致
return this;
}
/**
* 执行脚本
*
* @param value
* @param activity
* @param viewObj
* @param callback
*/
public boolean executeScript(LuaValue value, LuaValue activity, LuaValue viewObj, final LuaScriptLoader.ScriptExecuteCallback callback) {
try {
if (mGlobals != null && value != null) {
mGlobals.saveContainer(getRenderTarget());
mGlobals.set(LV_WINDOW, mWindowUserdata);//TODO 优化到其他地方?,设置window对象
value.call(activity, viewObj);
mGlobals.restoreContainer();
if (callback != null) {
callback.onScriptExecuted(getUri(), true);
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.e("[Executed Script Failed]", e);
}
if (callback != null) {
callback.onScriptExecuted(getUri(), false);
}
return false;
}
//-----------------------------------------网络回调----------------------------------------------
@Override
public void onConnectionClosed() {
if (mWindowUserdata != null) {
if (mWindowUserdata.getCallback() != null && mWindowUserdata.getCallback().istable()) {
LuaUtil.callFunction(LuaUtil.getFunction(mWindowUserdata.getCallback(), "onConnectionClosed", "OnConnectionClosed"));
}
}
}
@Override
public void onMobileConnected() {
if (mWindowUserdata != null) {
if (mWindowUserdata.getCallback() != null && mWindowUserdata.getCallback().istable()) {
LuaUtil.callFunction(LuaUtil.getFunction(mWindowUserdata.getCallback(), "onMobileConnected", "OnMobileConnected"));
}
}
}
@Override
public void onWifiConnected() {
if (mWindowUserdata != null) {
if (mWindowUserdata.getCallback() != null && mWindowUserdata.getCallback().istable()) {
LuaUtil.callFunction(LuaUtil.getFunction(mWindowUserdata.getCallback(), "onWifiConnected", "OnWifiConnected"));
}
}
}
//----------------------------------------getter and setter-------------------------------------
/**
* set window userdata
*
* @param userdata
* @return
*/
public LuaViewCore setWindowUserdata(UDView userdata) {
this.mWindowUserdata = userdata;
return this;
}
/**
* get userdata for window
*
* @return
*/
public LuaValue getWindowUserdata() {
return this.mWindowUserdata;
}
/**
* set render target
*
* @param viewGroup
* @return
*/
public LuaViewCore setRenderTarget(ViewGroup viewGroup) {
if (mGlobals != null) {
mGlobals.setRenderTarget(viewGroup);
}
return this;
}
/**
* get render target
*
* @return
*/
public ViewGroup getRenderTarget() {
return mGlobals != null ? mGlobals.getRenderTarget() : null;
}
//----------------------------------------显示的生命周期 管理-------------------------------------
/**
* View初始化的时候注册监听
*/
public void onAttached() {
}
/**
* 显示
*
* @param visibility
*/
public void onShow(int visibility) {
if (visibility == View.VISIBLE) {//onShow
NetworkUtil.registerConnectionChangeListener(mContext, this);//show之前注册
}
}
/**
* 隐藏
*
* @param visibility
*/
public void onHide(int visibility) {
if (visibility != View.VISIBLE) {//onHide
NetworkUtil.unregisterConnectionChangeListener(mContext, this);//hide之后调用
}
}
/**
* 在onDetached的时候清空cache
*/
public void onDetached() {
clearCache();
}
/**
* 销毁的时候从外部调用,清空所有外部引用
*/
public void onDestroy() {
clearCache();
if (mGlobals != null) {
mGlobals.onDestroy();
mGlobals = null;
}
mContext = null;
mWindowUserdata = null;
}
/**
* 清空cache
*/
private void clearCache() {
if (mGlobals != null) {
mGlobals.clearCache();
}
NetworkUtil.unregisterConnectionChangeListener(mContext, this);
}
}