使用 jCIFS 流文件进行 Android ServerSocket 编程

2024-01-05

我遇到了一些问题,我已经问过很多次了,但我想我现在更近了一步,所以希望有人可以帮助我解决剩下的问题。

我之前的问题:

  • 从 Android 连接到 NAS 设备 https://stackoverflow.com/questions/8583277/connect-to-nas-device-from-android
  • 如何使用 jCIFS 使用默认查看器在 Android 中打开文件 https://stackoverflow.com/questions/8030769/how-to-open-files-in-android-with-default-viewer-using-jcifs

简而言之 - 我想创建一个应用程序:

  1. 可以使用 jCIFS 连接到 NAS 设备
  2. 能够在默认查看器中启动文件 - 即视频播放器中的视频

第一部分相对简单,我已经做到了,但第二部分是困扰我的问题,也是我之前问过几次的问题。我想我还是取得了一些进步。

我想我需要使用ServerSocket在我的应用程序中以某种方式在 NAS 和正在播放内容的应用程序之间创建桥梁。我想这可以使用Service。 NAS 设备中的文件可以作为FileInputStream.

市场上有很多应用程序(即ES文件浏览器 https://market.android.com/details?id=com.estrongs.android.pop)能够在没有 root 访问权限的情况下执行此操作,所以我知道这是可能的 - 目前我只是不知道如何。

我在使用上述一些应用程序时一直在查看 Logcat,它们似乎都在创建本地服务器,然后启动视频Intent从该服务器。如何才能实现这一目标?


基本答案是使用 SmbFileInputStream 来获取输入流你可能会用这个。

现在棘手的部分是如何向其他应用程序提供InputStream。

一种可能的方法是,有多少应用程序向设备上的其他应用程序提供任何 InputStream 的流式传输,即使用http:URL 方案,并通过 http 调整您的流。 然后,可以处理 http URL 的应用程序就可以打开并使用您的数据。

为此,你必须制作某种 http 服务器,这听起来很困难,但实际上是可以实现的任务。好的开始来源是纳米httpd https://github.com/NanoHttpd/nanohttpd库只是一个 java 源代码,最初用于列出目录中的文件,但您可以调整它以通过 http 流式传输您的 InputStream。这就是我成功做到的。

您的 url 类似于 http:// localhost:12345,其中 12345 是您的服务器侦听请求的端口。该端口可以从 ServerSocket.getLocalPort() 获得。然后将此 URL 提供给某个应用程序,您的服务器将等待连接并发送数据。

关于 http 流的注意事项:某些应用程序(例如视频播放器)喜欢可查找的 http 流(http Range 标头)。由于您还可以获得 SmbRandomAccessFile,因此您可以使小型服务器提供文件中数据的任何部分。 Android 的内置视频播放器需要这样的可查找 http 流才能在视频文件中进行查找,否则会出现“视频无法播放”错误。您的服务器必须准备好处理断开连接和具有不同范围值的多个连接。

http服务器的基本任务:

  1. 创建服务器套接字
  2. 创建等待连接的线程(Socket Accept = serverSocket.accept()),一个线程可能没问题,因为您一次处理单个客户端
  3. 读取http请求(socket.getInputStream()),主要检查GET方法和Range头)
  4. 发送标头,主要是Content-Type、Content-Length、Accept-Ranges、Content-Range标头
  5. 发送实际的二进制数据,这是InputStream(文件)到OutputStream(套接字)的简单复制
  6. 处理断开连接、错误、异常

祝实施顺利。

EDIT:

这是我的班级,就是做这件事的。它引用了一些不存在的文件类,您可以轻松地将其替换为文件类。

/**
 * This is simple HTTP local server for streaming InputStream to apps which are capable to read data from url.
 * Random access input stream is optionally supported, depending if file can be opened in this mode. 
 */
public class StreamOverHttp{
   private static final boolean debug = false;

   private final Browser.FileEntry file;
   private final String fileMimeType;

   private final ServerSocket serverSocket;
   private Thread mainThread;

   /**
    * Some HTTP response status codes
    */
   private static final String 
      HTTP_BADREQUEST = "400 Bad Request",
      HTTP_416 = "416 Range not satisfiable",
      HTTP_INTERNALERROR = "500 Internal Server Error";

   public StreamOverHttp(Browser.FileEntry f, String forceMimeType) throws IOException{
      file = f;
      fileMimeType = forceMimeType!=null ? forceMimeType : file.mimeType;
      serverSocket = new ServerSocket(0);
      mainThread = new Thread(new Runnable(){
         @Override
         public void run(){
            try{
               while(true) {
                  Socket accept = serverSocket.accept();
                  new HttpSession(accept);
               }
            }catch(IOException e){
               e.printStackTrace();
            }
         }

      });
      mainThread.setName("Stream over HTTP");
      mainThread.setDaemon(true);
      mainThread.start();
   }

   private class HttpSession implements Runnable{
      private boolean canSeek;
      private InputStream is;
      private final Socket socket;

      HttpSession(Socket s){
         socket = s;
         BrowserUtils.LOGRUN("Stream over localhost: serving request on "+s.getInetAddress());
         Thread t = new Thread(this, "Http response");
         t.setDaemon(true);
         t.start();
      }

      @Override
      public void run(){
         try{
            openInputStream();
            handleResponse(socket);
         }catch(IOException e){
            e.printStackTrace();
         }finally {
            if(is!=null) {
               try{
                  is.close();
               }catch(IOException e){
                  e.printStackTrace();
               }
            }
         }
      }

      private void openInputStream() throws IOException{
         // openRandomAccessInputStream must return RandomAccessInputStream if file is ssekable, null otherwise
         is = openRandomAccessInputStream(file);
         if(is!=null)
            canSeek = true;
         else
            is = openInputStream(file, 0);
      }

      private void handleResponse(Socket socket){
         try{
            InputStream inS = socket.getInputStream();
            if(inS == null)
               return;
            byte[] buf = new byte[8192];
            int rlen = inS.read(buf, 0, buf.length);
            if(rlen <= 0)
               return;

            // Create a BufferedReader for parsing the header.
            ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
            BufferedReader hin = new BufferedReader(new InputStreamReader(hbis));
            Properties pre = new Properties();

            // Decode the header into params and header java properties
            if(!decodeHeader(socket, hin, pre))
               return;
            String range = pre.getProperty("range");

            Properties headers = new Properties();
            if(file.fileSize!=-1)
               headers.put("Content-Length", String.valueOf(file.fileSize));
            headers.put("Accept-Ranges", canSeek ? "bytes" : "none");

            int sendCount;

            String status;
            if(range==null || !canSeek) {
               status = "200 OK";
               sendCount = (int)file.fileSize;
            }else {
               if(!range.startsWith("bytes=")){
                  sendError(socket, HTTP_416, null);
                  return;
               }
               if(debug)
                  BrowserUtils.LOGRUN(range);
               range = range.substring(6);
               long startFrom = 0, endAt = -1;
               int minus = range.indexOf('-');
               if(minus > 0){
                  try{
                     String startR = range.substring(0, minus);
                     startFrom = Long.parseLong(startR);
                     String endR = range.substring(minus + 1);
                     endAt = Long.parseLong(endR);
                  }catch(NumberFormatException nfe){
                  }
               }

               if(startFrom >= file.fileSize){
                  sendError(socket, HTTP_416, null);
                  inS.close();
                  return;
               }
               if(endAt < 0)
                  endAt = file.fileSize - 1;
               sendCount = (int)(endAt - startFrom + 1);
               if(sendCount < 0)
                  sendCount = 0;
               status = "206 Partial Content";
               ((RandomAccessInputStream)is).seek(startFrom);

               headers.put("Content-Length", "" + sendCount);
               String rangeSpec = "bytes " + startFrom + "-" + endAt + "/" + file.fileSize;
               headers.put("Content-Range", rangeSpec);
            }
            sendResponse(socket, status, fileMimeType, headers, is, sendCount, buf, null);
            inS.close();
            if(debug)
               BrowserUtils.LOGRUN("Http stream finished");
         }catch(IOException ioe){
            if(debug)
               ioe.printStackTrace();
            try{
               sendError(socket, HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
            }catch(Throwable t){
            }
         }catch(InterruptedException ie){
            // thrown by sendError, ignore and exit the thread
            if(debug)
               ie.printStackTrace();
         }
      }

      private boolean decodeHeader(Socket socket, BufferedReader in, Properties pre) throws InterruptedException{
         try{
            // Read the request line
            String inLine = in.readLine();
            if(inLine == null)
               return false;
            StringTokenizer st = new StringTokenizer(inLine);
            if(!st.hasMoreTokens())
               sendError(socket, HTTP_BADREQUEST, "Syntax error");

            String method = st.nextToken();
            if(!method.equals("GET"))
               return false;

            if(!st.hasMoreTokens())
               sendError(socket, HTTP_BADREQUEST, "Missing URI");

            while(true) {
               String line = in.readLine();
               if(line==null)
                  break;
   //            if(debug && line.length()>0) BrowserUtils.LOGRUN(line);
               int p = line.indexOf(':');
               if(p<0)
                  continue;
               final String atr = line.substring(0, p).trim().toLowerCase();
               final String val = line.substring(p + 1).trim();
               pre.put(atr, val);
            }
         }catch(IOException ioe){
            sendError(socket, HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
         }
         return true;
      }
   }


   /**
    * @param fileName is display name appended to Uri, not really used (may be null), but client may display it as file name.
    * @return Uri where this stream listens and servers.
    */
   public Uri getUri(String fileName){
      int port = serverSocket.getLocalPort();
      String url = "http://localhost:"+port;
      if(fileName!=null)
         url += '/'+URLEncoder.encode(fileName);
      return Uri.parse(url);
   }

   public void close(){
      BrowserUtils.LOGRUN("Closing stream over http");
      try{
         serverSocket.close();
         mainThread.join();
      }catch(Exception e){
         e.printStackTrace();
      }
   }

   /**
    * Returns an error message as a HTTP response and
    * throws InterruptedException to stop further request processing.
    */
   private static void sendError(Socket socket, String status, String msg) throws InterruptedException{
      sendResponse(socket, status, "text/plain", null, null, 0, null, msg);
      throw new InterruptedException();
   }

  private static void copyStream(InputStream in, OutputStream out, byte[] tmpBuf, long maxSize) throws IOException{

     while(maxSize>0){
        int count = (int)Math.min(maxSize, tmpBuf.length);
        count = in.read(tmpBuf, 0, count);
        if(count<0)
           break;
        out.write(tmpBuf, 0, count);
        maxSize -= count;
     }
  }
   /**
    * Sends given response to the socket, and closes the socket.
    */
   private static void sendResponse(Socket socket, String status, String mimeType, Properties header, InputStream isInput, int sendCount, byte[] buf, String errMsg){
      try{
         OutputStream out = socket.getOutputStream();
         PrintWriter pw = new PrintWriter(out);

         {
            String retLine = "HTTP/1.0 " + status + " \r\n";
            pw.print(retLine);
         }
         if(mimeType!=null) {
            String mT = "Content-Type: " + mimeType + "\r\n";
            pw.print(mT);
         }
         if(header != null){
            Enumeration<?> e = header.keys();
            while(e.hasMoreElements()){
               String key = (String)e.nextElement();
               String value = header.getProperty(key);
               String l = key + ": " + value + "\r\n";
//               if(debug) BrowserUtils.LOGRUN(l);
               pw.print(l);
            }
         }
         pw.print("\r\n");
         pw.flush();
         if(isInput!=null)
            copyStream(isInput, out, buf, sendCount);
         else if(errMsg!=null) {
            pw.print(errMsg);
            pw.flush();
         }
         out.flush();
         out.close();
      }catch(IOException e){
         if(debug)
            BrowserUtils.LOGRUN(e.getMessage());
      }finally {
         try{
            socket.close();
         }catch(Throwable t){
         }
      }
   }
}

/**
 * Seekable InputStream.
 * Abstract, you must add implementation for your purpose.
 */
abstract class RandomAccessInputStream extends InputStream{

   /**
    * @return total length of stream (file)
    */
   abstract long length();

   /**
    * Seek within stream for next read-ing.
    */
   abstract void seek(long offset) throws IOException;

   @Override
   public int read() throws IOException{
      byte[] b = new byte[1];
      read(b);
      return b[0]&0xff;
   }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 jCIFS 流文件进行 Android ServerSocket 编程 的相关文章

  • 使用新语法应用 Android Gradle 插件

    如何使用新的 Gradle 插件语法应用 Android 插件 plugins id version 代替 buildscript dependencies classpath com android tools build gradle
  • Gradle 构建错误:内存不足

    当我使用 gradle 构建时 它失败并显示以下信息 OpenJDK 64 Bit Server VM warning INFO os commit memory 0x0000000788800000 89128960 0 failed e
  • 让协程等待之前的调用

    我还没有完全掌握 Kotlin 协程 基本上我希望协程在执行之前等待任何先前的调用完成 下面的代码似乎可以工作 但它正在做我认为它正在做的事情吗 private var saveJob Job null fun save saveJob s
  • 从 arraylist 和 hashmap 中删除重复项

    我有一个数组列表 其中包含付款人的姓名 另一个数组列表包含每次付款的费用 例如 nameArray 尼古拉 劳尔 洛伦佐 劳尔 劳尔 洛伦佐 尼古拉 价格数组 24 12 22 18 5 8 1 我需要将每个人的费用相加 所以数组必须变成
  • Android 中的 Sugar ORM:更新 SQLite 中保存的对象

    我是在 Android 上使用 SQLite 和 Sugar ORM 进行应用程序开发的新手 并尝试阅读 Sugar ORM 文档 但没有找到有关如何更新 SQLite 中保存的对象的任何信息 更改对象属性后还可以保存对象吗 就像是 Cus
  • 如何访问android库项目中的资源

    我正在构建一个 android 库项目 它内部需要一些静态资源 图像 xml 等 然后我想知道我可以把这些资源放在哪里以及如何访问它们 既然我把资源放到了assets文件夹 我使用 AssetManager 来访问资源 public cla
  • Google 地图删除标记路线上下文菜单

    我使用 Android Studio 的 Google 地图模板启动了一个新项目 并在地图上添加了一个标记 LatLng location new LatLng lat lng Marker marker mMap addMarker ne
  • 如何向开发人员发送崩溃报告?

    我开发 Android 应用程序 但在某些情况下我的应用程序force close 如果出现以下情况 我如何向开发人员发送包含详细信息的电子邮件force close随时发生 The ACRA https github com ACRA a
  • 使用 Android Studio 进行调试永远停留在“等待调试器”状态

    UPDATE The supposed重复是一个关于陷入 等待调试器 执行时Run 而这个问题就陷入了 等待调试器 执行时Debug 产生问题的步骤不同 解决方案也不同 每当我尝试使用Android Studio的调试功能时 运行状态总是停
  • 像 WhatsApp 一样发送图片

    我做了一个聊天应用程序 我想添加照片 文件共享我的应用程序中的概念与 WhatsApp 相同 我已经使用该应用程序制作了Xmpp Openfire目前我正在使用此功能进行照片共享 但它并不完全可靠 public void sendFile
  • 如何制作在手机和平​​板电脑上使用的响应式Android应用程序?

    我创建了一个 Android 应用程序 当我运行我的应用程序时Mobile Phone它工作得很好 但是当我跑进去时Tablet应用程序的布局已更改 那么 如何制作响应式Android应用程序用于Mobile并且也在Tablet 在Andr
  • 有多少种方法可以将位图转换为字符串,反之亦然?

    在我的应用程序中 我想以字符串的形式将位图图像发送到服务器 我想知道有多少种方法可以将位图转换为字符串 现在我使用 Base64 格式进行编码和解码 它需要更多的内存 是否有其他可能性以不同的方式做同样的事情 从而消耗更少的内存 现在我正在
  • android textview 有字符限制吗?

    我正在尝试在 android TextView 中输入超过 2000 3000 个字符 它不显示任何内容 任何一份指南是否对 android textview 有字符限制或什么 我在G3中做了一些小测试 我发现 如果activtiy布局中有
  • SDK >=26 仍需要 mipmap/ic_launcher.png?

    在 Android 中 有两种指定启动器图标 可以说是应用程序图标 的方法 老 方式 在 mipmap 文件夹中指定不同的 png 文件 通常命名为 ic launcher png 但可以通过以下方式设置名称android icon mip
  • 如何构建自定义摄像机应用程序?

    我正在尝试开发一个自定义摄像机录像机 当我的设备在 Activity 的 beginRecording 中执行 start MediaRecorder 方法时 应用程序崩溃 我不知道出了什么问题 因为我遵循谷歌API指南 http deve
  • 由于“进程崩溃”,仪器运行失败。

    我想运行以下测试 package com xxx yyy import android content Context import androidx test InstrumentationRegistry import androidx
  • Android 地理围栏无法正常工作(未调用 IntentService)

    这是我的代码 安卓清单
  • 在android中创建SQLite数据库

    我想在我的应用程序中创建一个 SQLite 数据库 其中包含三个表 我将向表中添加数据并稍后使用它们 但我喜欢保留数据库 就好像第一次安装应用程序时它会检查数据库是否存在 如果存在则更新它 否则如果不存在则创建一个新数据库 此外 我正在制作
  • 使用Intent拨打电话需要权限吗?

    在我的一个应用程序中 我使用以下代码来拨打电话 Intent intent new Intent Intent ACTION CALL Uri parse startActivity intent 文档说我确实需要以下清单许可才能这样做
  • 如何正确编写AttributeSet的XML?

    我想创建一个面板适用于 Android 平台的其他小部件 http code google com p android misc widgets 在运行时 XmlPullParser parser getResources getXml R

随机推荐