JS在服务器端,可以随时更新。给用户的APP预留一些常用工具类,通过JS 调用android 反射生成对象,进而调用android预留的工具类方法。比如各种uitl
js代码:
function invoke(){
JSTest.webapp_invoke("com.android.test2mvvm.Test2_App","test");
JSTest.webapp_invoke("com.android.test2mvvm.Test2_App","test","good");
JSTest.webapp_invoke("com.android.test2mvvm.Test2_App","test","good",123456);
JSTest.webapp_invoke("com.android.test2mvvm.Test2_App","test","false",123456);
}
被调用android端代码:
binding.Webview.addJavascriptInterface(new WebApp() {
@JavascriptInterface
public void showToast(String msg) {
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public void webapp_invoke(String class_name, String method_name, String string) {
super.webapp_invoke(class_name, method_name, super.string_to_object(string));
}
@JavascriptInterface
public void webapp_invoke(String class_name, String method_name, String string, String string1) {
Object[] objects = new Object[]{super.string_to_object(string), super.string_to_object(string1)};
Loge.e(string+":"+string1);
super.webapp_invoke(class_name, method_name, objects);
}
@JavascriptInterface
public void webapp_invoke(String class_name, String method_name) {
Loge.e("测试" + ":" + method_name + ":" + class_name);
super.webapp_invoke(class_name, method_name);
}
}, "JSTest");
因为JS调用android端 传过来的都是String,所以需要转换一下,android 的转换 代码如下:
因为不是做项目,只是demo测试,只简单的转换了boolean和int,string类型的,其他的大同小异,根据需要补上即可
public Object string_to_object(String string) {
if (string.equals("true")) {
return true;
}
if (string.equals("false")) {
return false;
}
try {
Integer integer= Integer.valueOf(string);
return integer; //能转换成功,没有Exception 则说明是int,否则为string
} catch (Exception exception) {
return string;
}
}
根据JS传过来的String 反射生成对象,并调用方法的WebApp类:
public class WebApp {
public void test() {
}
public void test(String string) {
}
public void app_invoke(Object object, Method method, Object... args) {
try {
method.invoke(object, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public Object app_getObject(String class_name) {
try {
Class clazz = Class.forName(class_name);
Method method = clazz.getDeclaredMethod("getInstance", null);//这里 直接根据getInstance 获取到单例 然后用来调用对象的各个方法
Loge.e(class_name);
Object object = method.invoke(null);
return object;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public Method app_getMethod(String class_name, String method_name, Object... objects) {
try {
Class clazz = Class.forName(class_name);
Class[] classes1 = new Class[objects.length];
for (int i = 0; i < objects.length; i++) {
Class c = objects[i].getClass();
classes1[i] = c;
Loge.e(c.getName());
}
Method method = clazz.getMethod(method_name, classes1);
return method;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
public Object string_to_object(String string) {
if (string.equals("true")) {
return true;
}
if (string.equals("false")) {
return false;
}
try {
Integer integer= Integer.valueOf(string);
return integer;
} catch (Exception exception) {
return string;
}
}
public void webapp_invoke(String class_name, String method_name, Object... objects) {
app_invoke(app_getObject(class_name), app_getMethod(class_name, method_name, objects), objects);
}
}
最终被调用的Test2_App类:
@HiltAndroidApp
public class Test2_App extends Application {
public static Application context;
public static Application getInstance() { //尽量每一个工具类都用 getInstance()来获取单例,这样在WebApp反射的时候,可以拿到单例,进而可以调用里面的方法.
return context;
}
public void test(String s) {
Loge.e("测试一下" + s);
}
public void test() {
Loge.e("测试一下");
}
public void test(String s, Integer jj) {
Loge.e("测试一下" + s + "-" + jj);
}
public void test(Boolean b, Integer integer) {
Loge.e(String.valueOf(b) + integer + "测试测试");
}
}
服务端随时可以更改用户的UI,随时可以增加或者删除一些功能,不可能强求用户三天俩头更新升级的,一些灵活多变的UI尽量用JS实现,只需每次用户登录的时候更新一下即可。而且 android+h5是大势所趋。在后面追加一个使用room的实例,,,使用JS的好处就是灵活多变:
function getUser(){
JSTest.webapp_invoke("com.android.test2mvvm.test5.fragment7.dao.AppDatabase","getAllUser");//获取所有用户
}
var json={"account":"4","introduction":"null","nickname":"null","pwd":"4","uid":4}; //定义一个用户 的JSON
var jsonStr = JSON.stringify(json); //直接传JSON 传不过去,需要转义一下
// JS调用安卓的方法,并且传递的参数为json格式的字符串(JSONObject.toString()),
//
// 例如: var json = {"name":"XJY","age":25",company":"CSII"};
//
//直接将json作为参数传递:window.name.jsToClient(json);
//
// Android获取的参数是不可用的,打印出来的是undefinded。
//
// JS要这样处理,再作为参数传递给原生:
//
// var jsonStr = JSON.stringify(json);
//
// window.name.jsToClient(jsonStr);
//
// 这样Android才能接受到json的字符串。
function updateUser(){//更新用户
JSTest.webapp_invoke("com.android.test2mvvm.test5.fragment7.dao.AppDatabase","updateUser",jsonStr);
}
function insertUser(){//插入用户
JSTest.webapp_invoke("com.android.test2mvvm.test5.fragment7.dao.AppDatabase","insertUser",jsonStr);
}
下面这是User的 bean:
public class User extends BaseObservable {
@PrimaryKey
private int uid;
private String account;
private String pwd;
@Ignore
private String confirmPwd;
private String nickname;
private String introduction;
private String avatar;
.........//都是一键生成的代码
}
这是AppDatabase数据库类:
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "mvvm_demo.db";
private static volatile AppDatabase mInstance;
/**
* 单例模式
*/
public static AppDatabase getInstance(Context context) {
if (mInstance == null) {
synchronized (AppDatabase.class) {
if (mInstance == null) {
mInstance = Room.databaseBuilder(context, AppDatabase.class, DATABASE_NAME).build();
}
}
}
return mInstance;
}
public static AppDatabase getInstance() {
return mInstance;
}
public abstract UserDao userDao();
public void getAllUser() { //提供给JS调用
Loge.e(userDao().getAll_01().toString());
}
public void updateUser(String jsonuser) {//提供给JS调用
Loge.e(jsonuser+"------------");
Gson gson = new Gson();
User user = gson.fromJson(jsonuser, User.class);
Loge.e(user.toString());
userDao().update(user);
}
public void insertUser(String jsonuser){//提供给JS调用
Loge.e(jsonuser+"------------");
Gson gson = new Gson();
User user = gson.fromJson(jsonuser, User.class);
Loge.e(user.toString());
userDao().insert(user);
}
}
下面是DAO,操作room数据库 UserDao类:
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
LiveData<List<User>> getAll();
@Query("SELECT * FROM user")
List<User> getAll_01();
@Query("select * from user where uid = 1")
LiveData<User> getUser01();
@Update
void update(User user);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(User user);
@Query("DELETE FROM user")
void deleteAll();
}
就这样,就完成了 在JS调用android原生的类 代码 操作。
来个效果截图:
上面实现的功能随时增删改查,用户的UI可以每天多变,一点进去再下载新界面,一更新,又是一个新界面了。
在android 原生代码 尽量把工具类的常用功能实现。随时提供给JS 调用即可。JS方根据实际情况需求调用
当然,涉及到安全问题,在下载界面的时候加密即可,
关于使用Webview 内存泄露问题,我个人建议是,所有有使用WebView 的地方,统一放到另外一个activity 另开一个进程,退出activity 的时候,直接给它来个
@Override
protected void onDestroy() {
super.onDestroy();
Loge.e("退出");
System.exit(0);//直接退出进程,一刀切了,干净省事。什么内存泄露,什么内存不够,等等玩意,都是不存在的。
}
activity另开进程代码:
<activity
android:name=".test6.Test6_Activity"
android:process=":web" />
就这么解决了webview 的痛点难点了。主进程和webview 的私有进程 互不干扰,重要数据用腾讯的 MMKV传过去即可,实时数据,AADL一个,,,进程之间一般也不会有多大的数据传递,不至于会影响用户体验。主进程尽量简便轻巧,主要是负责其他进程的入口,没事就挂着,也不会卡顿。负责push的进程 就学某多多,弄一个其他公司的logo,不让用户发觉,时不时的偷偷来广告。用户要怨恨,就怨恨其他公司去,哈哈,跟我某多多无关。