【问题标题】:Very hard object-oriented "transplantation"in androidandroid中非常困难的面向对象的“移植”
【发布时间】:2013-12-03 18:23:20
【问题描述】:

在我的 android 应用程序中,至少有 4 个地方用户可以单击照片,然后出现一个对话框,上面写着“拍照或从图库中选择以更改您的照片” 在这些地方的每一个地方都有不同的活动,具有不同的变量和不同的 xml 元素名称,我有很多非常相似的代码,在我的应用程序中重复。

我想把它放在一个单独的班级里,但是被代码缠住了,感觉就像心脏移植一样。我的面向对象技能和 Java 技能不是很好(3 个月的经验),也许在 Android 中有一些我不知道的解决方法。

我将提供来自不同课程的两个示例,以便您了解有多少重复。如果有人能帮我制作一个单独的对象,我将不胜感激

设置类,图片转换器摘录:

iUserAvatarSettings.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                final Dialog dialog = new Dialog(Settings.this, R.style.no_title_dialog);
                if (!userHasProvidedOwnPhoto) {
                    dialog.setContentView(R.layout.signup_avatar_upload_dialog);
                } else {
                    dialog.setContentView(R.layout.signup_avatar_upload_dialog_2);
                    bDeleteAvatar = (Button) dialog.findViewById(R.id.bDeleteAvatar);
                    try {
                        bDeleteAvatar.setTypeface(font1);
                    } catch (Exception e) {
                    }
                    bDeleteAvatar.setOnClickListener(new OnClickListener() {

                        @Override
                        public void onClick(View v) {
                            setAvatarPath("default_avatar");
                            userHasProvidedOwnPhoto = false;
                            if (userSex.equals("Male") || userSex.equals("")) {
                                iUserAvatarSettings.setImageResource(R.drawable.avatar_default_male);

                            } else {
                                iUserAvatarSettings.setImageResource(R.drawable.avatar_default_female);
                            }
                            dialog.dismiss();
                        }
                    });
                }
                Button bTakeAPhoto = (Button) dialog.findViewById(R.id.bTakeAPhoto);
                Button bSelectPhotoFromFile = (Button) dialog.findViewById(R.id.bSelectPhotoFromFile);
                Button bCancelAvatarUpload = (Button) dialog.findViewById(R.id.bCancelAvatarUpload);
                try {
                    bTakeAPhoto.setTypeface(font1);
                    bSelectPhotoFromFile.setTypeface(font1);
                    bCancelAvatarUpload.setTypeface(font1);
                } catch (Exception e) {
                }

                bTakeAPhoto.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (isIntentAvailable(Settings.this, MediaStore.ACTION_IMAGE_CAPTURE)) {
                            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                            startActivityForResult(takePictureIntent, TAKE_IMAGE_WITH_CAMERA);

                            dialog.dismiss();

                        } else {
                            toastMaker.toast(net.asdqwe.activities.Settings.this, Configurationz.ErrorMessages.DEVICE_UNABLE_TO_TAKE_PHOTOS,  Toast.LENGTH_LONG);
                            dialog.dismiss();
                            userHasProvidedOwnPhoto = false;
                        }
                    }
                });

                bSelectPhotoFromFile.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent getImageFromGallery = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                        getImageFromGallery.setType("image/*");
                        startActivityForResult(getImageFromGallery, PICK_IMAGE);
                        //avatarPath = saveUserAvatar.getUserAvatar().toString();
                        //setAvatarPath(saveUserAvatar.getUserAvatar().toString()); // this remains under question
                        dialog.dismiss();
                    }
                });

                bCancelAvatarUpload.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        dialog.dismiss();
                    }
                });
                dialog.show();
            }
        }); // end of image button on click handling

    public static boolean isIntentAvailable(Context context, String action) {
        final PackageManager packageManager = context.getPackageManager();
        final Intent intent = new Intent(action);
        List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        return list.size() > 0;
    }

    private void handleSmallCameraPhoto(Intent intent) {

        try {
            Bundle extras = intent.getExtras();
            mImageBitmap = (Bitmap) extras.get("data");
            iUserAvatarSettings.setImageBitmap(mImageBitmap);
            saveUserAvatar.SaveImage(this, mImageBitmap);
            userHasProvidedOwnPhoto = true;
            setAvatarPath(saveUserAvatar.getUserAvatar().toString());
        } catch (Exception e) {
            toastMaker.toast(net.asdqwe.activities.Settings.this, Configurationz.ErrorMessages.TAKING_PHOTO_FAILED,  Toast.LENGTH_LONG);
            userHasProvidedOwnPhoto = false;
        }

    }

    private void handleGalleryPhoto(Intent intent) {
        try {
            Uri _uri = intent.getData();
            Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
            cursor.moveToFirst();
            final String imageFilePath = cursor.getString(0);
            cursor.close();
            mImageBitmap2 = BitmapFactory.decodeFile(imageFilePath);
            iUserAvatarSettings.setImageBitmap(mImageBitmap2);
            saveUserAvatar.SaveImage(this, mImageBitmap2);
            userHasProvidedOwnPhoto = true;
            setAvatarPath(saveUserAvatar.getUserAvatar().toString());
        } catch (Exception e) {
            toastMaker.toast(net.zxcasd.activities.Settings.this, Configurationz.ErrorMessages.PICKING_PHOTO_FROM_GALLERY_FAILED,  Toast.LENGTH_LONG);
            userHasProvidedOwnPhoto = false;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == TAKE_IMAGE_WITH_CAMERA && resultCode == RESULT_OK && null != data) {
            handleSmallCameraPhoto(data);

        } else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK && null != data) {
            handleGalleryPhoto(data);
        }
    }

注册类,图片摘录:

iUserAvatar.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            final Dialog dialog = new Dialog(Signup.this, R.style.no_title_dialog);
            if (!userHasProvidedOwnPhoto) {
                dialog.setContentView(R.layout.signup_avatar_upload_dialog);
            } else {
                dialog.setContentView(R.layout.signup_avatar_upload_dialog_2);
                bDeleteAvatar = (Button) dialog.findViewById(R.id.bDeleteAvatar);
                try {
                    bDeleteAvatar.setTypeface(font1);
                } catch (Exception e) {
                }
                bDeleteAvatar.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        userHasProvidedOwnPhoto = false;
                        if (userSex.equals("Male") || userSex.equals("")) {
                            iUserAvatar.setImageResource(R.drawable.avatar_default_male);
                        } else {
                            iUserAvatar.setImageResource(R.drawable.avatar_default_female);
                        }
                        dialog.dismiss();
                    }
                });
            }
            Button bTakeAPhotoSignupPage = (Button) dialog.findViewById(R.id.bTakeAPhoto);
            Button bSelectPhotoFromFileSignupPage = (Button) dialog.findViewById(R.id.bSelectPhotoFromFile);
            Button bCancelAvatarUpload = (Button) dialog.findViewById(R.id.bCancelAvatarUpload);
            try {
                bTakeAPhotoSignupPage.setTypeface(font1);
                bSelectPhotoFromFileSignupPage.setTypeface(font1);
                bCancelAvatarUpload.setTypeface(font1);
            } catch (Exception e) {
            }
            bTakeAPhotoSignupPage.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {

                    if (isIntentAvailable(Signup.this, MediaStore.ACTION_IMAGE_CAPTURE)) {
                        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                        startActivityForResult(takePictureIntent, TAKE_IMAGE_WITH_CAMERA);
                        dialog.dismiss();

                    } else {
                        toastMaker.toast(net.asdqwe.activities.Signup.this, Configurationz.ErrorMessages.DEVICE_UNABLE_TO_TAKE_PHOTOS,  Toast.LENGTH_LONG);
                        dialog.dismiss();
                        userHasProvidedOwnPhoto = false;
                    }
                }
            });

            bSelectPhotoFromFileSignupPage.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent getImageFromGallery = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                    getImageFromGallery.setType("image/*");
                    startActivityForResult(getImageFromGallery, PICK_IMAGE);
                    dialog.dismiss();
                }
            });

            bCancelAvatarUpload.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    // and deal with photo
                    dialog.dismiss();
                }
            });
            dialog.show();
        }
    });

public static boolean isIntentAvailable(Context context, String action) {
    final PackageManager packageManager = context.getPackageManager();
    final Intent intent = new Intent(action);
    List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
    return list.size() > 0;
}
private void handleSmallCameraPhoto(Intent intent) {

    try {
        Bundle extras = intent.getExtras();
        mImageBitmap = (Bitmap) extras.get("data");
        iUserAvatar.setImageBitmap(mImageBitmap);
        saveUserAvatar.SaveImage(this, mImageBitmap);
        userHasProvidedOwnPhoto = true;
    } catch (Exception e) {
        toastMaker.toast(net.asdqwe.activities.Signup.this, Configurationz.ErrorMessages.TAKING_PHOTO_FAILED,  Toast.LENGTH_LONG);
        userHasProvidedOwnPhoto = false;
    }

}

private void handleGalleryPhoto(Intent intent) {
    try {
        Uri _uri = intent.getData();
        Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
        cursor.moveToFirst();
        final String imageFilePath = cursor.getString(0);
        cursor.close();
        mImageBitmap2 = BitmapFactory.decodeFile(imageFilePath);
        iUserAvatar.setImageBitmap(mImageBitmap2);
        saveUserAvatar.SaveImage(this, mImageBitmap2);
        userHasProvidedOwnPhoto = true;
    } catch (Exception e) {
        toastMaker.toast(net.asdqwe.activities.Signup.this, Configurationz.ErrorMessages.PICKING_PHOTO_FROM_GALLERY_FAILED,  Toast.LENGTH_LONG);
        userHasProvidedOwnPhoto = false;
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == TAKE_IMAGE_WITH_CAMERA && resultCode == RESULT_OK && null != data) {
        handleSmallCameraPhoto(data);

    } else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK && null != data) {
        handleGalleryPhoto(data);
    }
}

【问题讨论】:

  • 到目前为止,您在refactoring 的努力中做了哪些尝试?你在使用 IDE 吗?是否提供重构工具?
  • 好吧,我现在有两个想法 - 将每个摘录末尾的这三个方法取出到一个单独的类中,并为这两种情况使用相同的 xml 对话框。我使用的是 Eclipse IDE,但我没有使用它的重构工具(如果有的话)
  • 每次创建new OnClickListener() 时,它们都可以是扩展 OnClickListener 类的单独类。然后你可以调用 new extendedListener() 而不必重新粘贴覆盖的方法。
  • 尝试选择一些代码;右键单击并选择重构 -> 提取方法。
  • IDE 是否足够聪明,不会把事情搞砸?

标签: java android oop


【解决方案1】:
  1. isIntentAvailable(Context, String) 移动到实用程序类中。这在其他一些用例中可能会有所帮助。
  2. 创建一个包含所有常用活动方法的抽象类(称为MyAbstractActivity):handleSmallCameraPhoto(Intent)handleGalleryPhoto(Intent)onActivityResult(int, int, Intent)
    • iUserAvatarSettings / iUserAvatar 必须是 MyAbstractActivity 的一部分。如果您需要针对不同用例的不同实现,请创建一个抽象 getter,f.e. getUserAvatarButton()
    • setAvatarPath(String) 仅用于两个类之一。在 MyAbstractActivity 中将其设为抽象方法并根据需要实现。
    • 让其他类继承自MyAbstractActivity
  3. 将 OnClickListener 转换为一个类。
    • 作为构造函数参数,您传递 MyAbstractActivity 的子类和 Button (iUserAvatarSettings / iUserAvatar)。
    • 使用构造函数参数来引用方法和变量,例如userHasProvidedOwnPhoto
    • iUserAvatar.setOnClickListener(...) 部分将保留在 MyAbstractActivity 中。
  4. 现在您已经删除了代码冗余,但还有更多工作要做。可能封装现在还不够好。
    • 考虑将方法和变量移动到 OnClickListener 类或远离它。
    • 考虑将方法和变量移至 MyAbstractActivity 或远离它。
    • 请一些更有经验的开发人员进行审核。 (不要让他重构它。)
    • 如果您有具体的重构问题,请回复她并询问社区。​​li>

【讨论】:

    【解决方案2】:

    你可以在 Github 上查看我的项目Android Image Helper

    具有图像选择功能的 Activity 如下所示:

    public class ImageUploaderActivity extends Activity implements ImageChooseCallback {
        ImageUploadEngine uploadEngine;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            uploadEngine = new ImageUploadEngine.Builder(this, savedInstanceState).build();
            //...
            someButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    uploadEngine.performImageAsk(R.string.app_name, R.string.newphoto, R.string.oldphoto, R.string.choose, R.drawable.ic_launcher);
                }
            });
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
            uploadEngine.onSaveInstanceState(outState);
        }
    
        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            if(uploadEngine.onActivityResult(requestCode, resultCode, data)) return;
            super.onActivityResult(requestCode, resultCode, data);
        }
    
        @Override
        public void onCanceled(ImageUploadEngine engine) {
            //notifying you that the user has canceled the request
        }
    
        @Override
        public void onChosen(ImageUploadEngine engine, String path, boolean newPicture) {
            //notifying you that the user has selected an image
        }
    
        @Override
        public void onError(ImageUploadEngine engine, Exception ex) {
            //notifying you that an error has occurred
        }
    }
    

    uploadEngine 还公开了 performImageTake 和 performImageChoose 函数,以防默认对话框(选择)不适合您的需要。

    仅供参考:

    本项目旨在为安卓开发者提供一个易于使用的工具,供安卓开发者在需要用户选择安卓手机上的图片并上传时使用。如果该过程在特定手机上出现任何错误,则应在库内进行处理,以在所有手机上提供相同的用户体验。

    【讨论】:

      【解决方案3】:

      这是我重构代码的方法。我也试图为我的设计提供一些理由。

      1) 您可以将对话框抽象为一个小部件。

      您可以创建一个扩展Dialog 并提供所需功能的PhotoPickerDialog。然后你可以在不同的地方/活动中重复使用这个小部件。

      public class PhotoPickerDialog extends Dialog {
      
          //private fields
          private Typeface buttonFont = Typeface.DEFAULT;
          private Button bTakeAPhoto;
          private Button bSelectPhotoFromFile;
          private Button bCancelAvatarUpload;
          private IMediaHelper mMediaHelper;
      
          public PhotoPickerDialog(Context context) {
              super(context);
              initView();
          }
      
          public PhotoPickerDialog(Context context, int theme) {
              super(context, theme);
              initView();
          }
      
          protected PhotoPickerDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
              super(context, cancelable, cancelListener);
              initView();
          }
      
          protected void setLayoutResource() {
              setContentView(R.layout.signup_avatar_upload_dialog);
          }
      
          protected void initView() {
              setLayoutResource();
      
              bTakeAPhoto = (Button) findViewById(R.id.bTakeAPhoto);
              bSelectPhotoFromFile = (Button) findViewById(R.id.bSelectPhotoFromFile);
              bCancelAvatarUpload = (Button) findViewById(R.id.bCancelAvatarUpload);
      
              bCancelAvatarUpload.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      dismiss();
                  }
              });
          }
      
          /**
           * Set the type face of the button, in case you want to have different
           * font at different times.
           */
          public void setButtonFont(Typeface font) {
              buttonFont = font;
              bTakeAPhoto.setTypeface(buttonFont);
              bSelectPhotoFromFile.setTypeface(buttonFont);
              bCancelAvatarUpload.setTypeface(buttonFont);
          }
      
          /**
           * Set the media helper object which implements the functionality to
           * take and pick photo. So the task delegated to this media helper.
           */
          public void setMediaHelper(IMediaHelper mediaHelper) {
              mMediaHelper = mediaHelper;
              bTakeAPhoto.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      mMediaHelper.takePhoto();
                      dismiss();
                  }
              });
      
              bSelectPhotoFromFile.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      mMediaHelper.pickPhoto();
                      dismiss();
                  }
              });
          }
      }
      

      因为您需要两种不同类型的对话框,具体取决于用户是否已经拥有头像照片。让我们创建一个扩展PhotoPickerDialogPhotoPickerDialogWithDelete。然后我们只需要override几个方法并添加删除回调就可以制作我们的新型对话框,而无需重新编写大量代码。

      public class PhotoPickerDialogWithDelete extends PhotoPickerDialog {
          private Button bDeleteAvatar;
      
          public PhotoPickerDialogWithDelete(Context context) {
              super(context);
          }
      
          public PhotoPickerDialogWithDelete(Context context, int theme) {
              super(context, theme);
          }
      
          protected PhotoPickerDialogWithDelete(Context context, boolean cancelable, OnCancelListener cancelListener) {
              super(context, cancelable, cancelListener);
          }
      
          @Override
          protected void setLayoutResource() {
              setContentView(R.layout.signup_avatar_upload_dialog_2);
          }
      
          @Override
          protected void initView() {
              super.initView();
              bDeleteAvatar = (Button) findViewById(R.id.bDeleteAvatar);
          }
      
          @Override
          public void setButtonFont(Typeface font) {
              super.setButtonFont(font);
              bDeleteAvatar.setTypeface(font);
          }
      
          public void setOnDeleteListener(final View.OnClickListener listener) {
              bDeleteAvatar.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      dismiss();
                      listener.onClick(v);
                  }
              });
          }
      }
      

      2) 将与照片意图相关的功能移至abstract 基类MediaSupportActivity

      所有需要支持拍照/挑选照片的活动只需要从这个类中extend并实现两个简单的方法handleSmallCameraPhoto(Bitmap)handleGalleryPhoto(Bitmap)。他们不必明确关心如何拍摄/挑选照片,而只关心如何处理它们(对于不同的活动可能有所不同)。

      public abstract class MediaSupportActivity extends Activity implements IMediaHelper {
      
          private static final int TAKE_IMAGE_WITH_CAMERA = 1;
          private static final int PICK_IMAGE = 2;
      
          protected abstract void handleSmallCameraPhoto(Bitmap image);
          protected abstract void handleGalleryPhoto(Bitmap image);
      
          @Override
          protected void onActivityResult(int requestCode, int resultCode, Intent data) {
              super.onActivityResult(requestCode, resultCode, data);
      
              if (requestCode == TAKE_IMAGE_WITH_CAMERA && resultCode == RESULT_OK && null != data) {
                  Bundle extras = data.getExtras();
                  Bitmap bitmap = (Bitmap) extras.get("data");
                  handleSmallCameraPhoto(bitmap);
              }
              else if (requestCode == PICK_IMAGE && resultCode == RESULT_OK && null != data) {
                  Uri _uri = data.getData();
                  Cursor cursor = getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
                  cursor.moveToFirst();
                  final String imageFilePath = cursor.getString(0);
                  cursor.close();
                  Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
                  handleGalleryPhoto(bitmap);
              }
          }
      
          @Override
          public void takePhoto() {
              Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
              startActivityForResult(takePictureIntent, MediaSupportActivity.TAKE_IMAGE_WITH_CAMERA);
          }
      
          @Override
          public void pickPhoto() {
              Intent getImageFromGallery = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
              getImageFromGallery.setType("image/*");
              startActivityForResult(getImageFromGallery, MediaSupportActivity.PICK_IMAGE);
          }
      }
      

      3) 有一个接口IBBMediaHelper 定义了两个方法:takePhoto()pickPhoto()。我们将IBBMediaHelper 实例传递给对话框,以便该对话框无法完全访问MediaSupportActivity 的其他属性和方法

      public interface IMediaHelper {
      
          public void takePhoto();
      
          public void pickPhoto();
      }
      

      4) 下面是如何一起使用上述类的示例:

      public class MainActivity extends MediaSupportActivity {
      
          Button iUserAvatarSettings;
          private boolean userHasProvidedOwnPhoto = false;
      
          public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              iUserAvatarSettings = (Button) findViewById(R.id.button);
              iUserAvatarSettings.setOnClickListener(new OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      final PhotoPickerDialog dialog;
                      if (!userHasProvidedOwnPhoto) {
                          dialog = new PhotoPickerDialog(MainActivity.this);
                      }
                      else {
                          dialog = new PhotoPickerDialogWithDelete(MainActivity.this);
                          ((PhotoPickerDialogWithDelete)dialog).setOnDeleteListener(onAvatarDelete);
                      }
                      dialog.setButtonFont(Typeface.MONOSPACE);
                      dialog.setMediaHelper(MainActivity.this);
                      dialog.show();
                  }
              });
          }
      
          @Override
          protected void handleSmallCameraPhoto(Bitmap image) {
              // sample code
              ImageView imageView = (ImageView) findViewById(R.id.imageView);
              imageView.setImageBitmap(image);
          }
      
          @Override
          protected void handleGalleryPhoto(Bitmap image) {
              // sample code
              ImageView imageView = (ImageView) findViewById(R.id.imageView);
              imageView.setImageBitmap(image);
          }
      
          private OnClickListener onAvatarDelete = new OnClickListener() {
              @Override
              public void onClick(View v) {
                  // this is executed when delete is clicked
                  // check for male/female and update the avatar
                  // etc. etc.
              }
          };
      }
      

      更新:您还可以创建包含ImageViewPhotoPickerDialog 的头像小部件AvatarWidget 类。该类封装了显示头像、点击显示菜单以及点击删除时设置默认头像的逻辑。然后在主活动中,只需通过代码或 XML 声明您的头像小部件,并在收到图像时调用其上的设置器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-30
        • 1970-01-01
        • 1970-01-01
        • 2015-01-22
        • 2013-11-04
        • 2021-09-07
        • 1970-01-01
        • 2014-12-15
        相关资源
        最近更新 更多