精华内容
下载资源
问答
  • Unity iOS混合开发

    2020-01-19 15:10:04
    我的第一篇博客是写的Unity和Android平台混合开发相关,接触iOS和Object-C也有一段时间了,此次,将讲述下Unity与iOS混合开发的原理,也为Unity高级移动端混合开发做下铺垫。闲话少说,直接上代码! 首先在代码之前...

    我的第一篇博客是写的Unity和Android平台混合开发相关,接触iOS和Object-C也有一段时间了,此次,将讲述下Unity与iOS混合开发的原理,也为Unity高级移动端混合开发做下铺垫。闲话少说,直接上代码!

    首先在代码之前我们先了解下Unity与iOS最基本的通讯方式https://docs.unity3d.com/Manual/PluginsForIOS.html

    Unity的这篇文档主要讲述了Unity通过C#定义[DllImport ("__Internal")]func 函数接口,iOS端从过C++声明func的实现,达到Unity直接调用iOS端原生代码

    举个播放广告的例子:

    C#端定义

            [DllImport("__Internal")]
            private static extern void showRewardVideo();

    iOS端C++实现,

    为实现统一管理,新建任意名称的.mm文件进行管理您自己写的Unity插件封装类(也可以通过Unity Postbuild的方式,强行将此函数写入你想指定的位置,写到哪无所谓,只要函数名称和签名能与C#中的定义对应)

    
    
    extern "C"{
      -(void)showRewardVideo{
         [[AdsManager shareInstance]showRewardVideo];
      }
    }

    Unity 中直接添加.mm文件

    iOS端Object-C实现

    //单例
    +(instancetype)shareInstance{
        static AdsManager *manager=nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            manager = [AdsManager new];
        });
        return manager;
    }
    
    
    //AdsManager.h函数声明
    -(void)showRewardVideo;
    
    
    //AdsManager.mm函数实现
    - (void)showRewardVideo{
      //具体的实现代码
    }

    上面讲述了Unity如何调用iOS的代码,下面将讲述iOS如果调用Unity

    先粘贴一段源代码。此段代码就是iOS如何实现与Unity进行调用的

    
    //obj:Unity Scene中的GameObject对象名称
    //method:Unity Scene中的GameObject对象身上继承MonoBehaviour的C#脚本的接收iOS函数声明
    //msg:发送的消息
    void    UnitySendMessage(const char* obj, const char* method, const char* msg);

     

    iOS端Object-C中函数中进行调用

    UnitySendMessage("AdsManager", "Callback", "测试");

    Unity接收

    C#函数实现

    void Callback(string message){
       Debug.LogError(message);
    }
            

    如上所示,Unity和iOS的交互就已经实现了,其实对与Unity程序来说C#端的处理实际上很简单,难点是对与没有任何iOS开发经验的Unity开发需要掌握一些iOS开发的基础

    展开全文
  • 主要介绍了Unity iOS混合开发界面切换思路解析的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
  • Unity iOS混合开发界面切换思路 最近有很多博友QQ 私信 或则 留言联系我,请教iOS和Unity界面之前相互切换的问题,源代码就不私下发你们了,界面跳转功能的代码我直接贴到下面好了,顺带说iOS和Unity界面切换的思路...

    Unity iOS混合开发界面切换思路

    最近有很多博友QQ 私信 或则 留言联系我,请教iOS和Unity界面之前相互切换的问题,源代码就不私下发你们了,界面跳转功能的代码我直接贴到下面好了,顺带说iOS和Unity界面切换的思路。。。

    思路

    之前一篇文章里面只谈到了Unity和iOS工程的融合,并没有谈到iOS和Unity界面的切换,这里谈谈思路,Unity导出的iOS工程里面的结构大致是这样的,有一个Window,Window上有一个UnityView,但是并没有控制器,也没有根控制器,虽然在导出的iOS工程中Classes文件夹下的UnityAppController中有rootController的属性,但是上面也标注为空~ 所以,思路就只有一种,,既然Unity导出的iOS工程有一个Window并没有控制器,那好,混合开发我们就做两个Window,一个Window用来展示Unity的几面,另外一个Window用于展示iOS APP 原生的界面~ 这就是切换Window的思想。。混合开发这样切换Window的思路应该很常见了。。下面直接贴代码,公司重要代码已经删除,核心代码都在,应该不会影响您的阅读~

    Unity界面切换到iOS界面

    Unity部分代码~

    /// <summary>
    /// 停止Unity
    /// </summary>
    [DllImport ("__Internal")]
    private static extern void StopUnity ();
    
    // 关闭unity界面
    public void CloseUnity ()
    {
        #if UNITY_ANDROID
        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
        jo.Call("StopUnity");
        #elif UNITY_IPHONE || UNITY_IOS
        StopUnity();
        #endif
    }

    Unity这段代码是当在Unity的界面点击一个按钮关闭Unity并且跳转到iOS的界面的时候执行的,上面声明一个内联函数,这个内联函数可以链接到iOS C语言的函数。。在Unity里面声明后当执行一个方法后调用内联函数,这时候会执行iOS已经写好的内联函数,iOS部分代码如下。。

    iOS部分代码~

    extern "C" {
    void StopUnity () {
        
        UnityAppController *unityDe = (UnityAppController *)[UIApplication sharedApplication].delegate;
        if (unityDe.window.windowLevel == UIWindowLevelNormal){
            unityDe.window.windowLevel = UIWindowLevelNormal - 1;
        }
        [unityDe.appWindow makeKeyAndVisible];
    }
    }

    这段代码写到iOS .mm 的文件中,用 extern "C" {} 包起来,生命这是C 的内联函数。。当Unity调用了StopUnity() 的方法,就会调用iOS extern "C" {} 中的 StopUnity() 方法。。这时候iOS在 StopUnity中执行切换界面的方法。。

    iOS界面切换到Unity界面

    iOS界面点击一个Button切换到Unity界面。。

    iOS部分代码~

    // 点击按钮切换到Unity界面~
    - (void)didClickButton {
        UnityAppController *unityDe = (UnityAppController *)[UIApplication sharedApplication].delegate;
        if(unityDe.window.windowLevel == UIWindowLevelNormal - 1) {
        unityDe.window.windowLevel = UIWindowLevelNormal;
        }
        UnitySendMessage("NativeManager", "NStartUnity", "1");
        [unityDe.window makeKeyAndVisible];
        }
        
        

    iOS界面中有一个Button,点击这个Button切换到Unity的界面,其中UnitySendMessage("NativeManager", "NStartUnity", "1");这句代码的意思是向Unity发送了一个消息,这个消息发送给Unity里面NativeManager这个对象,告诉NativeManager这个对象调用NStartUnity这个方法,并且传递参数1 。。iOSer看到这里有点可能不解了吧。。。下面再看看Unity部分的代码

    Unity场景
    7305b707jw1f8ad8dgl8nj21kw0qrq8k.jpg

    场景中有一个NativeManager,,也就是iOS发送的对象,NativeManger上面挂了一个脚本,YXUnityAPI,这个脚本里的代码如下:

    Unity部分代码:

    using UnityEngine;
    using System.Collections;
    using Vuforia;
    using CFramework;
    
    /// <summary>
    /// 此脚本只处理交互,不做功能性的方法处理~
    /// </summary>
    
    public class YXUnityAPI : MonoBehaviour
    {
        #region 新的API接口
    
        /// <summary>
        /// 打开Unity,展现第几个场景
        /// </summary>
        /// <param name="num">打开场景编号</param>
        public void NStartUnity (string scenseNum)
        {
    
            int num = int.Parse (scenseNum);
            switch (num) {
    
            case 1:
                UnityEngine.SceneManagement.SceneManager.LoadScene ("YXMJ");
                break;
    
            case 2:
                UnityEngine.SceneManagement.SceneManager.LoadScene ("GRYO");
                break;
    
            default:
                break;
            }
    
            YXUnityAPIHandler.Instance ().StartUnity (num);
    
        }
    
        #endregion
    
    }

    如果博友 是一名 iOSer或则是一名Unityer,,您可以和你们公司另外一名小伙伴一起阅读上面的代码,毕竟一部分是iOS的代码,一部分是Unity的代码,,好了,上面的思路就能实现Unity和iOS界面的切换了。。如果您还有疑问~欢迎留言。。

    转载于:https://www.cnblogs.com/Erma-king/p/5919551.html

    展开全文
  •    之前整理过一篇关于混合开发调试的文章《iOS混合开发调试秘籍》,虽然在一定程度上解决了混合开发过程中的调试问题,但是操作比较麻烦,无法直观的进行调试。为了方便混合开发的时候进行直观的调试,我这边引入...

       之前整理过一篇关于混合开发调试的文章《iOS混合开发调试秘籍》,虽然在一定程度上解决了混合开发过程中的调试问题,但是操作比较麻烦,无法直观的进行调试。为了方便混合开发的时候进行直观的调试,我这边引入了vconsole.min.js这个文件,同时呢,在我需要测试的页面引入该文件,具体代码如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>iOS and Js</title>
            <style type="text/css">
                body,
                html {
                    height: 500px;
                    line-height: 1;
                    font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial,
                    sans-serif, 'Droid Sans Fallback';
                    -webkit-text-size-adjust: 100% !important;
                    -webkit-tap-highlight-color: transparent;
                }
            #__vconsole .vc-switch{
                top:0;
            }
            </style>
            <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
    
            <script src="./vconsole.min.js"></script>
            <script>
                new VConsole();
                </script>
            <script>
                </script>
        </head>
    
        <body>
    
            <div style="margin-top: 100px">
                <h1 style="color: red;">教你如何用H5与OC进行交互,并且把H5输入的内容显示到当前的控制器上</h1><br/>
                <div><input type="button" value="sendInfoToNative" onclick="sendInfoToNative()"></div>
                <br/>
                <div><input type="button"  value="getInfoFromNative" onclick="getInfoFromNative()"></div>
                <br/>
                <div><input type="button"  value="newGInfoFromNative" onclick="newGetInfoFromNative()"></div>
                <br/>
                <div><input type="button" value="cleanAllCallBacks" onclick="cleanAllCallBacks()"></div>
                <br/>
                <div><input type="button" value="点击触发JS方法(callJsConfirm)" onclick="callJsConfirm()"></div><br/>
            </div>
            <br/>
            <div>
                <div><input type="button" value="点击触发JS输入方法(callJsInput) " onclick="callJsInput()"></div><br/>
            </div>
            <script type="text/javascript">
                function sendInfoToNative() {
                   console.log('sendInfoToNative 调用');
    
                    var params ={'Phone':'13566668888'};
    
                    JKEventHandler.callNativeFunction('sendInfoToNative',params,null,null);
    
                }
    
            function getInfoFromNative(){
              console.log('getInfoFromNative 调用');
                var params = {'Phone':'13933333333'};
                JKEventHandler.callNativeFunction('getInfoFromNative',params,'getInfoFromNativekkk',function(data){
                                                  console.log(data);                            alert(data);
                                                  });
    
    
            }
    
            function newGetInfoFromNative(){
                var params = {'name':'我是jack!!!'};
                JKEventHandler.newCallNativeFunction('newGetInfoFromNative',params,'newGetInfoFromNativeFunction',function(data){
                                                     alert(data);
                                                     },
                                                     function(data){
                                                     alert(data);
                                                     });
            }
    
            function callJsConfirm() {
                if (confirm('confirm', 'Objective-C call js to show confirm')) {
                    document.getElementById('jsParamFuncSpan').innerHTML
                    = 'true';
                } else {
                    document.getElementById('jsParamFuncSpan').innerHTML
                    = 'false';
                }
    
            }
    
            function callJsInput() {
                var response = prompt('Hello', '请输入你的名字:');
                document.getElementById('jsParamFuncSpan').innerHTML = response;
                alert (response);
    
            }
    
            function cleanAllCallBacks(){
                JKEventHandler.removeAllCallBacks();
            }
    
                </script>
        </body>
    </html>
    
    

    大家可以看到在两个js方法中,我进行了将某些内容输出到控制台的操作,具体代码入下:

      function getInfoFromNative(){
              console.log('getInfoFromNative 调用');
     function sendInfoToNative() {
                   console.log('sendInfoToNative 调用');
    

    这样在交互的时候,我就可以清楚的知道函数的执行状态。
    演示如下:
    这里写图片描述
    大家可以看到,我调用时,相关的js相关的log信息都输出出来了。可以非常方便的进行调试。
    demo下载地址如下:https://github.com/xindizhiyin2014/JKWKWebViewHandler

    更多优质文章,可以微信扫码关注:
    这里写图片描述

    展开全文
  • iOS混合开发1.前言2.项目背景3.项目框架4.从入坑到踩坑5.从踩坑到挖坑 1.前言     前端统一开发越来越多,公司为了大一统前端的所有页面显示问题,同时为节约人力资源成本,决定做前端统一开发的预研以及选型。 ...

    1.前言

        前端统一开发越来越多,公司为了大一统前端的所有页面显示问题,同时为节约人力资源成本,决定做前端统一开发的预研以及选型。

    2.项目背景

        经过几次讨论后决定前端统一开发选型方案从以下几种方式进行:

    1. flutter
    2. reactnative
    3. uniapp
    4. ionic

    各统一开发方案的优劣势与选型结果见文档:app技术选型.docx,提取码: qs6z

        根据实际情况我们选择了uni-app进行后续前端统一开发的语言。其中最重要的有以下几点:

    1. uni-app热度逐渐上升趋势非常明显
    2. uni-app使用vue技术,公司部门内部部分人员已有vue前端开发基础
    3. 中文文档,上手快

    3.项目框架

    1. 前端开发框架: HbuilderX 2.5.1
    2. 前端页面开发参考文档:5+Appuni-app开发(ps:因为前期统一使用uni-app进行开发,但是后来发现uni-app在按照文档的方式调用原生NFC读写iOS 13.0以上才支持的功能时不起作用,后来决定采用插件的形式进行iOS原生代码开发,安卓还是按照原有方式进行开发。另外页面显示上你可以直接集成某些vue的页面组件进行开发使用,例如ColorUI
    3. App开发框架:Xcode
    4. 先看文档
    5. 多看文档
    6. 仔细看文档

    4.从入坑到踩坑

    4.1打包错误

    1. HbuilderX找不到iOS模拟器或真机
      解决方案:Xcode->Preferences->Command Line Tools选上然后重启IDE,如下图在这里插入图片描述
    2. 在演示App上正常显示,但是乱七八槽不确定的东西这么多我上架确定不会被拒吗?iOS创建最简工程参考iOS创建最精简离线打包工程,不要错误的参考了iOS离线打包,,也不要参考IOS平台5+SDK技术白皮书.docx(可以简单看一下,比较全但实际开发还是要看自己需求的,这明显不符合我的uni-app需求),不然你会在这条道路上越走越远。
    3. iOS创建工程运行时各种Error:根据error信息提示缺啥静态库动态库在下载的(5+ SDK下载)的SDK中找到补啥。
    4. 运行到模拟器或者真机弹框提示打包缺少XXX模块
      解决方案:参考5+ SDK下载中Excel表格:Feature-iOS.xls
    5. 使用uni-app iOS NFC读写功能时importClass(“NFCTagReaderSession”)失败
      解决方案:在manifest.json源码视图中添加framework,参考
    6. 开发原生插件请认准自己的项目类型,我的项目框架要去看uni-app,或者更详细的5+app-uniplugin-demo,不要看html5+,也不要看5+ SDK中的插件开发说明。不然你会错的莫名其妙不知所措。
    7. 自定义基座看iOS平台离线生成自定义基座
    8. 在HbuilderX中调用iOS原生插件方法不起作用,本地打包放到在Xcode中试试看!记得先删掉模拟器或真机上的旧基座。

    4.2 运行错误

    1. 运行到模拟器或者真机发现弹窗提示各种莫名其妙的ABCD,如下图
      在这里插入图片描述
      解决方案:做一下国际语言本地化生成Localizable.strings。问题参考内容参考

    2. 启动图片未全屏?
      解决方案:info下添加启动图片,参考示例工程info.plist中的字段UILaunchImages

    3. 启动时黑屏,进入页面提示如下图
      在这里插入图片描述

    4. 加上liblibNavigator.a之后,项目报错提示如下图
      在这里插入图片描述
      解决方案:根据提示添加AssetsLibrary.framework、AVFoundation.framework、AddressBook.framework、CoreLocation.framework

    5. 最近提包到苹果市场后会有一个回馈邮件关于UIWebview使用废弃问题

    ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs .
    

        解决方案:Appstore审核反馈废弃UIWebview APIs问题的说明

    说两个个我耗时比较久的问题,也比较简单:

    1. 集成到Xcode中显示HbuilderX编译版本高于手机SDK版本,查看详情提示升级HbuilderX需要手动升级手机SDK进入uni-app运行环境版本和编译器版本不一致的问题
      解决方案:到HBuilderX官网下载最新的SDK重新集成到Xcode中
    2. uni-app中使用了uni.getLocation本地打包然后提交App Store总是得到一封ITMS-90683错误提示缺少NSLocationAlwaysUsageDescription的key,项目中没有用到持续定位但总是过不了,当我单独在本地基座中添加该隐私说明后App内授权弹框提示又不提示了。反复检查代码查看文档,也没有发现需要使用。
      解决方案:iOS云打包修改权限提示语NSLocationAlwaysUsageDescription 审核不过中按照该文档下评论修改为
    "ios" : {
    	"privacyDescription" : {
    		"NFCReaderUsageDescription" : "需要使用您的NFC功能操作标签",
    		"NSLocationWhenInUseUsageDescription" : "需要访问您的位置显示天气信息",
    		"NSLocationAlwaysUsageDescription" : "需要访问您的位置显示天气信息",
    		"NSLocationAlwaysAndWhenInUseUsageDescription" : "需要访问您的位置显示天气信息"
    	}
    }
    

    然后在本地基座的info.plist中同样按先后顺序添加隐私权限发现成功了。

    总结

    总之一句话:还是多花时间仔仔细细看一下文档再去开发!

    展开全文
  • Flutter_iOS混合开发

    2019-07-19 15:54:24
    iOS项目最终是要打包上线的,上线后的代码我们动都不敢动,可能是动不了吧,尴尬……。然而Flutter应用是可以的,带有Flutter工程...2、创建混合开发iOS工程 3、引入pod,创建Podfile文件添加内容 platform :io...
  • <div><p>我是iOS混合开发 pod引入RN 加入pushy后报错找不到RCTHotUpdate.h文件等</p><p>该提问来源于开源项目:reactnativecn/react-native-pushy</p></div>
  • iOS混合开发研究

    2017-09-15 09:41:00
    iOS-Hybrid写给移动开发者的 React Native 指南Cordova教程在已有 Xcode 项目中 加入Cordova框架
  • iOS混合开发调试秘籍

    千次阅读 2017-03-02 19:40:06
    在进行混合开发的时候(H5&&iOS),进行联合调试很是让我们开发者头疼,为了锁定问题所在,H5小伙伴没少打alert,我们这边也没少打log日志,很是麻烦,下面给大家说一下我的新发现。轻松解决混合开发中的调试问题。1...
  • 在进行web与安卓,iOS混合开发时,由于业务需求,需要web与原生相互调用方法。 当安卓,IOS调用web方法时候,如果没有用Vue框架或者其他JS框架,那么web不需要额外操作,若是你使用了Vue框架,那么,你要在vue生命...
  • React Native iOS混合开发

    2019-08-14 18:56:40
    在做RN开发的时候通常离不了JS 和Native之间的通信,比如:初始化RN时Native向JS传递数据,JS调用Native的相册选择图片,JS调用Native的模块进行一些复杂的计算,Native将一些数据(GPS信息,陀螺仪,传感器等)主动...
  • 在15年时,之前公司使用 Cordova 做混合开发使用,后来公司没有用到了,现在重新记录下。  Cordova (官网:http://cordova.apache.org/)简介:  Apache Cordova 是一个开源移动开发框架,可以使用标准的Web ...
  • iOS混合开发之一

    2017-11-16 14:06:56
    1,iOS 9以上的话很简单直接使用 NSURL *fileURL = [NSURL fileURLWithPath:path]; [myWebView loadFileURL:fileURL allowingReadAccessToURL:fileURL]; 2,iOS 8的话WKWebView直接加载的话是不行的,控制台会...
  • 在做RN开发的时候通常离不了JS 和Native之间的通信,比如:初始化RN时Native向JS传递数据,JS调用Native的相册选择图片,JS调用Native的模块进行一些复杂的计算,Native将一些数据(GPS信息,陀螺仪,传感器等)主动...
  • Flutter iOS混合开发集成实践

    千次阅读 2019-06-03 14:32:34
    然而,flutter的集成方案,官方提供了一套:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps ,并不完美,native需要依赖flutter工程和flutter环境才能运行起来,对于团队开发来讲是不能容.....
  • 在做RN开发的时候通常离不了JS 和Native之间的通信,比如:初始化RN时Native向JS传递数据,JS调用Native的相册选择图片,JS调用Native的模块进行一些复杂的计算,Native将一些数据(GPS信息,陀螺仪,传感器等)主动...
  • iOS 混合开发 —— weex

    2017-07-19 12:25:00
    我是做iOS开发的,在学习weex的时候更多的使用工具 sublime Text 来编写 .we 。 下载和配置参考 这个博客  (http://www.cnblogs.com/saytome/p/7274724.html)。     介绍: 官方脚手架全家桶: weex-...
  • 从这里看,要优化Webview好像可以通过它来实现,不过要求iOS9.0以上才能使用。   // 默认数据存储 + (WKWebsiteDataStore *)defaultDataStore; // 返回非持久化存储,数据不会写入文件系统 + ...
  • 混合开发方案分析比较 Native、Hybird、React Native、Weex 方案分析 http://www.jianshu.com/p/20a3d10a4d57 Hybird Cordova/PhoneGap:侧重于JS与原生的交互,开发简单,但性能差,如触摸时反应不灵敏。 ...
  • 1.安装一个不含Android和iOS模块的React Native项目 (1)进入workspace文件夹 (2)创建项目文件 mkdir RNBearin或者直接创建RNBearin文件夹 (3)进入RNBearin文件夹 (4)创建package.json文件 (5)在package....
  • 1、图片太大- (void)webViewDidFinishLoad:(UIWebView *)webView {NSString *js = @"function imgAutoFit() { \var imgs = document.getElementsByTagName('img'); \for (var i = 0; i < imgs.length;...
  • index.ios.js 或是 index.android.js 文件中即可运行。   二、reactNative 的见解: 1、传统的HybridApp开发方式,它们的原理其实就是在一个运行在Webview中的app,只不过这个Webview比较特殊而已。 2、...
  • 在做RN开发的时候通常离不了JS 和Native之间的通信,比如:初始化RN时Native向JS传递数据,JS调用Native的相册选择图片,JS调用Native的模块进行一些复杂的计算,Native将一些数据(GPS信息,陀螺仪,传感器等)主动...
  • 安卓开发行业发展现状据大数据统计,从事安卓开发1到3年的,工资约在1.2万左右.当然不同地域间存在差异,最高的自然是北上广深等一线城市.职业生涯规划安卓开发属于前端开发体系,本身涵盖了Java、Kotlin、Flutter等语言...
  • 加入以下代码就可以了 <script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script> ...这个时候页面就多个个 像微信小程序一样 在真机上可以点开控制台 这个工具台强大了 混合开发 必备
  • GICXMLLayout(以下简称GIC)是iOS上的一个全新的混合开发库,项目地址:github.com/ghwghw4/GIC… 在线文档地址:gicxmllayout.gonghaiwei.cn GICXMLLayout能干什么?有什么特点? 使用XML来描述UI、动画、事件绑定...
  • 纵观所有iOS与H5交互的方案,有以下几种: 第一种:有很多的app直接使用在webview的代理中通过拦截的方式与native进行交互,通常是通过拦截url scheme判断是否是我们需要拦截处理的url及其所对应的要处理的功能是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,051
精华内容 820
关键字:

ios混合开发