草庐IT

具有 Google Play 服务位置的 Android 服务导致应用程序随机打开

coder 2023-12-11 原文

我有一项服务应该捕获用户的位置和当前电池电量并将其发送到我的 firebase 后端。出于某种原因,似乎每当我在我的应用程序中启用此服务时,我的应用程序都会在没有任何用户交互的情况下随机打开(即使用户在另一个应用程序中,我的应用程序也会弹出)。有谁知道为什么会这样?

这是我的服务代码:

import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.server.converter.StringToIntConverter;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static android.R.attr.lines;
import static com.package.R.id.location;
import static com.package.R.id.view;

public class LocationBatteryService extends Service implements
    GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
    private String currentUser;
    private String city;
    private String country;
    private float batteryLevel;
    private int batteryLevelInt;
    protected GoogleApiClient mGoogleApiClient;
    protected LocationRequest mLocationRequest;
    protected Location mCurrentLocation;
    List<Address> addresses;
    Geocoder geocoder;




    @Override
    public void onCreate() {
        super.onCreate();
        buildGoogleApiClient();
    }
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    protected synchronized void buildGoogleApiClient() {
        Log.i("TAG", "Building GoogleApiClient");
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        createLocationRequest();
        mGoogleApiClient.connect();
    }



    protected void createLocationRequest() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String locationSyncSettings = prefs.getString("location_sync_time", "");
        long intervalTime = 5;
        if(locationSyncSettings.equals("5 minutes")) {
            intervalTime = 5;
        }
        if (locationSyncSettings.equals("10 minutes")) {
            intervalTime = 10;
        }
        if (locationSyncSettings.equals("15 minutes")) {
            intervalTime = 15;
        }
        if (locationSyncSettings.equals("30 minutes")) {
            intervalTime = 30;
        }
        if (locationSyncSettings.equals("1 hour")) {
            intervalTime = 60;
        }
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(TimeUnit.MINUTES.toMillis(intervalTime));
        mLocationRequest.setFastestInterval(60000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
    }


    @Override
    public void onConnected(Bundle connectionHint) {
        Log.i("TAG", "Connected to GoogleApiClient");
        try {
            LocationServices.FusedLocationApi.requestLocationUpdates(
                    mGoogleApiClient, mLocationRequest, this);
            mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            onLocationChanged(mCurrentLocation);
        }
        catch (SecurityException e){
            Log.d("TAG", "Error: " + e);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("TAG", "You got destoryed mate");
    }

    @Override
    public void onLocationChanged(Location location) {
        updateLocationBackend(mCurrentLocation);
    }

    @Override
    public void onConnectionSuspended(int cause) {
        // The connection to Google Play services was lost for some reason. We call connect() to
        // attempt to re-establish the connection.
        Log.i("TAG", "Connection suspended");
        mGoogleApiClient.connect();
    }

    public void onTaskRemoved (Intent rootIntent){
        this.stopSelf();
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // Refer to the javadoc for ConnectionResult to see what error codes might be returned in
        // onConnectionFailed.
        Log.i("TAG", "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
    }

    public float getBatteryLevel() {
        Intent batteryIntent = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

        // Error checking that probably isn't needed but I added just in case.
        if(level == -1 || scale == -1) {
            return 50.0f;
        }

        return ((float)level / (float)scale) * 100.0f;
    }

    private void updateLocationBackend(final Location location) {

        Log.i("TAG", "Location and battery being updated");
        batteryLevel = getBatteryLevel();
        batteryLevelInt = Math.round(batteryLevel);
        // Get current user
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        currentUser = sharedPrefs.getString("Username", "");

        // Get users added by you
        DatabaseReference ref = FirebaseDatabase.getInstance().getReferenceFromUrl(Passwords.FB_URL).child("Relations").child(currentUser);
        ref.addListenerForSingleValueEvent(
                new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        // Get user value
                        for (DataSnapshot ds : dataSnapshot.getChildren()) {
                            final String contactNumber = ds.getKey();
         // Check to see if users added you
                            final DatabaseReference ref = FirebaseDatabase.getInstance().getReferenceFromUrl(Passwords.FB_URL).child("Contacts").child(contactNumber).child(currentUser);
                            ref.addListenerForSingleValueEvent(new ValueEventListener() {
                                @Override
                                public void onDataChange(DataSnapshot dataSnapshot) {
                                    if (!dataSnapshot.child("name").exists()) {
                                    // User has not added you so do not update location
                                    }
                                    // User has added you so update location
                                    else {
                                        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                                        boolean contactLocationSetting = sharedPrefs.getBoolean(contactNumber + "_location_pref", true);
                                        Log.d("TAG", "ContactLocationSetting for " + contactNumber + " is equal to: " + contactLocationSetting);
                                        if (location != null && contactLocationSetting == true) {
                                            Map updateLocation = new HashMap();
                                            double latitude = location.getLatitude();
                                            double longitude = location.getLongitude();

                                            geocoder = new Geocoder(getApplicationContext(), Locale.getDefault());
                                            try {
                                                addresses = geocoder.getFromLocation(latitude, longitude, 1); // Here 1 represent max location result to returned, by documents it recommended 1 to 5
                                            } catch (IOException e) {
                                                Log.e("TAG", "error is: " + e);
                                            }
                                            if (addresses.size() == 0) {
                                                // Do nothing
                                            } else {
                                                city = addresses.get(0).getLocality();
                                                country = addresses.get(0).getCountryName();
                                                // String knownName = addresses.get(0).getFeatureName(); // Only if available else return NULL
                                            }
                                            updateLocation.put("battery", batteryLevelInt);
                                            updateLocation.put("latitude", latitude);
                                            updateLocation.put("longitude", longitude);
                                            updateLocation.put("city", city);
                                            updateLocation.put("country", country);
                                            updateLocation.put("lastUpdated", System.currentTimeMillis());
                                            ref.updateChildren(updateLocation);
                                            Log.d("TAG", "Updated location for: " + contactNumber);
                                        }
                                    }
                                }

                                @Override
                                public void onCancelled(DatabaseError databaseError) {

                                }
                            });


                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {
                        Log.w("TAG", "getUser:onCancelled", databaseError.toException());
                    }
                });
    }

}

这是我启动服务的代码:

import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private Toolbar toolbar;
    private TabLayout tabLayout;
    public static ViewPager viewPager;
    public static ViewPagerAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        String currentUser = sharedPrefs.getString("Username", null);
        if (currentUser == null) {
            // Take user to log in screen
            Log.d("TAG", "user needs to login");
            Intent intent = new Intent(this, MyIntro.class);
            startActivity(intent);
            finish();
        }
        else {
            // User already logged in
            setupMainActivity();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
return;
    }

    private void setupViewPager(ViewPager viewPager) {
        adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(new ChalkboardFragment(), getString(R.string.chalkboard_label));
        adapter.addFragment(new ContactsFragment(), getString(R.string.contacts_label));
        viewPager.setAdapter(adapter);
    }

    class ViewPagerAdapter extends FragmentPagerAdapter {
        private final List<Fragment> mFragmentList = new ArrayList<>();
        private final List<String> mFragmentTitleList = new ArrayList<>();

        public ViewPagerAdapter(FragmentManager manager) {
            super(manager);
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0:
                    return new ChalkboardFragment();
                case 1:
                    return new ContactsFragment();
            }
            return null;
        }

        @Override
        public int getCount() {
            return mFragmentList.size();
        }

        public void addFragment(Fragment fragment, String title) {
            mFragmentList.add(fragment);
            mFragmentTitleList.add(title);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mFragmentTitleList.get(position);
        }
    }
public void setupMainActivity() {
    setContentView(R.layout.activity_main);
    toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
    viewPager = (ViewPager) findViewById(R.id.viewpager);
    setupViewPager(viewPager);
    tabLayout = (TabLayout) findViewById(R.id.tabs);
    tabLayout.setupWithViewPager(viewPager);
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    boolean locationSetting = prefs.getBoolean("location_pref", false);
    Log.d("TAG", "location_pref " + locationSetting);
    if (isMyServiceRunning(LocationBatteryService.class) == false && locationSetting == true) {
        startService(new Intent(this, LocationBatteryService.class));
    }
}
    private boolean isMyServiceRunning(Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}

申请开始:

import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.digits.sdk.android.Digits;
import com.twitter.sdk.android.core.TwitterAuthConfig;
import com.twitter.sdk.android.core.TwitterCore;

import io.fabric.sdk.android.Fabric;

public class ApplicationStart extends Application {
    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = this.getApplicationContext();
        /*if (!FirebaseApp.getApps(this).isEmpty()) {
            FirebaseDatabase.getInstance().setPersistenceEnabled(true);
        }*/
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        String currentUser = sharedPrefs.getString("Username", null);
        if (currentUser == null) {
            // Take user to log in screen
            Log.d("TAG", "user needs to login");
            Intent intent = new Intent(this, MyIntro.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
        else {
            // User already logged in
            Log.d("TAG", "user logged in: " + currentUser);
            Intent intent = new Intent(this, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
        TwitterAuthConfig authConfig = new TwitterAuthConfig(TWITTER_KEY,TWITTER_SECRET);
        Fabric.with(this, new TwitterCore(authConfig),new Digits.Builder().build());
    }
    public static Context getAppContext(){
        return mContext;
    }
}

完整 list :

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.package">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature" />

<application android:name=".ApplicationStart" android:allowBackup="false" android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme">
    <activity android:name=".MainActivity" android:label="@string/app_name" android:launchMode="singleTop">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity android:name=".LoginActivity" android:label="@string/title_activity_login"
        android:parentActivityName=".MainActivity" />
    <activity android:name=".ContactsSettingsActivity" android:label="Contact Settings"
        android:launchMode="singleTop">
        <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.package.ContactsChalkboard" />
    </activity>
    <activity android:name=".MyIntro" />
    <activity android:name=".ContactsChalkboard" />
    <activity android:name=".AlarmActivity" />
    <activity android:name=".AddContactActivity" android:parentActivityName=".MainActivity">
        <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.package.MainActivity" />
    </activity>
    <activity android:name=".PictureNameActivity" android:label="@string/title_activity_picture_name"
        android:parentActivityName=".LoginActivity">
        <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.package.LoginActivity" />
    </activity>
    <activity android:name=".Test" />
    <activity android:name=".SettingsActivity" android:label="Settings">
        <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.package.MainActivity" />
    </activity>
    <activity android:name="com.package.GeofenceActivity" android:label="@string/title_activity_geofence">
    </activity>

    <service android:name=".LocationBatteryService" android:enabled="true" android:exported="true" />
    <service android:name=".MyFirebaseMessagingService">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
    <service android:name=".MyFirebaseInstanceIDService">
        <intent-filter>
            <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
        </intent-filter>
    </service>
    <service android:name=".GeofenceTransitionsIntentService" />
    <receiver android:name=".AlarmReceiver" android:enabled="true" android:exported="true" />
    <receiver android:name=".BootReceiver" android:enabled="true" android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <meta-data android:name="com.google.android.geo.API_KEY" android:value="API KEY VALUE" />
    <meta-data android:name="io.fabric.ApiKey" android:value="API KEY VALUE" />
</application>
</manifest>

最佳答案

嗯,不能保证系统一直运行你的服务。当应用程序被终止,并且必须交付一个新位置时,播放服务库将实例化应用程序并启动您的服务。您必须从您的应用程序类中删除 startActivity

关于具有 Google Play 服务位置的 Android 服务导致应用程序随机打开,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40942577/

有关具有 Google Play 服务位置的 Android 服务导致应用程序随机打开的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  4. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  5. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  6. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  9. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  10. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

随机推荐