Chapter 7 Resources in Plug-In(1)
Activity and resource are like twin brothers. And so if the activity need to be solve in Plug-In completely, you will have to face how to use the resource in it.
In this chapter, we start with loading mechanism of the resource, and further discusses the Plug-In of resources through the addAssetPath method of AssetManager. Finally, we implement a skinning technique based on this solution.
7.1 How to load resources in Android
7.1.1 Kinds of resources
The resource files in Android fall into two categories.
One is under ‘res’ folder which can be compiled. During the compile process, ‘R.java’ file will be created, it contains the hex value of each resource file, here is an example below:
It is easy to access these resources. You will get an instance of ‘Resources’ by the method of ‘getResources()’ in ‘Context’ class, then fetch any resources through ‘getXXX()’ method of this instance, just like below:
Resources resources = getResources();
String appName = resources.getString(R.string.app_name);
The other is stored under assets folder, since the file will not be compile here during the compile process, so we cannot access them through ‘R.java’. Then can we access them from absolute path? The answer is no, because the ‘apk’ file won’t be uncompress in local after downloading. That’s why we cannot fetch the absolute path of ‘assets’ folder.
Now it is the only way to use ‘open()’ method of ‘AssetManager’ class to fetch them. And you can get ‘AssetManager’ instance from ‘getAssets()’ method of ‘Resources’ class. So, the code will be like below:
Resources resources = getResources();
AssetManager am = getResources().getAssets();
InputStream is = getResources().getAssets().open("filename");
This shows that ‘Resources’ class can do the everything.
7.1.2 Resources and AssetManager
‘Resources’ class is like sales, but ‘AssetManager’ class is like developer. sales are external and developer is not.
So we can find that ‘Resource’ class provide many methods like ‘getString()’, ’getText()’, ’getDrawable()’ and so on. In fact, all of these methods call the private method of ‘AssetManager’ class indirectly, and ‘AssetManager’ do the job of query the resources.
It is injustice for ‘AssetManager’, as it does lots of work but few people know, it only has two public methods. For example, ‘open()’ is used for access the resources in ‘assets’ folder.
There has a ‘addAssetPath(String path)’ method in ‘AssetManager’ class, it will set the current APK path as variable when an APP launched, then ‘AssetManager’ and ‘Resources’ can access all the resources of this APK.
‘addAssetPath()’ is not a public method, we can use reflection, put the path of plugin APK into this method, then the resources of plugin APK were added into a resource pool. The current APP resource is already in here too.
How many Plug-In Apps are there, how many times the ‘addAssetPath()’ method need to be execute. All the Plug-In resource need to put in the resource pool.
7-1 Resource and AssetManger
There is a NDK method in AssetManager for accessing resource files. During packaging APK file, it will generate a HEX value for each resources in ‘R.java’ class. But when app is running, how do we know which file or which resource is corresponding to HEX value?
There is a file named ‘resources.arsc’ will be generate during package time, it contains a Hash table, which has the corresponding relationship of resources and HEX value.
7.2 Plug-In Solution of Resources
Let’s try to read a String resource from Plug-In App in Host App.
1) First, see the code from Plug-In App called ‘Plugin1’:
public class Dynamic implements IDynamic {
@Override
public String getStringForResId(Context context) {
return context.getResources().getString(R.string.myplugin1_hello_world);
}
}
In Plugin1, there is a ‘strings.xml’ exists under ‘res/values’ folder and a string resource was defined here:
<resources>
<string name=" myplugin1_hello_world">Hello World</string>
</resources>
2) Second, let’s see the code of ‘MainActivity’ in Host App
public class MainActivity extends AppCompatActivity {
private AssetManager mAssetManager;
private Resources mResources;
private Resources.Theme mTheme;
private String dexpath = null; //apk file path
private File fileRelease = null; //decompress folder
private DexClassLoader classLoader = null;
private String apkName = "plugin1.apk"; //apk file name
TextView tv;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
Utils.extractAssets(newBase, apkName);
} catch (Throwable e) {
e.printStackTrace();
}
}
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File extractFile = this.getFileStreamPath(apkName);
dexpath = extractFile.getPath();
fileRelease = getDir("dex", 0); //0 means ‘Context.MODE_PRIVATE’
classLoader = new DexClassLoader(dexpath,
fileRelease.getAbsolutePath(), null, getClassLoader());
Button btn_6 = (Button) findViewById(R.id.btn_6);
tv = (TextView)findViewById(R.id.tv);
// The calling of resource files
btn_6.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
loadResources();
Class mLoadClassDynamic = null;
try {
mLoadClassDynamic = classLoader.loadClass("jianqiang.com.plugin1.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
IDynamic dynamic = (IDynamic) dynamicObject;
String content = dynamic.getStringForResId(MainActivity.this);
tv.setText(content);
Toast.makeText(getApplicationContext(), content + "", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("DEMO", "msg:" + e.getMessage());
}
}
});
}
protected void loadResources() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexpath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
mResources = new Resources(mAssetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
@Override
public AssetManager getAssets() {
if(mAssetManager == null) {
return super.getAssets();
}
return mAssetManager;
}
@Override
public Resources getResources() {
if(mResources == null) {
return super.getResources();
}
return mResources;
}
@Override
public Resources.Theme getTheme() {
if(mTheme == null) {
return super.getTheme();
}
return mTheme;
}
}
The logic of code above are divided into 4 parts:
1) ‘LoadResources’ method
Create a ‘AssetManager’ instance by reflection, call ‘addAssetPath’ method and add the path of Plug-In App into the ‘AssetManager’.
That’s it! From now on, this ‘AsssetManager’ instance has only served ‘Plugin1’.
Then base on this ‘AssetManager’, we can create corresponding ‘Resources’ and ‘Theme’.
2) Override ‘getAsset’, ‘getResources’ and ‘getTheme’ methods. The logic is almost same, take getAsset as an example in below:
@Override
public AssetManager getAssets() {
if(mAssetManager == null) {
return super.getAssets();
}
return mAssetManager;
}
‘mAssetManager’ is point to Plug-In App, if this object is null, we can call the ‘getAsset’ method in its parent class – ‘ContextImpl’, and this time, the ‘AssetManager’ object, is point to Host App and the resources we fetch is also belongs to Host App.
3) Load the external Plug-In App, generate the corresponding ‘Classloader’:
File extractFile = this.getFileStreamPath(apkName);
dexpath = extractFile.getPath();
fileRelease = getDir("dex", 0); //0 means Context.MODE_PRIVATE
classLoader = new DexClassLoader(dexpath,
fileRelease.getAbsolutePath(), null, getClassLoader());
4) Thanks to the reflection, we can use the classes in Plug-In App, generate ‘dynamicObject’ object from it, then we can fetch resources in Plug-In App.
loadResource();
Class mLoadClassDynamic = classLoader.loadClass("jianqiang.com.plugin1.Dynamic");
Object dynamicObject = mLoadClassDynamic.newInstance();
IDynamic dynamic = (IDynamic) dynamicObject;
String content = dynamic.getStringForResId(MainActivity.this);
tv.setText(content);
Now, we find a perfect solution to load resources in Plug-In App.
We have notice that there is a little bit confusing in source code of Dynamic1.3, all the logic is in the ‘MainActivity’ of Host App. So, it is need to be refactored. Please refer to the project called ’Dynamic2’, the common code was in ‘BaseActivity’. For example we refactored ‘getAsset’, ‘getResource’ and ‘getTheme’ methods, and load the 3rd-party apk into resource pool.
7.3 Skin changing by Plug-in
If you’ve ever used Mobile QQ, you will find there is a feature to change skin.
If you’ve ever played Glory of the king, you will notice that you can see new heroes without updating APP.
The emoji in WeChat can be downloaded and used immediately.
In fact, all the features above is to replace the image, with the new resource files from the package just downloaded.
A simple but rough solution is to compress these images into a ZIP, and uncompressed to a folder after downloading, then you can use it.
Since we have the Plug-In technology, we find that we can put all the images into Plug-In App and read each of them by ‘R.java’.
Let’s continue to complete this function base on ‘Dynamic1.2’ .
1) Do some interest working in ‘plugin1’
We write a util class called ‘UIUtil’ and it provides ‘getText’, ‘getImage’ and ‘getLayout’ methods, which can fetch string, image and layout from ‘R.java’.
public class UIUtil {
public static String getTextString(Context ctx){
return ctx.getResources().getString(R.string.hello_message);
}
public static Drawable getImageDrawable(Context ctx){
return ctx.getResources().getDrawable(R.drawable.robert);
}
public static View getLayout(Context ctx){
return LayoutInflater.from(ctx).inflate(R.layout.main_activity, null);
}
}
Then put some resources into it:
· Put an image into ‘res/drawable’, the file name is ‘robert.png’, and the image content just like below:
· Add string value into ‘strings.xml’ which belongs to ‘res/values’, named ‘hello_message’.
<string name="hello_message">Hello</string>
· Modify the file ‘main_activity.xml’ in ‘res/layout’ and make three buttons placed horizontally.
After compile, rename the apk file to ‘plugin1.apk’ and put it into ‘assets’ folder in the Host App.
2) Make ‘Plungin2’ rapidly
Copy the entire project code of ‘Plugin1’ and rename to ‘Plugin2’. No need to modify anything.
Modify the resources of ‘Plugin2’
· Rotate the robert.png image 180 degrees.
· Change ‘hello_message’ string value to ‘你好’
· Change layout xml code, make three buttons placed vertically.
· After compile, rename the apk file to ‘plugin2.apk’ and put it into ‘assets’ folder in the Host App.
3) Work with Host App
Based on ‘Dynamic1.2’, let’s continue.
Firstly, move the common methods to ‘BaseActivity’, these includes
· Load 2 plugins
· Generate 2 ‘ClassLoader’
· Override ‘getAssets’, ’getResources’ and ‘getTheme’.
· ‘loadResources’ was increased to 2 methods because there are 2 Plug-In Apps now.
The code will be like this:
public class BaseActivity extends Activity {
private AssetManager mAssetManager;
private Resources mResources;
private Resources.Theme mTheme;
private String dexpath1 = null; //apk file path
private String dexpath2 = null; //apk file path
private File fileRelease = null; //decompression path
protected DexClassLoader classLoader1 = null;
protected DexClassLoader classLoader2 = null;
TextView tv;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
Utils.extractAssets(newBase, "plugin1.apk");
Utils.extractAssets(newBase, "plugin2.apk");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fileRelease = getDir("dex", 0);
File extractFile1 = this.getFileStreamPath("plugin1.apk");
dexpath1 = extractFile1.getPath();
classLoader1 = new DexClassLoader(dexpath1, fileRelease.getAbsolutePath(), null, getClassLoader());
File extractFile2 = this.getFileStreamPath("plugin2.apk");
dexpath2 = extractFile2.getPath();
classLoader2 = new DexClassLoader(dexpath2, fileRelease.getAbsolutePath(), null, getClassLoader());
}
protected void loadResources1() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexpath1);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
protected void loadResources2() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexpath2);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
@Override
public Resources.Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}
}
Secondly, let ResourceActivity inherit from BaseActivity, click Button1 to load the skin of plugin1 and click Button2 to load the skin of plugin2:
public class ResourceActivity extends BaseActivity {
/**
* The user control need to be change skin
* List 3 examples : TextView,ImageView,LinearLayout
*/
private TextView textV;
private ImageView imgV;
private LinearLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_resource);
textV = (TextView) findViewById(R.id.text);
imgV = (ImageView) findViewById(R.id.imageview);
layout = (LinearLayout) findViewById(R.id.layout);
findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
loadResources1();
doSomething1();
}
});
findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
loadResources2();
doSomething2();
}
});
}
private void doSomething1() {
try {
Class clazz = classLoader1.loadClass("jianqiang.com.plugin1.UIUtil");
String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);
textV.setText(str);
Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);
imgV.setBackground(drawable);
layout.removeAllViews();
View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);
layout.addView(view);
} catch (Exception e) {
Log.e("DEMO", "msg:" + e.getMessage());
}
}
private void doSomething2() {
try {
Class clazz = classLoader2.loadClass("jianqiang.com.plugin1.UIUtil");
String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);
textV.setText(str);
Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);
imgV.setBackground(drawable);
layout.removeAllViews();
View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);
layout.addView(view);
} catch (Exception e) {
Log.e("DEMO", "msg:" + e.getMessage());
}
}
}
Run Host App, click ‘Button1’ and the theme in ‘Plugin1’ will be show:
7-2 The effect of click ‘Button1’
Click ‘Button2’ and the theme in ‘Plugin2’ will be show:
7-3 The effect of click ‘Button2’
But there is too much duplicate code in HostApp, it just for your easy understand, not elegant. In addition, as the number of plugin skins increases, the redundant code will be more and more. So it is necessary to throw them into a ‘HashMap’ for maintenance.
Here is the code of ‘BaseActivity’:
public class BaseActivity extends Activity {
private AssetManager mAssetManager;
private Resources mResources;
private Resources.Theme mTheme;
protected HashMap<String, PluginInfo> plugins = new HashMap<String, PluginInfo>();
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
Utils.extractAssets(newBase, "plugin1.apk");
Utils.extractAssets(newBase, "plugin2.apk");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
genegatePluginInfo("plugin1.apk");
genegatePluginInfo("plugin2.apk");
}
protected void genegatePluginInfo(String pluginName) {
File extractFile = this.getFileStreamPath(pluginName);
File fileRelease = getDir("dex", 0);
String dexpath = extractFile.getPath();
DexClassLoader classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());
plugins.put(pluginName, new PluginInfo(dexpath, classLoader));
}
protected void loadResources(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
@Override
public Resources.Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}
}
And here is the code of ‘ResourceActivity’:
public class ResourceActivity extends BaseActivity {
/**
* The widgets that need to replace the theme
* The example of them: TextView,ImageView,LinearLayout
*/
private TextView textV;
private ImageView imgV;
private LinearLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_resource);
textV = (TextView) findViewById(R.id.text);
imgV = (ImageView) findViewById(R.id.imageview);
layout = (LinearLayout) findViewById(R.id.layout);
findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
PluginInfo pluginInfo = plugins.get("plugin1.apk");
loadResources(pluginInfo.getDexPath());
doSomething(pluginInfo.getClassLoader());
}
});
findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
PluginInfo pluginInfo = plugins.get("plugin2.apk");
loadResources(pluginInfo.getDexPath());
doSomething(pluginInfo.getClassLoader());
}
});
}
private void doSomething(ClassLoader cl) {
try {
Class clazz = cl.loadClass("jianqiang.com.plugin1.UIUtil");
String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);
textV.setText(str);
Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);
imgV.setBackground(drawable);
layout.removeAllViews();
View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);
layout.addView(view);
} catch (Exception e) {
Log.e("DEMO", "msg:" + e.getMessage());
}
}
}
To change skin by using Plug-In way is very easy. ‘Plugin1’ is a template, there is no need to change any code when we create a new skin, just replace the resources under ‘res’ folder, and do not need to modify the resources files name.
That’s it! Have you learned?
7.4 Another solution of Skin changing
In this section, we talk about ‘Dynamic3.2’, it based on ‘Dynamic 3.1’, and modify ‘doSomething’ method of ‘ResourceActivity’.
The example in last section, we use ‘R.drawable.robert’ to access resource in Plug-In.
In fact, we can access the inner class - ‘R.java’ in Plug-In in the HostApp directly. With the Hex value, the image can be fetch by the use of ‘getDrawable(resId)’. In this way, ‘getResources’ method will provide the resources in Plug-In App.
Class stringClass = cl.loadClass("jianqiang.com.plugin1.R$string");
int resId1 = (int) RefInvoke.getStaticFieldObject(stringClass, "hello_message");
textV.setText(getResources().getString(resId1));
Class drawableClass = cl.loadClass("jianqiang.com.plugin1.R$drawable");
int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass, "robert");
imgV.setBackground(getResources().getDrawable(resId2));
Class layoutClazz = cl.loadClass("jianqiang.com.plugin1.R$layout");
int resId3 = (int) RefInvoke.getStaticFieldObject(layoutClazz, "main_activity");
View view = (View) LayoutInflater.from(this).inflate(resId3, null);
layout.removeAllViews();
layout.addView(view);
From now on, there is no need for ‘UIUtil’ class in Plug-In App, it is only a APK contains resource files and ‘R’ file.
7.5 Summary
This chapter gives a detailed description of the principle of resources. Based on this, we use reflection, use ‘addAssetPath’ of ‘AssetManager’ to load resources in Plug-In App.
And skin changing is the specific implementation of resource Plug-In.
Chapter 7 Resources in Plug-In(1)的更多相关文章
- 译:Spring框架参考文档之IoC容器(未完成)
6. IoC容器 6.1 Spring IoC容器和bean介绍 这一章节介绍了Spring框架的控制反转(IoC)实现的原理.IoC也被称作依赖注入(DI).It is a process wher ...
- Android二维码开源项目zxing编译
ZXing是一个开放源代码的,用Java实现的多种格式的1D/2D条码图像处理库,它包括了联系到其它语言的port.Zxing能够实现使用手机的内置的摄像头完毕条形码的扫描及解码.该项目可实现的条形码 ...
- Spring4参考手册中文版
Spring4参考手册中文版 前言 https://github.com/b2gats/stone-docs/blob/master/spring-4-beans.md Part III. 核心技术 ...
- Android Programming: Pushing the Limits -- Chapter 3: Components, Manifests, and Resources
Android Components Manifest文件 Resource and Assets v\:* {behavior:url(#default#VML);} o\:* {behavior: ...
- TIJ——Chapter One:Introduction to Objects
///:~容我对这个系列美其名曰"读书笔记",其实shi在练习英文哈:-) Introduction to Objects Object-oriented programming( ...
- Chapter 5: Container
Chapter 5: Container A container is a module that processes the requests for a servlet and populates ...
- 如何写出优秀的研究论文 Chapter 1. How to Write an A+ Research Paper
This Chapter outlines the logical steps to writing a good research paper. To achieve supreme excelle ...
- Chapter 2 - How to Add a sprite
Chapter 2 - How to Add a sprite 1. Add image resources 1.1add resources on win32 2. Add a sprite TIP ...
- Chapter 6 — Improving ASP.NET Performance
https://msdn.microsoft.com/en-us/library/ff647787.aspx Retired Content This content is outdated and ...
随机推荐
- Spring Cloud+Dubbo对Feign进行RPC改造
因为Spring Cloud Feign是基于Http Restful的调用,在高并发下的性能不够理想(虽然他是基于Ribbon以及带有熔断机制,可以防止雪崩),成为性能瓶颈,所以我们今天对Feign ...
- mysql 中启动服务的命令 、登录命令、退出命令 mysql 的常用命令
1.cmd 以管理员执行 下面命令 启动服务 :net start mysql57 关闭 服务:net stop mysql57 查看mysql 的版本信息 : mysql -V 指定主机地址登录: ...
- 关于Android UI 优化
之前项目为了同时兼容tv和手机端的UI,使用了百分比布局来动态计算控件的宽高,这种适配方案只关心屏幕的宽高(分辨率),与屏幕的像素密度无关. 在新的项目里也使用了这种方案.但是由于项目的运行硬件计算能 ...
- zabbix的api接口
zabbix官方文档解释,api是开发者能获得修改zabbix配置,获取历史数据.主要用于: 1.创建新应用 2.集成zabbix与第三方软件 3.自动运行任务 运用JSON-RPC2.0协议,因此接 ...
- Saliency Detection via Graph-Based Manifold Ranking
Saliency Detection via Graph-Based Manifold Ranking https://www.yuque.com/lart/papers 本文不是按照之前的论文那样, ...
- 解决删除镜像时image is referenced in multiple repositories
1.查看镜像 docker images rt@:~# docker images REPOSITORY TAG IMAGE ID CREATED SIZE hours ago MB f8ab12e0 ...
- nova-api nova-compute 启动服务的时候有的没有加配置文件有的加了
nova/nova/cmd/api.pyfrom nova import config def main(): config.parse_args(sys.argv) logging.setup(CO ...
- 第三次Java作
- 当Vue中img的src是动态渲染时不显示问题
最近遇见动态渲染img时,想起了当初刚开始写vue时,曾经遇见的一个小小坑. Vue中:img的src属性是动态渲染时不显示问题1.需求:展示用户头像,数据从后台获取,如果没有拿到则显示默认图片. 如 ...
- nohup 运行后台程序
写了个程序,大概就是日志文件快写满的时候自动删日记.然后 java -jar log_delete.jar & 跑起来.过两天ps发现程序没有了, 网上一查明白是因为:通过终端(shel ...