Unable to access Internet via WiFi from a Background service

↘锁芯ラ 提交于 2019-12-05 00:47:32

问题


I'll get straight onto some facts/figures I discovered pls help me if you've faced/solved a similar problem.

I send back data every 5 minutes to a server unless the user manually toggles it Off with the help of a wakeful broadcast receiver through a intent service. Plus I get wifi locks before doing (I've tried this already without locks; doesnt work either)

Now on devices sending back data from regular data networks (2G/3G/4G) this works fine but on those connected to a wifi network somehow gets me a getActiveNetworkInfo() as null (Even directly hitting the web URL gives a hostname not found error) this surely happens on HTC One (with v.4.4.2 installed) others devices do work fine.

  • we all know about the previous official connectivity manager have had issues in returning too much true / false condition even if network was not/available. I highly doubt if they've cropped up again or is just some custom OEM jing-bang

  • the alarm gets fired >> fires up the wakeful broadcast receiver >> get wifi locks >> sleep the thread for 3 secs inside theonReceive` after getting locks in order to wait for wifi to resume connectivity >> do my service work afterwards.

  • am weighing the possibility of forcing use of mobile data connection instead of wifi. (by using startUsingNetworkFeature()) Does anyone have a solution for the same?


回答1:


Hope I'm not too late, I had a problem just like yours. My app needed to send data (from internal storage file) to server at certain intervals (30m/1h/2h/4h). For getActiveNetworkInfo() to give proper result, you need to wake the wifi (because after 5-15 min of phone sleep wifi turns off). That gave me a lot of trouble, but here's how my solution works:

First, I have a WakefulBroadcastReceiver that gets called when I need to wake the phone:

/** Receiver for keeping the device awake */
public class WakeUpReceiver extends WakefulBroadcastReceiver {

    // Constant to distinguish request
    public static final int WAKE_TYPE_UPLOAD = 2;

    // AlarmManager to provide access to the system alarm services.
    private static AlarmManager alarm;
    // Pending intent that is triggered when the alarm fires.
    private static PendingIntent pIntent;

    /** BroadcastReceiver onReceive() method */
    @Override
    public void onReceive(Context context, Intent intent) {
        // Start appropriate service type
        int wakeType = intent.getExtras().getInt("wakeType");
        switch (wakeType) {
        case WAKE_TYPE_UPLOAD:
            Intent newUpload = new Intent(context, UploadService.class);
            startWakefulService(context, newUpload);
            break;
        default:
            break;
        }
    }

    /** Sets alarms */
    @SuppressLint("NewApi")
    public static void setAlarm(Context context, int wakeType, Calendar startTime) {
        // Set alarm to start at given time
        alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, WakeUpReceiver.class);
        intent.putExtra("wakeType", wakeType);
        pIntent = PendingIntent.getBroadcast(context, wakeType, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        // For android 4.4+ the method is different
        if (android.os.Build.VERSION.SDK_INT >= 
                android.os.Build.VERSION_CODES.KITKAT) {
            alarm.setExact(AlarmManager.RTC_WAKEUP, 
                    startTime.getTimeInMillis() + 5000, pIntent);
        } else {
            alarm.set(AlarmManager.RTC_WAKEUP, 
                    startTime.getTimeInMillis() + 5000, pIntent);
        }
        // The + 5000 is for adding symbolic 5 seconds to alarm start
    }
}

Note the public static void setAlarm(Context, int, Calendar) function, I use that function to set the alarms (see Main application).

Next, the service itself isn't an IntentService, just Service:

/** Service for uploading data to server */
public class UploadService extends Service {

    private static final String serverURI = "http://your.server.com/file_on_server.php";

    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;

    WifiLock wfl;

    private Intent currentIntent;

    private SharedPreferences sharedPrefs;
    private int updateInterval;
    private boolean continueService;

    /** Service onCreate() method */
    @Override
    public void onCreate() {
        super.onCreate();

        // Initialize wifi lock
        wfl = null;

        // Initialize current Intent
        currentIntent = null;

        // Create separate HandlerThread
        HandlerThread thread = new HandlerThread("SystemService",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        // Get the HandlerThread's Looper and use it for our Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);

        // Get shared variables values if set
        sharedPrefs = getSharedPreferences("serviceVars", MODE_PRIVATE);
        updateInterval = sharedPrefs.getInt("sendInterval", 60); // in your case 5
        continueService = sharedPrefs.getBoolean("bgServiceState", false);
    }

    /** Service onStartCommand() method */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // If continuing, set new alarm
        if (continueService) {
            Calendar nextStart = Calendar.getInstance();
            nextStart.set(Calendar.SECOND, 0);
            // Set alarm to fire after the interval time
            nextStart.add(Calendar.MINUTE, updateInterval);
            WakeUpReceiver.setAlarm(this, WakeUpReceiver.WAKE_TYPE_UPLOAD,
                    nextStart);
        }

        // Get current Intent and save it
        currentIntent = intent;

        // Acquire a wifi lock to ensure the upload process works
        WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        wfl = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, "WifiLock");
        if (!wfl.isHeld()) {
            wfl.acquire();
        }

        // For each start request, send a message to start a job and give
        // start ID so we know which request we're stopping when we finish
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);
        // If service gets killed, it will restart
        return START_STICKY;
    }

    /** Handler that receives messages from the thread */
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        /** Executes all service operations */
        @Override
        public void handleMessage(Message msg) {
            // First wait for 5 seconds
            synchronized (this) {
                try {
                    wait(5 * 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // Start checking if internet is enabled
            boolean hasInternet = false;
            long endTime = System.currentTimeMillis() + 55 * 1000;
            // Check every second (max 55) if connected
            while (System.currentTimeMillis() < endTime) {
                if (hasInternet(UploadService.this)) {
                    hasInternet = true;
                    break;
                }
                synchronized (this) {
                    try {
                        wait(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            // Connected to internet or 60 (5 + 55) seconds passed
            if (hasInternet) {
                // Connect to server
                connectToServer(serverURI, fileName);
            } else {
                // Can't connect, send message or something
            }

            // Stop service
            stopSelf(msg.arg1);
        }
    }

    /** Checks if phone is connected to Internet */
    private boolean hasInternet(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo ni = null;
        if (cm != null) {
            ni = cm.getActiveNetworkInfo();
        }
        return ni != null && ni.getState() == NetworkInfo.State.CONNECTED;
    }

    /** Service onDestroy() method */
    @Override
    public void onDestroy() {
        // Release wifi lock
        if (wfl != null) {
            if (wfl.isHeld()) {
                wfl.release();
            }
        }

        // Release wake lock provided by BroadcastReceiver.
        WakeUpReceiver.completeWakefulIntent(currentIntent);

        super.onDestroy();
    }

    /** Performs server communication */
    private void connectToServer(String serverUri, String dataFileName) {
        // this function does my network stuff
    }

    /** Service onBind() method - Not used */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

I also have a BOOT BroadcastReceiver, so if alarm is set and then phone is rebooted it will run service again:

/** Receiver for (re)starting alarms on device reboot operations */
public class BootReceiver extends BroadcastReceiver {
    WakeUpReceiver alarm = new WakeUpReceiver();

    /** BroadcastReceiver onReceive() method */
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            WakeUpReceiver.setAlarm(context, WakeUpReceiver.WAKE_TYPE_UPLOAD,
                    Calendar.getInstance());
        }
    }
}

Last, but not least in the Main Activity, I start the service by starting receivers:

// Start upload service now
Calendar timeNow = Calendar.getInstance();
WakeUpReceiver.setAlarm(this, WakeUpReceiver.WAKE_TYPE_UPLOAD, timeNow);

// Enable BootReceiver to (re)start alarm on device restart
getPackageManager().setComponentEnabledSetting(
        new ComponentName(this, BootReceiver.class),
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP);

And when I stop the service, I stop the BOOT receiver too:

// Disable BootReceiver to (re)start alarm on device restart
getPackageManager().setComponentEnabledSetting(
        new ComponentName(this, BootReceiver.class),
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);

Additionally, don't forget to add proper permissions and components to manifest:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

<receiver
    android:name=".BootReceiver"
    android:enabled="false" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
<receiver android:name=".WakeUpReceiver" />

<service
    android:name=".UploadService"
    android:enabled="true"
    android:label="ServerUpload"
    android:launchMode="singleInstance" />

I think I covered it all, I'll be glad if this helps anyone solve their problems.



来源:https://stackoverflow.com/questions/24750860/unable-to-access-internet-via-wifi-from-a-background-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!