1.创建资源包:
public class Sript_01 : MonoBehaviour
{
[MenuItem ("自定义资源包/创建资源包")]
static void ExecCreateAssetBunldes()
{
//设置保存资源包的根路径
string targetDir = Application.dataPath + "/AssetBundles";
Object[] SelectedAsset = Selection.GetFiltered(typeof (Object),SelectionMode.DeepAssets);
//创建一个目录,用于保存资源包
if(!Eirectory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
}
for(int i=0;i<selectedAsset.Length;i++)
{
//生成资源包的完整路径
string filepath = targetDir + "/" + SelectedAsset[i].name + ".unity3d";
//根据路径判断资源包文件是否存在
if(File.Exists(filePath))
{
File.Delete(filePath);
}
//生成新的资源包文件
if(BuildPipeline.BuildAssetBundle(SelectedAsset[i],null,filePath,BuildAssetBundleOptions.CollectDependencies))
{
Debug.Log(“资源包生成成功”);
AssetDatabase.Refresh();
}
else
Debug.Log("资源包文件生成失败");
}
}
}
}
Note: This is an editor class. To use it you have to place your script in Assets/Editor inside your project folder. Editor classes are in the UnityEditor namespace so for C# scripts you need to add "using UnityEditor;" at the beginning of the script.
2.下载资源包
IEnumerator loadBundleMat(string name)
{
Material mat;
//资源在本机的路径
WWW date = new WWW("file://" + Application.dataPath + "/AssetBundles/" + name + "./unity3d");
//等待下载完成
yield return date;
//将下载得到的资源强制转换成材质资源
mat = (Material)date.assetBundle.mainAsset;
//更换为下载的资源
renderer.material = mat;
//释放资源对象
date.assetBundle.Unload(false);
}
IEnumerator loadBundleObject (string name)
{
WWW date = new WWW("file://" + Application.dataPath + "AssetBundles/" + name + ".unity3d");
//等待下载完成,并且克隆游戏对象,
yield return Instantiate(date.assetbundle.mainAsset);
//释放资源对象
date.assetbundle.Unload(false);
}
注释: 资源包下载完成后,资源文件将保持在assetBundle.mainAsset 对象中,接着使用该对象即可。由于连续下载资源包对象会出错,所以下载完资源后需要手动释放对象。
创建屏幕控件----App Widget
Android SDK给开发人员提供了可以在移动应用程序的传统边界外面提供功能,这就是App Widget。开发人员可使用App Widget API创建可加入到主屏幕中的小型控件。这些控件简单但功能强大,可向用户提供有关应用程序的补充信息,并提醒用户在必要时启动应用程序。
对有些类型的应用程序(如需要将状态或更新告知用户的应用程序)来说,App Widget很有用。天气应用程序可能包含这样的App Widget,即显示指定地区的当前天气状况;任务管理应用程序可能包含这样的App Widget,即告诉用户其待办事项中的下一个事项是什么以及当前还有多少个事项要办;图库应用程序可能包含这样的App Widget,即以幻灯片的方式列出图库中所有的图片。
要实现简单的App Widget,必须利用前面学习的很多技能。创建App Widget的步骤如下:
1、 创建App Widget配置文件;
2、创建App Widget布局资源文件;
3、实现App Widget提供器;
4、 实现Android服务,在必要时更新App Widget;
5、 在Android清单文件中注册App Widget和相关的服务。
下面我们来更详细地学习每个步骤:
1、配置App Widget的属性
必须在一个独立的XML文件中指定App Widget的定义和配置属性,然后在Android清单文件中引用他们。下面是一些用于定义App Widget的常见属性:
大小:App Widget的宽度和高度,以独立于密度的像素(dp或dip)为单位,它对应于正确显示App Widget需要的主屏幕网格单元数。Android主屏幕被划分成网格单元格,每个单元格为74×74像素。在每个单元格中,只能有一项内容,如App Widget或应用程序快捷方式,这可确保各项内容不会重叠。
更新频率:系统两次调用App Widget提供器来更新App Widget的内容之间相隔的时间,单位为毫秒。
初始布局:最初添加App Widget时使用的布局文件,以后可使用代码对初始布局进行修改。
配置活动:首次显示App Widget前将启动的活动,该活动将配置App Widget的各个方面。
首先在资源文件夹/res/xml中添加一个名为example_appwidget_info.xml的XML文件:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="60dp" android:minHeight="30dp" android:updatePeriodMillis="10000" android:initialLayout="@layout/example_appwidget" > </appwidget-provider>
注意,Google给了这样一个说明:
public int updatePeriodMillis
Since: API Level 3
How often, in milliseconds, that this AppWidget wants to be updated. The AppWidget manager may place a limit on how often a AppWidget is updated.
This field corresponds to the android:updatePeriodMillis attribute in the AppWidget meta-data file.
Note: Updates requested with updatePeriodMillis will not be delivered more than once every 30 minutes.
这说明android:updatePeriodMillis="1080000"App Widget最短更新间隔为30分钟,所以updatePeriodMillis如果设置小于30分钟,App Widget将默认30分钟执行一次。
这个文件定义了一个App Widget,它每隔3小时更新一次,大小为4×2个单元格。如果您进行计算就会发现我们定义的宽和高不是前面定义的网格单元格大小74dp的整数倍,原因是虽然通常认为网个单元格大小为74dp,但计算App Widget的大小时,必须将最终结果减去2,例如74×2 = 148,减去2后为146。如果不这样做,App Widget占据的单元格数可能不符合预期。
另外,这个App Widget最初使用一个预定义布局,这是使用android:initialLayout = “@layout/widget”指定的。下面我们创建这个布局,它表示App Widget的用户界面。
2、创建App Widget布局资源文件
App Widget有独特的布局要求。首先App Widget是通过接口RemoteViews绘制的,这限制了可显示的用户界面控件的类型;其次App Widget必须与其定义中配置的大小一致。当视图由另一个进程显示时,将使用RemoteViews对象。App Widget正是这样的,他是在App Widget宿主进程而不是在应用程序主进程中显示的。RemoteViews对象可以使用的布局和视图对象收到了限制,下面是可以再App Widget中使用的布局和视图对象:
LinearLayout;
FrameLayout;
RelativeLayout;
TextView;
ImageView;
Button;
ImageButton;
ProgressBar;
AnalogClock;
Chronometet。
不能使用这些控件的子类,这意味着布局的设计受到限制。一种改善App Widget功能的典型的方式是,在需要更强大的功能或更复杂的屏幕时启动一个应用程序活动。
下面我们开始设计App Widget的布局,创建一个布局文件/res/layout/example_appwidget.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/widgetTextId" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="firstWidgetText" android:background="#000000"/> </LinearLayout>
3、实现App Widget提供器
使用AppWidgetProvider类必须通过在清单文件中使用<receiver>元素来声明它,实现为一个广播接收器。
AppWidgetProvider类扩展BroadcastReceiver为一个简便类来处理App Widget广播。AppWidgetProvider只接收和这个AppWidget相关的事件广播,比如这个App Widget被更新,删除,启用,以及禁用。当这些广播事件发生时,AppWidgetProvider将接收到下面的方法调用;
onUpdate(Context , AppWidgetManager , int[])
这个方法用来间隔性的更新App Widget,间隔时间用AppWidgetProviderInfo里的updatePeriodMillis属性定义。这个方法也会在用户添加App Widget时被调用,因此它应该执行基础的设置,比如视图定义事件处理器并启动一个临时的服务Service。但是如果你已经声明了一个配置活动,这个方法在用户添加App Widget时将不会被调用,而只在后续更新是被调用。配置活动应该在配置完成时负责执行一次更新;
onDeleted(Context , int[])
当App Widget从宿主中删除是被调用;
onEnabled(Context)
当一个App Widget实例第一次创建时被调用。比如,如果用户添加两个你的App Widget实例,只在第一次被调用。如果你需要打开一个新的数据库或者执行其他对于所有的App Widget实例只需要发生一次的设置,那么这里是完成这个工作的好地方。
onDisabled(Context)
当你的App Widget的最后一个实例被从宿主中国删除时被调用。你应该在onEnabled(Context)中做一些清理工作,比如删除一个临时的数据库。
onReceive(Context , Intent)
当接收到每个广播时都会被调用,而且在上面的回调函数之前。你通常不需要实现这个方法,因为缺省的AppWidgetProvider实现过滤所有App Widget广播并恰当的调用上述方法。
最重要的AppWidgetProvider回调函数是onUpdated(),因为它是在每个App Widget添加进宿主时被调用的(除非你使用一个配置活动)。如果你的App Widget要接受任何用户交互事件,那么你需要在这个回调函数中注册时间处理器。如果你的App Widget不创建临时文件或数据库,或者不执行其他需要清理的工作,那么onUpdated()可能是你需要定义的唯一的回调函数。比如你想要一个带按钮的App Widget,当点击时启动一个活动,你可以使用下面的AppWidgetProvider实现:
package com.example.test04; import java.util.Calendar; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.util.Log; import android.widget.RemoteViews; public class ExampleAppWidgetProvider extends AppWidgetProvider { @Override public void onEnabled(Context context) { super.onEnabled(context); Log.i("输出", "onEnabled...正在执行"); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.i("输出", "onUpdate...正在执行"); super.onUpdate(context, appWidgetManager, appWidgetIds); Log.i("输出", "onUpdate...正在执行"); } @Override public void onDisabled(Context context) { super.onDisabled(context); Log.i("输出", "onDisabled...正在执行"); } @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); Log.i("输出", "onDeleted...正在执行"); } @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); Log.i("输出", "onReceive...正在执行"); }
这个AppWidgetProvider仅定义了onUpdate()方法,为了定义一个PendingIntent,来启动一个活动并使用setOnClickPendingIntent(int , PendingIntent)方法把他附着到这个AppWidget的按钮上。注意它包含了一个遍历appWidgetIds中所有项的循环,这是一个Ids数组,每个ID用来标识由这个Provider创建的一个App Widget。这样,如果创建多余一个这个App Widget的实例,那么它们将被同步更新。不过,对于所有的App
Widget实例,只有一个updatePeriodMillis时间表被管理。比如,如果这个更新时间表被定义为每隔两个小时,而且App Widget的第二个是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略。
注意:因为这个AppWidgetProvider是一个广播接收器BroadcastReceiver,不能保证你的进程在回调函数返回后仍然继续。
4、处理App Widget后台任务
对于持续时间很长的操作,常规的是以异步方式执行它们。然后,对于App Widget,这种不可行,因为没有管理这种任务的底层活动。App Widget所属的进程随时可能终止,即使正在执行异步任务。因此,必须创建一个Android Service对象,并在服务中执行所需的后台操作。
@Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.i("输出", "onUpdate...正在执行"); updateAppWidget(context, appWidgetManager , appWidgetIds); super.onUpdate(context, appWidgetManager, appWidgetIds); Log.i("输出", "onUpdate...正在执行"); } public static void updateAppWidget(Context context , AppWidgetManager appWidgetManager , int[] appWidgetIds) { RemoteViews remoteViews = new RemoteViews(context.getPackageName() , R.layout.example_appwidget); remoteViews.setTextViewText(R.id.widgetTextId, Calendar.getInstance().getTime() + ""); Log.i("输出", Calendar.getInstance().getTime() + ""); appWidgetManager.updateAppWidget(appWidgetIds, remoteViews); }
5、在Android清单文件中注册App Widget和相关的服务
<receiver android:name="com.example.test04.ExampleAppWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/example_appwidget_info"> </meta-data> </receiver>
描述符包括,存储段描述符(代码段,数据段,堆栈段),系统描述符(任务状态段TSS,局部描述符表LDT),门描述符(调用门,任务门,中断门,陷阱门),注意门描述符和系统描述符都是DT=0时候,对应的状态。存储段描述符和系统描述符如图1,门描述符如图2。
图1 存储段描述符和系统描述符
图2 门描述符
(1) P:存在(Present)位。
P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;
P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。
(2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。
(3) DT:说明描述符的类型。
对于存储段描述符而言,DT=1,以区别与系统段描述符和门描述符(DT=0)。
(4) TYPE: 说明存储段描述符所描述的存储段的具体属性。
数据段类型
类型值说明
----------------------------------
0 只读
1 只读、已访问
2 读/写
3 读/写、已访问
4 只读、向下扩展
5 只读、向下扩展、已访问
6 读/写、向下扩展
7 读/写、向下扩展、已访问
类型值
说明
代码段类型
----------------------------------
8 只执行
9 只执行、已访问
A 执行/读
B 执行/读、已访问
C 只执行、一致码段
D 只执行、一致码段、已访问
E 执行/读、一致码段
F 执行/读、一致码段、已访问
系统段类型
类型编码说明
----------------------------------
0 <未定义>
1 可用286TSS
2 LDT
3 忙的286TSS
4 286调用门
5 任务门
6 286中断门
7 286陷阱门
8 未定义
9 可用386TSS
A <未定义>
B 忙的386TSS
C 386调用门
D <未定义>
E 386中断门
F 386陷阱门
(5) G:段界限粒度(Granularity)位。
G=0 表示界限粒度为字节;
G=1 表示界限粒度为4K 字节。
注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。
(6) D:D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。
在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。
① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;
② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。
在向下扩展数据段的描述符中,D位决定段的上部边界。
① D=1表示段的上部界限为4G;
② D=0表示段的上部界限为64K,这是为了与80286兼容。
在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。
① D=1表示使用32位堆栈指针寄存器ESP;
② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。
(7) AVL:软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。
(8) Dword Count:从调用者堆栈中将参数复制到被调用者堆栈(新堆栈)中,复制参数的个数由调用门中Dword Count一项来决定。如果Dword
Count为0,那么不会复制参数。
选择子图示:
┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
┃ 描述符索引 ┃ TI ┃ RPL ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛
RPL(Requested Privilege Level): 请求特权级,用于特权检查。
TI(Table Indicator): 引用描述符表指示位
TI=0 指示从全局描述符表GDT中读取描述符;
TI=1 指示从局部描述符表LDT中读取描述符。
LDT中的描述符和GDT中的描述符除了选择子的bit3一个为0一个为1用于区分该描述符是在GDT中还是在LDT中外,描述符本身的结构完全一样。开始我考虑既然是这样,为什么要将LDT放在GDT中而不是像GDT那样找一个GDTR寄存器呢?
后来终于明白了原因--很简单,GDT表只有一个,是固定的;而LDT表每个任务就可以有一个,因此有多个,并且由于任务的个数在不断变化其数量也在不断变化。如果只有一个LDTR寄存器显然不能满足多个LDT的要求。因此INTEL的做法是把它放在放在GDT中。
pm.inc代码如下:
;---------------------------------------- ; 描述符类型值说明 ; 其中: ; DA_ : Descriptor Attribute ; D : 数据段 ; C : 代码段 ; S : 系统段 ; R : 只读 ; RW : 读写 ; A : 已访问 ; 其它 : 可按照字面意思理解 G D 0 AVL 0 0 0 0 P DPL(2位) DT TYPE(4位) ;---------------------------------------- DA_32 EQU 4000h ; 32 位段 0100 0000 0000 0000 DA_DPL0 EQU 00h ; DPL = 0 0000 0000 DA_DPL1 EQU 20h ; DPL = 1 0010 0000 DA_DPL2 EQU 40h ; DPL = 2 0100 0000 DA_DPL3 EQU 60h ; DPL = 3 0110 0000 ;---------------------------------------- ; 存储段描述符类型值说明 ;---------------------------------------- DA_DR EQU 90h ; 存在的只读数据段类型值 1001 0000 DA_DRW EQU 92h ; 存在的可读写数据段属性值 1001 0010 DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 1001 0011 DA_C EQU 98h ; 存在的只执行代码段属性值 1001 1000 DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 1001 1010 DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 1001 1100 DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值 1001 1110 ;---------------------------------------- ; 系统段描述符类型值说明 ;---------------------------------------- DA_LDT EQU 82h ; 局部描述符表段类型值 1000 0010 DA_TaskGate EQU 85h ; 任务门类型值 1000 0101 DA_386TSS EQU 89h ; 可用 386 任务状态段类型值 1000 1001 DA_386CGate EQU 8Ch ; 386 调用门类型值 1000 1100 DA_386IGate EQU 8Eh ; 386 中断门类型值 1000 1110 DA_386TGate EQU 8Fh ; 386 陷阱门类型值 1000 1111 ;---------------------------------------- ;---------------------------------------- ; 选择子类型值说明 ; 其中: ; SA_ : Selector Attribute SA_RPL0 EQU 0 ; ┓00 SA_RPL1 EQU 1 ; ┣ RPL01 SA_RPL2 EQU 2 ; ┃10 SA_RPL3 EQU 3 ; ┛11 SA_TIG EQU 0 ; ┓TI 0000 SA_TIL EQU 4 ; ┛ 0100 ;---------------------------------------- ; 宏 ------------------------------------------------------------------ ; ; 描述符 ; usage: Descriptor Base, Limit, Attr ; Base: dd ; Limit: dd (low 20 bits available)低二十位可用 ; Attr: dw (lower 4 bits of higher byte are always 0)高字节的低四位始终为0 %macro Descriptor 3 ;段界限为低地址 1代表Base 2代表Limit 3代表属性 dw %2 & 0FFFFh ; 段界限 1 (2 字节) dw %1 & 0FFFFh ; 段首地址 1 (2 字节) db (%1 >> 16) & 0FFh ; 段首地址 2 (1 字节) dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节) db (%1 >> 24) & 0FFh ; 段首地址 3 (1 字节) %endmacro ; 共 8 字节 ; ; 门 ; usage: Gate Selector, Offset, DCount, Attr ; Selector: dw ; Offset: dd ; DCount: db ; Attr: db %macro Gate 4 ;1代表Selector 2代表Offset 3代表DCount 4代表Attr dw (%2 & 0FFFFh) ; 偏移 1 (2 字节) dw %1 ; 选择子 (2 字节) dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节) dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节) %endmacro ; 共 8 字节