2014-02-25 6 views
0

내 현재 설정에 대해 조금 :블루투스 연결 (안드로이드 소켓)

은 현재 내가 다른 조각의 무리로 안드로이드 프로그램 분할이있다. 홈 조각은 특정 MAC 주소 및 소켓 UUID를 사용하여 Bluetooth 연결을 검색합니다. 이 연결은 잘 작동합니다 (아직 문제 없음).

브로드 캐스팅 블루투스 서버는 우분투를 실행중인 beaglebone black으로 bluez를 사용하여 특정 UUID로 특정 서비스를 브로드 캐스트합니다.

문제 : 갤러리 조각으로 전환하면 이미 연결된 Bluetooth 서비스를 사용하여 데이터 (그림)를 보내려고합니다.

내가 문제가되는 곳은 outputStream.write(msgBuffer);입니다. "null"오류와 함께 반환됩니다.

지금까지 디버깅을 위해 내가 수행 한 다음

내가 함께 블루투스 서버에 연결 한 후, 그래서 나는 원래 연결 적 (홈 조각에) 일을하는지 궁금했다 connect()를 호출하고 socket.getConnected()를 호출하여 테스트했다. 이 사실은 Bluetooth 소켓이 Bluetooth 서버에 연결되어야 함을 의미합니다.

그런 다음 갤러리 조각에서 무슨 일이 일어나고 있는지보고 싶었습니다. outputStream.write (msgBuffer);로 데이터를 보내기 전에; 명령, 나는 false를 반환 socket.getConnected()를 실행했습니다. 따라서 소켓 연결이 끊어져 있어야 함을 의미합니다. 이제 Bluetooth 서버가 분리 되었습니까? 블루투스 연결을 유지하려면 특별한 조치를 취해야합니까? 조각을 전환 할 때 연결을 유지하려면 특별한 작업을 수행해야합니까?

이 두 이벤트 사이에 내 연결이 끊어 지도록하는 원인은 무엇입니까?

다음은 두 가지 단편입니다.

홈 조각

package edu.umass.ecs.chalkmaster3000; 

import android.annotation.TargetApi; 
import android.app.Activity; 
import android.app.Fragment; 
import android.bluetooth.BluetoothAdapter; 
import android.bluetooth.BluetoothDevice; 
import android.bluetooth.BluetoothSocket; 
import java.io.IOException; 
import java.util.UUID; 

/** 
* Created by Oskoss on 11/16/13. 
*/ 
public class HomeFragment extends Fragment { 

    public static final int BT_ACTIVITY = 5; 
    public int i; 

    ActivityCommunication activityCallback; 

    public interface ActivityCommunication{ 
     public void updateColors(int color); 
     public String getAddress(); 
     public UUID getUUID(); 
     public BluetoothSocket getBTSocket(); 
     public BluetoothAdapter getBTAdapter(); 
     public void setConnectionStatus(boolean status); 
     public boolean getConnectionStatus(); 
     public View getView(int viewID); 
     public Context getContext(); 
    } 

    @Override 
    public void onAttach(Activity activity) { 
     super.onAttach(activity); 
     try { 
      activityCallback = (ActivityCommunication) activity; 
     } catch (ClassCastException e) { 
      throw new ClassCastException(activity.toString() 
        + " must implement ActivityCommunication"); 
     } 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ 
     View V = inflater.inflate(R.layout.home_fragment,container,false); 
     ImageView robotImage = (ImageView) V.findViewById(R.id.robotStatus); 
     ImageButton staticRobotImage = (ImageButton) V.findViewById(R.id.button); 
     TextView status = (TextView) V.findViewById(R.id.textView_Status); 
     if(activityCallback.getConnectionStatus()){ 
      V.setBackgroundColor(getResources().getColor(R.color.blue)); 
      activityCallback.updateColors(1); 
      activityCallback.setConnectionStatus(true); 

      status.setText("Bluetooth Connected to Dusty!"); 
      robotImage.setImageResource(R.drawable.happy_dusty); 
      Animation pulse = AnimationUtils.loadAnimation(activityCallback.getContext(), R.anim.pulse); 
      robotImage.startAnimation(pulse); 
     } 
     else{ 
      //checkBTStates(); 
      if(BluetoothAdapter.getDefaultAdapter()==null){ 
       V.setBackgroundColor(getResources().getColor(R.color.orange)); 
       staticRobotImage.setImageResource(R.drawable.sad_dusty); 
       //robotImage.setImageResource(R.drawable.sad_dusty); 
       status.setText("Your phone has no Bluetooth :/!"); 
       activityCallback.updateColors(0); 
       activityCallback.setConnectionStatus(false); 
      } 
      else{ 
       V.setBackgroundColor(getResources().getColor(R.color.red)); 
       staticRobotImage.setImageResource(R.drawable.sad_dusty); 
       //robotImage.setImageResource(R.drawable.sad_dusty); 
       activityCallback.updateColors(2); 
       activityCallback.setConnectionStatus(false); 
      } 

      final ImageButton button = (ImageButton) V.findViewById(R.id.button); 
      button.setOnClickListener(new View.OnClickListener() { 
       public void onClick(View v) { 
        checkBTStates(); 
       } 
      }); 
     } 
     return V; 
    } 

    private void checkBTStates() { 
     BluetoothAdapter btAdapter = activityCallback.getBTAdapter(); 
     View currentView = activityCallback.getView(R.id.home_fragment); 
     ImageButton staticRobotImage = (ImageButton) getView().findViewById(R.id.button); 
     TextView status = (TextView) getView().findViewById(R.id.textView_Status); 
     // Check for Bluetooth support and then check to make sure it is turned on 
     if(btAdapter==null) { 
      status.setText("Fatal Error: Bluetooth doesn't seem to be supported on your device! :("); 
      staticRobotImage.setImageResource(R.drawable.sad_dusty); 
      //Toast toast = Toast.makeText(activityCallback.getContext(), "Fatal Error: Bluetooth doesn't seem to be supported on your device! :(", Toast.LENGTH_LONG); 
      //toast.show(); 
      currentView.setBackgroundColor(getResources().getColor(R.color.orange)); 
      activityCallback.updateColors(0); 
      activityCallback.setConnectionStatus(false); 
      //robotImage.setImageResource(R.drawable.sad_dusty); 
     } 
     else { 
       if (btAdapter.isEnabled()) { 
        connectBT(); 
        } 
       else { 
         //Prompt user to turn on Bluetooth 
         Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 
         startActivityForResult(enableBtIntent, BT_ACTIVITY); 
       } 
     } 
    } 

    public String address = "00:02:72:3F:86:B3"; 

    final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); 
    public BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); 
    // Set up a pointer to the remote node using it's address. 
    BluetoothDevice device = btAdapter.getRemoteDevice(address); 
    BluetoothSocket btSocket = null; 



    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 
    private void connectBT(){ 
     ImageButton staticRobotImage = (ImageButton) getView().findViewById(R.id.button); 
     TextView status = (TextView) getView().findViewById(R.id.textView_Status); 
     try { 
      btSocket = device.createInsecureRfcommSocketToServiceRecord(MY_UUID); 
     } catch (IOException e) { 
      status.setText("Fatal Error in onResume() and socket create failed"); 
      //Toast toast = Toast.makeText(activityCallback.getContext(), "Fatal Error in onResume() and socket create failed: " + e.getMessage() + ".", Toast.LENGTH_LONG); 
      //toast.show(); 
     } 

     View currentView = activityCallback.getView(R.id.home_fragment); 
     ImageView robotImage = (ImageView) activityCallback.getView(R.id.robotStatus); 


     // Two things are needed to make a connection: 
     // A MAC address, which we got above. 
     // A Service ID or UUID. In this case we are using the 
     //  UUID for SPP. 


     // Discovery is resource intensive. Make sure it isn't going on 
     // when you attempt to connect and pass your message. 
     btAdapter.cancelDiscovery(); 

     // Establish the connection. This will block until it connects. 
     try { 
      btSocket.connect(); 
      if(btSocket.isConnected()){ 

      Toast toast = Toast.makeText(activityCallback.getContext(), "socket is connected", Toast.LENGTH_LONG); 
      toast.show(); 
      } 
      currentView.setBackgroundColor(getResources().getColor(R.color.blue)); 
      activityCallback.updateColors(1); 
      activityCallback.setConnectionStatus(true); 
      ImageButton connect = (ImageButton) getView().findViewById(R.id.button); 
      ViewGroup layout = (ViewGroup) connect.getParent(); 
      status.setText("Bluetooth Connected to Dusty!"); 
      robotImage.setImageResource(R.drawable.happy_dusty); 
      Animation pulse = AnimationUtils.loadAnimation(activityCallback.getContext(), R.anim.pulse); 
      robotImage.startAnimation(pulse); 
      if(null!=layout) //for safety only as you are doing onClick 
       layout.removeView(connect); 

     } catch (IOException e) { 
      try { 
       staticRobotImage.setImageResource(R.drawable.sad_dusty); 
       currentView.setBackgroundColor(getResources().getColor(R.color.orange)); 
       activityCallback.updateColors(0); 
       //robotImage.setImageResource(R.drawable.sad_dusty); 
       status.setText("No Bluetooth server broadcasting to connect to...."); 
       activityCallback.setConnectionStatus(false); 
       //Toast toast = Toast.makeText(activityCallback.getContext(),"No Bluetooth server broadcasting to connect to....", Toast.LENGTH_LONG); 
       //toast.show(); 
       btSocket.close(); 
      } catch (IOException e2) { 
       status.setText("Fatal Error in onResume() and unable to close socket during connection failure"); 
       //Toast toast = Toast.makeText(activityCallback.getContext(),"Fatal Error in onResume() and unable to close socket during connection failure" + e2.getMessage() + ".", Toast.LENGTH_LONG); 
       //toast.show(); 
      } 
     } 
    } 

    @Override 
    public void onActivityResult(int requestCode, int resultCode, Intent data) 
    { 
     TextView status = (TextView) getView().findViewById(R.id.textView_Status); 
     ImageButton staticRobotImage = (ImageButton) getView().findViewById(R.id.button); 
     super.onActivityResult(requestCode, resultCode, data); 
     View currentView = activityCallback.getView(R.id.home_fragment); 
     switch(requestCode){ 
      case BT_ACTIVITY: 
       if(resultCode == -1){ 
        i = 50; //Stop asking the user to enable bluetooth 
        status.setText("Bluetooth is now on!"); 
        connectBT(); 
        Toast.makeText(activityCallback.getContext(), "Bluetooth is now on!", Toast.LENGTH_SHORT).show(); 
       } 
       else if(resultCode == 0){ 
        staticRobotImage.setImageResource(R.drawable.sad_dusty); 
        currentView.setBackgroundColor(getResources().getColor(R.color.red)); 
        activityCallback.updateColors(2); 
        //robotImage.setImageResource(R.drawable.sad_dusty); 
        activityCallback.setConnectionStatus(false); 
        status.setText("This application requires Bluetooth...Maybe try to turn it on?! ;)"); 
        //Toast toast = Toast.makeText(activityCallback.getContext(), "This application requires Bluetooth...Maybe try to turn it on?! ;)", Toast.LENGTH_LONG); 
        //toast.show(); 
       } 
      break; 
     } 
    } 
} 

갤러리는 언제나 당신을 조각

package edu.umass.ecs.chalkmaster3000; 


import android.widget.CheckBox; 
import android.widget.GridView; 
import android.widget.ImageButton; 
import android.widget.ImageView; 
import android.widget.RelativeLayout; 
import android.widget.Toast; 

import java.io.ByteArrayOutputStream; 
import java.io.File; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.util.UUID; 



public class GalleryFragment extends Fragment { 
    private int count; 
    int id; 
    private Bitmap[] thumbnails; 
    private boolean[] thumbnailsselection; 
    private String[] arrPath; 
    private ImageAdapter imageAdapter; 
    public OutputStream outStream = null; 
    public ViewGroup container1 = null; 

    ActivityCommunication activityCallback; 

    public interface ActivityCommunication{ 
     public String getAddress(); 
     public UUID getUUID(); 
     public BluetoothSocket getBTSocket(); 
     public boolean getConnectionStatus(); 
    } 

    @Override 
    public void onAttach(Activity activity) { 
     super.onAttach(activity); 
     try { 
      activityCallback = (ActivityCommunication) activity; 
     } catch (ClassCastException e) { 
      throw new ClassCastException(activity.toString() 
        + " must implement ActivityCommunication"); 
     } 
    } 
    /** Called when the activity is first created. */ 
    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     container1 = container; 
     View V = inflater.inflate(R.layout.gallery, container, false); 
     final String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID }; 
     final String orderBy = MediaStore.Images.Media._ID; 
     Cursor imagecursor = getActivity().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
       columns, 
       MediaStore.Images.Media.DATA + " like ? ", 
       new String[]{"%/Robo Pics/%"}, 
       null); 

     int image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID); 
     this.count = imagecursor.getCount(); 
     this.thumbnails = new Bitmap[this.count]; 
     this.arrPath = new String[this.count]; 
     this.thumbnailsselection = new boolean[this.count]; 
     for (int i = 0; i < this.count; i++) { 
      imagecursor.moveToPosition(i); 
      id = imagecursor.getInt(image_column_index); 
      int dataColumnIndex = imagecursor.getColumnIndex(MediaStore.Images.Media.DATA); 
      thumbnails[i] = MediaStore.Images.Thumbnails.getThumbnail(
        getActivity().getContentResolver(), id, 
        MediaStore.Images.Thumbnails.MICRO_KIND, null); 
      arrPath[i]= imagecursor.getString(dataColumnIndex); 
     } 
     GridView imagegrid = (GridView) V.findViewById(R.id.PhoneImageGrid); 
     imageAdapter = new ImageAdapter(); 
     imagegrid.setAdapter(imageAdapter); 

     final Button selectBtn = (Button) V.findViewById(R.id.selectBtn); 
     final Button previewBtn = (Button) V.findViewById(R.id.previewBtn); 
     final Button deleteBtn = (Button) V.findViewById(R.id.deleteBtn); 
     selectBtn.setOnClickListener(new OnClickListener() { 

      public void onClick(View v) { 
       if(activityCallback.getConnectionStatus()){ 
        // TODO Auto-generated method stub 
        final int len = thumbnailsselection.length; 
        int cnt = 0; 
        String selectImages = ""; 

        for (int i =0; i<len; i++) 
        { 
         if (thumbnailsselection[i]){ 
          cnt++; 
          selectImages = selectImages + arrPath[i]; 
         } 
        } 
        if (cnt == 0){ 
         Toast.makeText(getActivity(), 
           "Dusty can't draw anything unless you select an image! Sadface", 
           Toast.LENGTH_LONG).show(); 
        } 
        else if(cnt>1){ 
         Toast.makeText(getActivity(), 
           "Dusty is amazing but he can't draw two images at one time! Sadface", 
           Toast.LENGTH_LONG).show(); 
        } 
        else { 
          byte sendArray[] = readBytes(selectImages); 
          sendBTData(sendArray); 
        } 
       } 
       else 
        Toast.makeText(getActivity(), "You need to first connect to Dusty!", Toast.LENGTH_LONG).show(); 
      } 
     }); 

     previewBtn.setOnClickListener(new OnClickListener() { 

      public void onClick(View v) { 
        final int len = thumbnailsselection.length; 
        int cnt = 0; 
        String selectImages = ""; 

        for (int i =0; i<len; i++) 
        { 
         if (thumbnailsselection[i]){ 
          cnt++; 
          selectImages = selectImages + arrPath[i]; 
         } 
        } 
        if (cnt == 0){ 
         Toast.makeText(getActivity(), 
           "There is nothing to preview! Sadface", 
           Toast.LENGTH_LONG).show(); 
        } 
        else if(cnt>1){ 
         Toast.makeText(getActivity(), 
           "You can't preview more than one thing! Silly", 
           Toast.LENGTH_LONG).show(); 
        } 
        else { 
         Intent intent = new Intent(); 
         intent.setAction(Intent.ACTION_VIEW); 
         intent.setDataAndType(Uri.parse("file://" + selectImages), "image/*"); 
         startActivity(intent); 
        } 
      } 
     }); 

     deleteBtn.setOnClickListener(new OnClickListener() { 

      public void onClick(View v) { 
        final int len = thumbnailsselection.length; 
        int cnt = 0; 
        String[] storage = new String[len]; 

        for (int i =0; i<len; i++) 
        { 
         if (thumbnailsselection[i]){ 
          cnt++; 
          storage[i] = arrPath[i]; 
         } 
        } 
        if (cnt == 0){ 
         Toast.makeText(getActivity(), 
           "Can't delete nothing! Silly", 
           Toast.LENGTH_LONG).show(); 
        } 
        else{ 
         AlertDialog diaBox = AskOption(storage); 
         diaBox.show(); 


        } 
      } 
     }); 
     return V; 
    } 

    public class ImageAdapter extends BaseAdapter { 
     private LayoutInflater mInflater; 

     public ImageAdapter() { 
      mInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     } 

     public int getCount() { 
      return count; 
     } 

     public Object getItem(int position) { 
      return position; 
     } 

     public long getItemId(int position) { 
      return position; 
     } 

     public View getView(int position, View convertView, ViewGroup parent) { 
      ViewHolder holder; 
      if (convertView == null) { 
       holder = new ViewHolder(); 
       convertView = mInflater.inflate(
         R.layout.galleryitem, null); 
       holder.imageview = (ImageView) convertView.findViewById(R.id.thumbImage); 
       holder.checkbox = (CheckBox) convertView.findViewById(R.id.yup123); 

       convertView.setTag(holder); 
      } 
      else { 
       holder = (ViewHolder) convertView.getTag(); 
      } 
      holder.checkbox.setId(position); 
      holder.imageview.setId(position); 
      holder.checkbox.setOnClickListener(new OnClickListener() { 

       public void onClick(View v) { 
        // TODO Auto-generated method stub 
        CheckBox cb = (CheckBox) v; 
        int id = cb.getId(); 
        if (thumbnailsselection[id]){ 
         cb.setChecked(false); 
         thumbnailsselection[id] = false; 
        } else { 
         cb.setChecked(true); 
         thumbnailsselection[id] = true; 
        } 
       } 
      }); 
      holder.imageview.setOnClickListener(new View.OnClickListener() { 
       public void onClick(View v) { 
        // TODO Auto-generated method stub 
        RelativeLayout row = (RelativeLayout) v.getParent(); 
        CheckBox cb = (CheckBox) row.getChildAt(2); 
        id=cb.getId(); 
        if (thumbnailsselection[id]) { 
         cb.setChecked(false); 
         thumbnailsselection[id] = false; 
        } else { 
         cb.setChecked(true); 
         thumbnailsselection[id] = true; 
        } 
       } 
      }); 
      holder.imageview.setImageBitmap(thumbnails[position]); 
      holder.checkbox.setChecked(thumbnailsselection[position]); 
      holder.id = position; 
      return convertView; 
     } 
    } 
    class ViewHolder { 
     ImageView imageview; 
     CheckBox checkbox; 
     int id; 
    } 

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 
    private void sendBTData(byte[] msgBuffer){ 
     String address = activityCallback.getAddress(); 
     UUID MY_UUID = activityCallback.getUUID(); 
     BluetoothSocket btSocket = activityCallback.getBTSocket(); 
     try { 
      btSocket.connect(); 

     } catch (IOException e) { 
      try { 
       e.printStackTrace(); 
       btSocket.close(); 
      } catch (IOException e2) { 
        e2.printStackTrace(); 
      } 
     } 

     // Create a data stream so we can talk to server. 
     Toast toast1 = Toast.makeText(getActivity(),"Sending message to server...", Toast.LENGTH_LONG); 
     toast1.show(); 

     try { 
      outStream = btSocket.getOutputStream(); 
     } catch (IOException e) { 

      Toast toast = Toast.makeText(getActivity(),"Fatal Error in onResume() and output stream creation failed:" + e.getMessage() + ".", Toast.LENGTH_LONG); 
      toast.show(); 
     } 

     try { 
      if(btSocket.isConnected()){ 

       Toast toast = Toast.makeText(getActivity(), "socket is connected", Toast.LENGTH_LONG); 
       toast.show(); 
      } 
      else{ 
       Toast toast = Toast.makeText(getActivity(), "NOT CONNECTEDDDDDD", Toast.LENGTH_LONG); 
       toast.show(); 
      } 
      outStream.write(msgBuffer); 
      Toast.makeText(getActivity(), "Images are now being sent through outter space to Robo for drawing!" , Toast.LENGTH_LONG).show(); 
     } catch (Throwable e) { 
      String msg = "In onResume() and an exception occurred during write: " + e.getMessage(); 
      e.printStackTrace(); 
      if (address.equals("00:00:00:00:00:00")) { 
       msg = msg + ".\n\nUpdate your server address from 00:00:00:00:00:00 to the correct address on line 37 in the java code"; 
      } 
      else{ 
       msg = msg + ".\n\nCheck that the SPP UUID: " + MY_UUID.toString() + " exists on server.\n\n"; 
      } 
      Toast toast = Toast.makeText(getActivity(), msg, Toast.LENGTH_LONG); 
      toast.show(); 
     } 
    } 

    public byte[] readBytes(String filePath){ 
     BitmapFactory cute = new BitmapFactory(); 
     Bitmap picSend = cute.decodeFile(filePath); 
     ByteArrayOutputStream stream1 = new ByteArrayOutputStream(); 
     picSend.compress(Bitmap.CompressFormat.PNG, 100, stream1); 
     byte picByteSend[] = stream1.toByteArray(); 
     return picByteSend; 

    } 
    public AlertDialog AskOption(final String[] toBeDeleted) 
    { 
     AlertDialog myQuittingDialogBox =new AlertDialog.Builder(getActivity()) 
       //set message, title, and icon 
       .setTitle("Delete Confirmation") 
       .setMessage("Are you sure you want to delete the selected pictures?") 
       .setIcon(R.drawable.new_pic) 

       .setPositiveButton("Delete", new DialogInterface.OnClickListener() { 

        public void onClick(DialogInterface dialog, int whichButton) { 
         for (int i = 0; i < toBeDeleted.length; i++) { 
          if (toBeDeleted[i] != null) { 
           File f = new File(toBeDeleted[i]); 
           if (f.exists()) { 
            if (f.delete()) { 

             Log.w("DELETED", "file Deleted"); 
            } else { 
             Log.w("NOT DELETED", "file not Deleted"); 
            } 
           } 
          } 

          getActivity().sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
            Uri.parse("file://" + Environment.getExternalStorageDirectory()))); 
          FragmentManager manager = getActivity().getFragmentManager(); 
          FragmentTransaction ft = manager.beginTransaction(); 
          Fragment newFragment = getFragmentManager().findFragmentByTag("gallery");; 
          newFragment.onDestroy(); 
          ft.remove(newFragment); 
          ft.replace(container1.getId(),newFragment); 
          //container is the ViewGroup of current fragment 
          ft.addToBackStack(null); 
          ft.commit(); 
         } 
         dialog.dismiss(); 
        } 

       }) 

       .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 
        public void onClick(DialogInterface dialog, int which) { 
         dialog.dismiss(); 

        } 
       }) 
       .create(); 

     return myQuittingDialogBox; 

    } 
} 

감사합니다!

편집 :

조금 깔끔하게하려고 시도했습니다. 당신이 이것을 이해하지 못한다면, 당신이 이해하지 못하는 것을 말해 주어서 더 많은 정보를 제공 할 수 있습니까?

답변

1

조각 사이에 소켓을 올바르게 전달하지 않아서 여러 개의 소켓을 열었습니다. 프로젝트에서 휴식을 취하여 그러한 어리석은 실수를 범하는 데 필요했습니다.

관련 문제