NGUI源码剖析之UIRoot

UIRoot脚本绑定的GameObject是NGUI里所有UI对象的根节点,脚本本身的作用是将根节点保持缩放到2/(Screen.height)的大小。
由于Unity中对父对象进行缩放会同时对其子对象做相应的缩放,因此NGUI用UIRoot控制对整个UI的缩放以适配不同屏幕的宽高比。
UIRoot有3种缩放模式:
public enum Scaling
{
PixelPerfect,
FixedSize,
FixedSizeOnMobiles,
}
PixelPerfect模式是当屏幕高度在minimumHeight和maximumHeight区间时不进行缩放。
FixedSize模式是只有屏幕高度为manualHeight时才不进行缩放。
FixedSizeOnMobiles模式与FixedSize模式无异,只是通过UNITY_IPHONE和UNITY_ANDROID宏区分该模式是否能生效。
参考文章:Unity3D开发(一):NGUI之UIRoot屏幕分辨率自适应
不同屏幕高度下的缩放比可以通过GetPixelSizeAdjustment函数获取:
public float GetPixelSizeAdjustment (int height)
{
height = Mathf.Max(2, height);

if (scalingStyle == Scaling.FixedSize)
return (float)manualHeight / height;

#if UNITY_IPHONE || UNITY_ANDROID
if (scalingStyle == Scaling.FixedSizeOnMobiles)
return (float)manualHeight / height;
#endif
if (height < minimumHeight) return (float)minimumHeight / height;
if (height > maximumHeight) return (float)maximumHeight / height;
return 1f;
}
缩放后的高度应为:Resource’s height / GetPixelSizeAdjustment(Screen.height)
UIRoot中有一个静态列表用于管理处于enable状态下的所有UIRoot对象,并提供了Broadcast方法。

Interacting with Other Apps

App内部通过创建Intent并显示的指明需要start的组件类名来导航到另一个Activity,而导航到另一个独立的app则不需要这种显示指明。只需要对Intent设置正确的MIME类型和相应的data即可,但在invoke Intent之前需要验证设备上是否已安装能处理这类Intent的app,否则你的app将会发生crash。

验证是否有Activity能处理某类Intent可以通过queryIntentActivities()来做:
PackageManager packageManager =getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,0);
boolean isIntentSafe = activities.size()>0;
一旦创建好Intent并调用startActivity(),如果有超过1个Activity能处理这类Intent,系统将会弹出对话框供用户选择哪个app来打开。系统弹出的该对话框底部会有一个checkbox,用户勾选后以后将默认使用该app处理这类Intent。但有时候你需要用户每次都做选择(例如提供不同的分享平台),这时创建的Intent就需要调用createChooser()来创建一个选择对话框。
当你需要从调用的Activity取回结果时(无论是自己的还是其它app的),应该调用startActivityForResult()。该API多了一个参数request code,当被调用的Activity有结果返回时会调用onActivityResult()回调,同时将request code带回供你判断如何处理返回结果。
@Override
protectedvoid onActivityResult(int requestCode,int resultCode,Intent data){
    // Check which request we're responding to
    if(requestCode == PICK_CONTACT_REQUEST){
        // Make sure the request was successful
        if(resultCode == RESULT_OK){
            // The user picked a contact.
            // The Intent's data Uri identifies which contact was selected.

            // Do something with the contact here (bigger example below)
        }
    }
}
当你的app允许接受其它app的调用时,只需在manifest.xml相应的<activiy>标签中通过<intent-filter>来声明。
<activityandroid:name="ShareActivity">
    <!-- filter for sending text; accepts SENDTO action with sms URI schemes -->
    <intent-filter>
        <actionandroid:name="android.intent.action.SENDTO"/>
        <categoryandroid:name="android.intent.category.DEFAULT"/>
        <dataandroid:scheme="sms"/>
        <dataandroid:scheme="smsto"/>
    </intent-filter>
    <!-- filter for sending text or images; accepts SEND action and text or image data -->
    <intent-filter>
        <actionandroid:name="android.intent.action.SEND"/>
        <categoryandroid:name="android.intent.category.DEFAULT"/>
        <dataandroid:mimeType="image/*"/>
        <dataandroid:mimeType="text/plain"/>
    </intent-filter>
</activity>

Saving Data

如果只是存储相对较小的key-values应该使用SharedPreferences APIs。一个SharedPreferences对象指向一个包含key-value pairs的文件并提供了相应的读写方法。而每个SharedPreferences文件则由框架管理并且能指定为独占的或共享的。
获取一个SharedPreferences句柄:
getSharedPreferences():如果想通过名字获取多个SharedPreferences文件则使用该API。
getPreferences():如果只想获取一个SharedPreferences则使用该API。
标识Context.MODE_PRIVATE、MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE分别表示SharedPreferences文件允许以独占、共享读、共享写的权限访问。

如果是大量顺序读写的数据应该使用File。所有的Android设备均有内部和外部两个存储区域,这是历史原因,即使有些设备没有可移动的存储,其固定存储也会被分为内部和外部两个存储区域。
Internal Storage:
1、总是可用。
2、你的app存储的文件默认情况下只能你自己访问。
3、当用户卸载你的app时全部文件都会被删除。
当你不想用户或其它app访问你的文件时内部存储是最好的选择。
External Storage:
1、并不总是可用,因为用户可以挂载一个USB存储来作为外部存储也能移除。
2、全局可访问。
3、当用户卸载你的app时存在getExternalFilesDir()目录下的文件也会被删除。
当文件不需要访问权限或需要共享时外部存储是最好的选择。
写外部存储需要在manifest中指明WRITE_EXTERNAL_STORAGE权限。
Saving a File On Internal Storage:
getFilesDir():返回一个代表你app内部存储目录的File。
getCacheDir():返回一个代表你app内部存储临时目录的File。注意要及时删除这里的文件。
Saving a File On External Storage:
getExternalStorageState():判断外部存储是否可用。
getExternalStoragePublicDirectory():返回一个代表你app外部存储公用目录的File,你app卸载时其中的文件不会被删除。
getExternalFilesDir():返回一个代表你app外部存储私有目录的File,你app卸载时其中的文件也会被删除。

Saving Data in SQL Databasees:SQLiteOpenHelper。

Managing the Activity Lifecycle

Image
Figure 1. A simplified illustration of the Activity lifecycle, expressed as a step pyramid. This shows how, for every callback used to take the activity a step toward the Resumed state at the top, there’s a callback method that takes the activity a step down. The activity can also return to the resumed state from the Paused and Stopped state.
用户在Home界面选择你的app图标时系统将调用你指定的”launcher”或是”main” activity的onCreate方法。该activity在AndroidManifest.xml中被指定,它必须有一个包含MAIN action和LAUNCHER category的,缺少其中任何一个你的app图标就不会再Home界面显示。

系统调用onDestroy一般是在按顺序调用onPause和onStop之后,但有一个例外:当在onCreate中调用finish方法时将会触发系统直接调用onDestroy。

* Does not crash if the user receives a phone call or switches to another app while using your app.
* Does not consume valuable system resources when the user is not actively using it.
* Does not lose the user’s progress if they leave your app and return to it at a later time.
* Does not crash or lose the user’s progress when the screen rotates between landscape and portrait orientation.

当activity处于部分可见(如activity上弹出对话框时)时系统将调用onPause方法。onPause中需要处理一些必要的数据保存工作,但不要处理很耗时的工作(如写数据库)。很耗时的工作将影响activity状态的转换(转到onStop),应该放在onStop中处理。

有些正常的app行为会导致activity被destroy,如:用户按了返回键、activity自己调用了finish方法。另一些情况,如activity处于stop状态很长一段时间了,或者当前拥有焦点的activity需要更多系统资源必须终止后台进程时,或者旋转屏幕时,也会导致activity被destroy。但对后一种情况系统通过Bundle保存了当时activity的信息。

系统会在发生后一种情况时调用onSaveInstanceState来保存activity当时的信息,因此我们可以通过重写该方法实现保存自定义数据。当系统重新创建该activity时,我们可以通过onCreate和onRestoreInstanceState来恢复。当有保存的数据时,系统会在onStart之后调用onRestoreInstaceState;而当用onCreate来恢复时需要注意判断参数是否为空,因为新建而非重建的activity也会调用onCreate。

Supporting Different Devices

多语言的支持通过将本地化的字符串保存到对应的strings.xml中来实现,需要建立以“-对应语言”为后缀的文件夹来存放,如:
English(default locale): res/values/strings.xml
French: res/values-fr/strings.xml
系统会根据用户设备当前的系统设置正确加载本地化的字符串。

Android用size和density两个属性来对设备屏幕进行归类,有4种size:small, normal, large, xlarge和4种density:low, medium, high, extra high。
屏幕多尺寸的支持是将资源存放在以“-”为后缀的文件夹中来实现的。对布局来说可能还要考虑屏幕的方向来优化布局的显示,如:
res/layout/main.xml # default
res/layout-land/main.xml # landscape
res/layout-large/main.xml # large
res/layout-large-land/main.xml # large landscape
位图则需要对原始的向量图进行缩放来生成对应尺寸的位图,缩放大小:
xhdpi: 2.0
hdpi: 1.5
mdpi: 1
ldip: 0.75
对应文件夹:
res/drawable-xhdpi/awesomeimage.png
res/drawable-hdpi/awesomeimage.png
res/drawable-mdpi/awesomeimage.png
res/drawable-ldpi/awesomeimage.png

多系统版本的支持一是通过在AndroidManifest.xml中通过minSdkVersion和targetSdkVersion来指定支持的最低和最高系统版本,二是尽量使用Android Support Library来在老系统中支持新的特性,三是编码中动态判断是否启用对用特性,如:
private void setUpActionBar() {
// Make sure we’re running on Honeycomb or higher to use ActionBar APIs
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
四是系统解析XML时会忽略不支持的属性。

Building Your First App

按官方教程创建并运行MyFirstApp时会crash,LogCat查看日志发现是一个类没找到,有点莫名其妙。
回头看项目里多了个appcompat_v7的项目,粗略看了下,应该是因为ActionBarActivity是新特性,需要引入这个项目,而这也是crash log指向的。
查了下资料,通过将项目属性中Java Build Path—>Order and Export—>Android 4.4.2勾选后解决,但奇怪的是我重建一个新项目时不再重现crash。

通过AVD Mgr创建AVD时也遇到个坑:AVD Name不能包含空格,不然OK按钮是禁用的。

mount坑爹记

在Mac又遭遇了一次mount坑爹记,想挂载一个FAT12格式的软驱,以前在Linux下使用如下命令:
sudo mount -o loop a.img /mnt/floppy
将a.img这个FAT12的软驱镜像挂载到/mnt/floppy目录下。

Mac上mount需要指定-t(文件系统)参数,man了但E文不好,看不太懂,但好歹找到了FAT12应该用-t msdos参数。可是mount -t msdos a.img /xxx/floppy却报错:Block device required。Google了大半天终于找到了解决方案:
$ hdid -nomount a.img
/dev/disk1
$ mount -t msdos /dev/disk1 /xxx/floppy

hdid的命令还不太清楚,后面了解了再补上。

另外一个方法是使用实用工具里的磁盘工具, File—> Open Disk Image—> a.img。

附上好不容易google到的两篇参考资料:
mounting dos floppy image
Create a blank floppy image in Mac OSX 10.6

Bochs on Mac

Win上使用Bochs应该是最方便的了,但Win上使用的命令行工具没Linux上的多,或者Win上已经没有使用命令行工具的氛围了。之前在Ubuntu上折腾过Bochs的编译和安装:Ubuntu编译Bochs,这次又在Mac上折腾了遍。

首先是使用./configure –with-x11 –enable-debugger –enable-disasm –disable-debugger-gui生成makefile时显示缺少gcc。机器上装有Xcode,怎么会缺少gcc呢?原来是没安装Xcode命令行工具,打开Xcode—>Preference—>Downloads中安装Command Line Tools。

完成后configure OK,但是make会找不到很多函数。原来Bochs需要X11,安装完X11后make OK,make install完成安装。

在终端中输入bochs可启动Bochs,但是会报错,原因是Bochs的隐藏配置文件.bochsrc中的配置不太兼容,自己新建个简单的配置即可。