android与unity交互,学习

Android与Unity

Android相关内容

1,Eclipse环境搭建

以下是各种错误:

安装eclipse和adt插件,adt插件必须是zip压缩包,且解压出来就可以看到jar包,否则检查不到adt。有时需要放到eclipse目录的plugins文件夹下。

https://blog.csdn.net/tomatulip/article/details/75453780 各个版本adt下载地址。

 

eclipse的adt插件必须和sdk manager版本相对应,一般更新了sdk,adt就需要相应的更新,否则无法创建工程。

如果是adt版本过高,请升级sdk。如下更新速度会很快。

在SDK Manager -> tools -> options中:

HTTP Proxy Server: mirrors.neusoft.edu.cn

HTTP Proxy Port: 80

勾选: Force https:// ...sources to be ...

或者换一个低版本的adt。

eclipse查看adt版本,Help > About Eclipse ,进入页面,点击细节。查看Android Development Toolkit。

 

下载最新版本ADT-23.0.7。然后更新sdk manager。发现sdk manager一闪而过。

Eclipse报错: Failed to get the required ADT version number from the SDK,最近通过Adroid Studio升级了SDK;结果Eclipse导致了这样的问题。解决方法之一:Adroid Studio 2.3之后不能和Eclipse共用一个SDK,给Eclispe重新配置一个SDK路径,即重新拷贝一份。拷贝之后肯定是没用的,因为android studio更新过sdk之后,会将sdk下的tools文件里的内容重新处理,所以eclipse就不能正常使用了。解决办法是下载新的tools文件夹和新的sdk manager,下载android-sdk_r24.4.1-windows就可以了。

在这个http://www.androiddevtools.cn/网站下载相应的SDK Tools压缩包就可以了。

都搞定之后,下载了一个最新的adt,但是发现并没有sdk avd的图标,到 Windows -> Perspective -> Customize Perspective去勾选就行了。

以下2篇是如何勾选的文章:

https://www.cnblogs.com/oukunqing/p/6728969.html

https://blog.csdn.net/jhjyear/article/details/43225629

以下是关于sdk manager哪些东西是需要下载的:

https://blog.csdn.net/jing__jie/article/details/74418691

错误:Android library projects cannot be launched的解决方法如下:

https://blog.csdn.net/w88193363/article/details/8528475

devices.xml文件出错的解决方法如下:

https://blog.csdn.net/xb12369/article/details/50510302?locationNum=9&fps=1

错误:Failed to load E:\SDK_For_Eclipse\SDK\build-tools\28.0.2\lib\dx.jar,这表示adt和sdk tools版本不匹配,在sdk manager里删掉相应的高版本就行了。另外,adt如果版本是23,那对应的sdk tools的版本也应该是23。

eclipse本身是有版本的,eclipse本身需要adt也达到某个最低版本,然后sdk再去对应adt版本。

另外,如果eclipse本身版本过低,那它就只能装低版本的sdk,一旦装了高版本的sdk,无论

什么版本的adt和sdk tools对应都没用。就是无法创建android工程。或者创建了工程,但是

错误This Android SDK requires Andr...ate ADT to the latest version.

这样的问题很好解决,一个升级ADT到指定版本或以上,另一个简单的办法是调低SDK版本。

如下:

https://zhidao.baidu.com/question/1818992187676541148.html

错误nno target available如下,加个/就好了:

https://zhidao.baidu.com/question/220357787.html

错误:This AVD's configuration is missing a kernel file!试了,没查证。

https://blog.csdn.net/qq503393230/article/details/39610545

 

 

2,Android学习,使用eclipse

1,android在eclipse下的目录结构

以下几个重要的:src,gen,assets,bin,libs,res,androidmanifest.xml。

1,src:自定义的java类,符合java的命名规范,分包。

2,gen:自动生成,自动维护的文件,分类管理android的res文件夹下的资源。比如R.java类里面会自动将用上的资源添加唯一标示符在里面,注意:资源名字默认小写,大写会出错,如果有出错,那R.java将不会继续自动维护。

3,assets:原生资源文件,通常是一些不会经常修改的资源,比如打开apk时的logo等。

4,bin:包含编译生成的apk应用程序,xx.apk。

5,libs:第3方库,jar包。

6,res:

darwable-xxxdpi图片资源,hdpi,ldpi,mdpi分别对应不同清晰度。

layout:布局文件,各种ui控件拖放。

menu:菜单。

android:应用菜单。

values:字符串资源文件,通常用在手机国际化。

7,Androidmanifest.xml:清单文件。

8,Android 4.1和Android Dependencies:android提供的环境。

 

2,Androidmanifest.xml:清单文件

 

1,在Activity中添加了 android:configChanges属性,目的是当所指定属性(Configuration Changes)发生改变时,通知程序调用 onConfigurationChanged()函数

2,android.intent.category.LAUNCHER决定应用决定应用程序是否显示在程序列表里。当将该App安装到手机上,在手机桌面上会出现相应的图标。详细如下:

https://blog.csdn.net/ahence/article/details/42676933

category的用途还有很多:

比如做个桌面,按home键时启动自己做的应用。

" />

https://blog.csdn.net/jason0539/article/details/10049899 关于过滤

 

过滤有2种,implicit(隐藏) intent,explicit(明确) intent。

Explicit Intent明确的指定了要启动的Acitivity ,比如以下Java代码:

Intent intent= new Intent(this, B.class);

startActivity(new Intent(MainActivity.this, NextActivity.class));转到下一个activity。

理论上来说,如果intent不指定category,那么无论intent filter的内容是什么都应该是匹配的。但是,如果是implicit intent,android默认给加上一个CATEGORY_DEFAULT,这样的话如果intent filter中没有android.intent.category.DEFAULT这个category的话,匹配测试就会失败。所以,如果你的 activity支持接收implicit intent的话就一定要在intent filter中加入android.intent.category.DEFAULT。

如下写法,xml文件要加上activity内容,注意action和category:

        <activity

            android:name=".MainActivity"

            android:label="@string/app_name" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            intent-filter>

        activity>

        <activity android:name=".NextActivity" >

             <intent-filter>

                <action android:name="XXXnextActivityTestTest" />

                <category android:name="android.intent.category.DEFAULT" />

            intent-filter>

        activity>

 

代码如下:

Intent intent = new Intent("XXXnextActivityTestTest");

startActivity(intent);

 

Intent intent = new Intent();

intent.setAction("XXXnextActivityTestTest");

startActivity(intent);

 

 

过滤的组成由:intent到底发给哪个activity,需要进行三个匹配,一个是action,一个是category,一个是data。在不直接指定要跳转的Activity的时候,为Intent提供一些相关的参数,让其自动去和AndroidManifest.xml中已有的Activity去匹配。

我们通过Intent的构造函数或者Intent提供的方法可以指定这个三个参数:

intent.setAction(action);

intent.addCategory(category);

intent.setData(data);

 

关于data:https://www.jianshu.com/p/37f3ea4fd3d6

    android:name="string"           

    android:resource="resource specification"           

    android:value="string" />

是一个键值对,用来为父控件存储多余的数据。一个控件可以包含任意数量的。如果写在在内,则可以为整个项目所使用。

 

android:name:该item的唯一名称。

android:resource:对资源的引用。其值为资源的ID,可以通过该Bundle的 Bundle.getIn()方法获取其ID值

android:value:分配给该item的值。

 

Data的值都存储在单个Bundle对象中,并作为PackageItemInfo.metaData字段提供给父控件。读取data数据:

ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(),PackageManager.GET_META_DATA);

Bundle bundle = ai.metaData;

String myApiKey = bundle.getString("my_test_metagadata");

 

Data还可以用于第3方库或APIs:

 

完整编写如下:

 

 

 

 

3,一些android基础知识

1,android结构,执行流程。

当运行我们的Android应用程序时,Android操作系统首先会去找我们的AndroidManifest.xml这个文件,这个文件是我们应用程序的主配置文件,因为我们一个应用可能有多个的Activity,那我们首先会展示哪个Activity呢?这个主配置文件就定义了当前这个应用默认所加载的那个Activity对象,找到这个Activity对象后,就会调用其onCreate()方法,这个方法主要就是用来加载我们的布局文件的,通过 setContentView()方法可以来加载我们指定的布局文件,最后根据布局文件中的各个控件显示在我们的屏幕上。这个就是我们Activity的启动流程。

 

xml:

Intent 意图 filter 过滤 category 类别 LAUNCHER 启动器

                

                

找到相匹配的意图过滤器所在的Activity元素,再根据元素的"name"属性来寻找其对应的Activity类。创建该Activity类的实例对象,执行该类的onCreate方法,初始化Activity实例对象。

public class MainActivity extends Activity {  

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        super.setContentView(R.layout.main);  }

}

super.onCreate:调用父类Activity的OnCreate方法来实现对界面的画图绘制工作。

SetContentView:加载一个界面,找到"res"目录下的"layout"子目录下main.xml文件的标识符。

 

2,实际代码

 

 

 

 

 

 

 

 

 

 

Android与Unity交互

1,unity与android交互的函数

1,Unity调用Android:

获得一个类,不是类的对象:

AndroidJavaClass jc = new AndroidJavaClass("com.example.callwifistrength.MyTestActivity");

,

使用类名,获得一个静态的属性,并且返回该属性,且该属性为当前类类型(当然也可以是int类型):

AndroidJavaObject joCurrent = jc.GetStatic("currentActivity");

jc.GetStatic("myIdNum");

使用对象,获得一个普通属性:

joCurrent.Get("myIdNum")

 

使用类名,调用一个静态的方法,没人任何返回,和返回某种类型的值:

jc.CallStatic("");

int jjjj = jc.CallStatic("");

AndroidJavaObject joCurrent = jc.CallStatic("getCurActi");

 

使用对象,调用一个普通方法,且返回值为int:

joCurrent(类的一个对象).Call("getRondomFun");

 

设置某个变量,某个静态变量的值。

joCurrent(类的一个对象).Set("myIdNum", 1001);

jc(类名).SetStatic("myIdNum",1001);

 

2,Android调用Unity:

//参数分别是:对象名字,函数名(该对象上的某个类的函数),传给函数的参数

UnityPlayer.UnitySendMessage(_objName, _funcStr, _content);

 

 

2,unity导入jar包的几种情况

 

Android里的Activity是用户能够操作的活动界面。

 

1,如果没有android插件时,androidmanifest内容为:

package="week.storm.snk" 包名

5.X以上版本 导出apk安装包的默认入口类名字为 com.unity3d.player.UnityPlayerActivity。

5.X以下的版本(亲验证4.6.3)的入口类名字则为 com.unity3d.player.UnityPlayerNativeActivity。

 

2,如果有插件,但是该插件没有继承UnityPlayerActivity也没有继承android的其他activity时的内容:

package="com.example.callwifistrength"包名

因为没有继承activity,所以依旧是这个作为入口。androidmanifest也没有加其他activity。

 

3,如果存在继承自UnityPlayerActivity时,androidmanifest内容:

package="com.example.callwifistrength"包名

activity应该是和onCreate()方法通过,使用setContentView()加载我们指定的布局文件有关系。activity和类里面有哪些业务逻辑并没有关系。所以只要和布局没有关系就同一默认写"com.unity3d.player.UnityPlayerNativeActivity" 肯定没错。

网上摘抄:继承UnityPlayerActivity这个类,继承后MainActivty就是游戏的入口文件了,也就不需要布局了,直接把setContentView这一行删除,最好把res-layout下的布局也一并删除了。

 

4,如果没有继承UnityPlayerActivity,但是继承了android的activity(这个类ActionBarActivity)会怎么样?

首先androidmanifest只写一个activity时,如下:

这说明,androidmanifest的主activity一定是和unity有关的activity。

如果只写一个activity为如下时:

android:name="com.unity3d.player.UnityPlayerNativeActivity" 可以进入apk,但是无法调用android里写好的方法,属性。

如果同时写2个:

入口必须是和unity有关的Activity,否则进不去apk。

继承自android的activity无论怎么写都不能在unity里生效,没有查到相关资料,只能暂时判定继承自android的activity在unity里无效。

 

当然,以上的操作仅仅是实验而已,而我对android并不了解,所以可能存在一些其他因素。

 

一个技巧:不继承任何activity,但是,通过unity调用该类,将unity当前的activity传入,

然后该类就拿到了当前的activity。测试可行。

 

3,unity与android交互的2种方式

unity可以拿到android里的类,对象,属性。android可以拿到到unity对象的某个函数并执行该函数。如果有些信息必须通过android的原生接口去取,那有2种方式:

1,直接通过unity的UnityPlayerActivity去获取,直接在unity里操作就可以了,不需要jar包。

2,创建android工程,导出jar包,再在unity里去取数据。

比如“获取移动网络信号,它只能在android里通过注册监听事件,当信号产生变化时才调用”,这种就只能写在android工程里,推荐的方法是创建的类不要继承任何activity,仅仅是业务逻辑就行,需要的和activity有关的东西通过"com.unity3d.player.UnityPlayer"的类给予(比如将UnityPlayerActivity当成参数传递到android)。

 

 

 

 

 

 

 

 

 

4,其他的一些相关的知识

1,只能有一个继承自UnityPlayerActivity的类。不会存在多个,可以由一个管理类同一处理。

2,res文件夹下的资源正常情况下没有任何作用。,

3,导出的jar包只需要src也就是类就行了。其他的libs,res文件夹是不需要导出的。,

4,单纯的java类,就是单纯的一些业务逻辑而已。是拿不到activity里提供的一些服务的。

但是可以通过unity调用相应的函数将activity传递进去,也可以使用unitysendmessage从unity里面拿。

5,unity只有在主线程才能调android的方法,就好像只有在主线程里才可以使用继承自mono的功能一样。

6,继承关系

MyActivity(自定义的类,继承UnityPlayerActivity) <-- UnityPlayerActivity <--UnityPlayerNativeActivity <-- NativeActivity<-- Activity

在 UnityPlayerNativeActivity 这个类中,声明了一个UnityPlayer对象并在oncreate方法里面初始化了这个对象,

把UnityPlayerNativeActivity上下文作为参数传了进去,然后在UnityPlayer类的构造方法中,

把当前的上下文赋值给了currentActivity。MyActivity是主Activity,Unity程序一启动就会调用这个Activity,

他是在AndroridManifest.xml中配置的.他需要继承UnityPlayerActivity,就是我们刚刚引入的class.jar包中的接口类.

接着我们配置本程序的AndroidManifest.xml。

 

5,懒得整理的一些知识

android打包导入到unity:

对于调用android原生接口,有2种方式,1,unity将工程导出到android。2,android打包导入到unity。

 

对于导入Library的Plugins接入方式:

一般u3d的SDK会直接提供代码+资源的形式,但Library并没有直观的给出这两者,不过我们可以自己把它分离出来:也就是在unity项目的android文件夹下放:

1,代码:libs文件夹下,分2种,一种是jar包的形式,一种是so库文件的形式。jar包即为源码,so文件一般是一些依赖库,放在不同的ABI文件夹里面(armeabi),所以so是放在文件夹里,jar是直接放。

对于so文件:u3d编译的时候支持的cpu类型主要有两种,一种是armeabi-v7a,一种是x86,所接入的SDK最好需要有这两个cpu类型的so文件。

2,资源:资源通常是放在res文件夹中(也有可能放在assets中),要注意的是当接入多个SDK的时候,value里面的string等资源的id不要重复了。

3,配置:一般只有AndroidManifest文件。

我们分离出这三个部分,就可以把这个几个部分分别放到Plugins对应的文件夹中去了。

 

对于androidManifest:

 

package:指定本应用内java主程序包的包名,它也是一个应用进程的默认名称

Application:一个AndroidManifest.xml中必须含有一个Application标签

intent-filter属性:

action很简单,只有android:name这个属性。常见的android:name值为android.intent.action.MAIN,表明此activity是作为应用程序的入口。

category也只有android:name属性。常见的android:name值为android.intent.category.LAUNCHER(决定应用程序是否显示在程序列表里)

 

修改 AndroidManifest.xml:

工程里已经有了AndroidManifest.xml,在导入jar时,需要合并2个AndroidManifest.xml。

一个 AndroidManifest.xml 只有一个主 activity (直接的观察就是这个 activity 标签中包含了下面这段标签)。

       

  

      

  

    

 

 

unity工程,需要将继承了 UnityPlayerActivity 或者 UnityNativePlayerActivity 的 Activity设置为启动 Activity ,才可以在 Android 平台(说白了就是手机等等)启动你的 unity 工程。在 AndroidManifest.xml 中将这个 Activity 注册为主 Activity 。

 

主 activity 的 android:name 需要为 用于导出 jar 的 Library 的包名 + 我们导出的 jar 的主类名:

Package name :com.example.myjar(这个包名,其实也就是 unity 工程 Player Settings 中填写的包名,因为他们需要一致)

com.example.myjar.MyActivity

 

 

 

 

实际的一些应用

1,电池电量wifi移动网络的获取

1,电量,时间相关的信息的获取

这个直接使用unity提供的的方法就行。

SystemInfo.batteryStatus 充电,放电等,可以直接在unity里查看。

SystemInfo.batteryLevel 0-1 之间, 如果目标平台上没有电池电量,则此属性返回-1。

string.Format("{0}:{1}",DateTime.Now.Hour,DateTime.Now.Minute) 按照格式获得系统时间。

 

 

 

2,wifi移动网络强度的获取

这个需要获取android原生的信息。这里先说下android和unity交互的一些方法,最后才是wifi和移动网络强度获取。

 

 

 

3,获取wifi信号强度

获取wifi信号强度,可以直接在unity里获取,这种方法不需要打jar包。

public static int GetWifiStrength()

{

AndroidJavaClass contextClass = new AndroidJavaClass("android.content.Context");

//拿到一个静态的规定好的字符串

string WIFI_SERVICE = contextClass.GetStatic("WIFI_SERVICE");

 

AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");

AndroidJavaObject joCurrent = jc.GetStatic("currentActivity");

//通过函数getSystemService获得操作wifi相关的对象

AndroidJavaObject joWifi = joCurrent.Call("getSystemService", WIFI_SERVICE);

AndroidJavaObject joWifiInfo = joWifi.Call("getConnectionInfo");

 

int level = joWifiInfo.Call("getRssi");

return level;

}

 

对应的android里的方法如下:

public int obtainWifiStrength(){

 WifiManager wifi_servicve = (WifiManager) getSystemService(WIFI_SERVICE);

 WifiInfo connectionInfo = wifi_servicve.getConnectionInfo();

 //得到的值是一个0到-100的区间值,是一个int型数据,其中0到-50表示信号最好,-50到-70表示信号偏差,小于-70表示最差,有可能连接不上或者掉线,一般Wifi已断则值为-200。

 int level = connectionInfo.getRssi();

 return level;

 }

 

在AndroidManifest里加上网络相关权限:

 

 

4,获取移动网络信号

获取移动网络信号,只能通过在android里注册点击事件来获取,只有导jar包的方法来做。

java类并没有继承任何activity,需要activity服务的时候通过unity提供传递。这里有2种方式处理m_dbm值。

1,直接在unity里定时去取,我目前使用了这种。

2,在触发监听事件时,使用UnitySendMessage调用unity的函数将m_dbm的值传递到unity。

 

import android.telephony.SignalStrength;

import android.telephony.TelephonyManager;

 

public class MyTestActivity {

//通过"com.unity3d.player.UnityPlayer",在unity里初始化currentActivity。

public static UnityPlayerActivity currentActivity;

public static void setActivity(Activity act) {

currentActivity = (UnityPlayerActivity)act;

}

 

public static int m_dbm = 0;

public static MyTestActivity myCurrentActivity = new MyTestActivity();

TelephonyManager tm;

PhoneStateListener mylistener;

public void initListenerNetDBMParam() {

         tm = (TelephonyManager)currentActivity.getSystemService(Context.TELEPHONY_SERVICE);

         mylistener = new PhoneStateListener(){

         //@Override

            public void onSignalStrengthsChanged(SignalStrength signalStrength) {

                super.onSignalStrengthsChanged(signalStrength);

 

//通过signalStrength来拿到移动网络的信号信息

 

                //第1种方式

https://blog.csdn.net/sinat_31057219/article/details/81134030

                int asu = signalStrength.getGsmSignalStrength();

                m_dbm = -113 + 2*asu;

                //api23 android6.0以上才可以使用

                //level = signalStrength.getLevel();

                

                //第2种方式

https://blog.csdn.net/cdzz11/article/details/52197732

            }

        };

    }

    public void ListenerNetDBM() {

        //开始监听

      tm.listen(mylistener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);

    }

    public void removeListenerNetDBM() {

       //取消监听

     tm.listen(mylistener, PhoneStateListener.LISTEN_NONE);

    }

}

 

在AndroidManifest里加上网络相关权限:

5,定位服务开启

/**
     * Android6.0申请权限的回调方法
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        testCB = "onRequestPermissionsResult fail 1 ";
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            // requestCode即所声明的权限获取码,在checkSelfPermission时传入
            case BAIDU_READ_PHONE_STATE:
                //如果用户取消,permissions可能为null.
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults.length > 0) {  //有权限
                    // 获取到权限,作相应处理
                    testCB = "onRequestPermissionsResult success";
                } else {
                    testCB = "onRequestPermissionsResult fail";
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        testCB = "onActivityResult fail "+requestCode;
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case PRIVATE_CODE:
                if(data == null) {
                    testCB = "onActivityResult fail "+resultCode;
                }else {
                    testCB = "onActivityResult success" + resultCode+"   "+data.getExtras().getString("result");
                }
           break;
        }
    }
    
    public void showGPSContacts(Activity activity) {
        LocationManager locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
        boolean isOpen = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        if (isOpen) {//开了定位服务
            if (Build.VERSION.SDK_INT >= 23) { //判断是否为android6.0系统版本,如果是,需要动态添加权限
                if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION)
                        != PackageManager.PERMISSION_GRANTED) {
                    // 没有权限,申请权限。
                    Toast.makeText(activity, "没有权限", Toast.LENGTH_SHORT).show();
                    ActivityCompat.requestPermissions(activity, LOCATIONGPS,
                            BAIDU_READ_PHONE_STATE);
                } else {
                     testCB = "7";
                }
            }
        } else {
            Toast.makeText(activity, "系统检测到未开启GPS定位服务,请开启", Toast.LENGTH_SHORT).show();
            //跳转到手机原生设置页面
            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            activity.setResult(100, intent);
            activity.startActivityForResult(intent,PRIVATE_CODE);
//            activity.finish(); 退出应用
        }
    }

    public static void OpenChatGps()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass jc = new AndroidJavaClass("setGps.SetGps");
        AndroidJavaObject instance = jc.GetStatic("instance");
        AndroidJavaClass jc2 = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject joCurrent = jc2.GetStatic("currentActivity");
        instance.Call("showGPSContacts", joCurrent);
        Input.location.Start(10.0f, 10.0f);
#endif
    }

1,使用Unity的Activity才可以跳转到定位开启界面
2,继承自Unity的Activity且在AndroidManifest中写入该类作为入口,才会触发定位服务的回调函数,目前回调函数没有参数数据,暂时没有找到原因
3,检查权限会报错,因为support-v4包版本不对,下载最新版本,并且导入到Unity中,因为sdk中的support-v4包就不对
4,本来这些行为除了回调函数外都可以在Unity中处理,但类中的静态类,某些jar包中的类好像不能从Unity中直接拿到

 

 

 

 

 

 

 

 

你可能感兴趣的:(当前保存的未分类的文档)