如何不使用 GPS 获取 Android 设备的国家代码?

Android 手机确实很清楚它在哪里——但是有没有一种方法可以通过某种东西来检索国家,比如国家代码或国家名称?

没有任何必要知道确切的 GPS 位置-国家代码或名称就足够了,我正在使用这个代码:

 String locale = context.getResources().getConfiguration().locale.getCountry(Locale.getDefault());
System.out.println("country = "+locale);

它给了我代码“ US”,但我的设备保存在印度。有没有办法不用 GPS 或网络供应商就能找到设备的当前国家代码?

因为我在用平板电脑。

151063 次浏览

You shouldn't be passing anything in to getCountry(). Remove Locale.getDefault():

String locale = context.getResources().getConfiguration().locale.getCountry();

You can simply use this code,

TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
String countryCodeValue = tm.getNetworkCountryIso();

This will return 'US' if your current connected network is in the United States. This works without a SIM card even.

Use the link http://ip-api.com/json. This will provide all the information as JSON. From this JSON content you can get the country easily. This site works using your current IP address. It automatically detects the IP address and sendback details.

Documentation

This is what I got:

{
"as": "AS55410 C48 Okhla Industrial Estate, New Delhi-110020",
"city": "Kochi",
"country": "India",
"countryCode": "IN",
"isp": "Vodafone India",
"lat": 9.9667,
"lon": 76.2333,
"org": "Vodafone India",
"query": "123.63.81.162",
"region": "KL",
"regionName": "Kerala",
"status": "success",
"timezone": "Asia/Kolkata",
"zip": ""
}

N.B. - As this is a third-party API, do not use it as the primary solution. And also I am not sure whether it's free or not.

The checked answer has deprecated code. You need to implement this:

String locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = context.getResources().getConfiguration().getLocales().get(0).getCountry();
} else {
locale = context.getResources().getConfiguration().locale.getCountry();
}

I have created a utility function (tested once on a device where I was getting an incorrect country code based on locale).

Reference: CountryCodePicker.java

fun getDetectedCountry(context: Context, defaultCountryIsoCode: String): String {
return detectSIMCountry(context)
?: detectNetworkCountry(context)
?: detectLocaleCountry(context)
?: defaultCountryIsoCode
}


private fun detectSIMCountry(context: Context): String? {
try {
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
Log.d(TAG, "detectSIMCountry: ${telephonyManager.simCountryIso}")
return telephonyManager.simCountryIso
}
catch (e: Exception) {
e.printStackTrace()
}
return null
}


private fun detectNetworkCountry(context: Context): String? {
try {
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
Log.d(TAG, "detectNetworkCountry: ${telephonyManager.networkCountryIso}")
return telephonyManager.networkCountryIso
}
catch (e: Exception) {
e.printStackTrace()
}
return null
}


private fun detectLocaleCountry(context: Context): String? {
try {
val localeCountryISO = context.resources.configuration.locales[0].country
Log.d(TAG, "detectLocaleCountry: $localeCountryISO")
return localeCountryISO
}
catch (e: Exception) {
e.printStackTrace()
}
return null
}

Here is a complete example. It tries to get the country code from TelephonyManager (from SIM or CDMA devices), and if not available, tries to get it from the local configuration.

private static String getDeviceCountryCode(Context context) {
String countryCode;


// Try to get country code from TelephonyManager service
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if(tm != null) {
// Query first getSimCountryIso()
countryCode = tm.getSimCountryIso();
if (countryCode != null && countryCode.length() == 2)
return countryCode.toLowerCase();


if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
// Special case for CDMA Devices
countryCode = getCDMACountryIso();
}
else {
// For 3G devices (with SIM) query getNetworkCountryIso()
countryCode = tm.getNetworkCountryIso();
}


if (countryCode != null && countryCode.length() == 2)
return countryCode.toLowerCase();
}


// If network country not available (tablets maybe), get country code from Locale class
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
countryCode = context.getResources().getConfiguration().getLocales().get(0).getCountry();
}
else {
countryCode = context.getResources().getConfiguration().locale.getCountry();
}


if (countryCode != null && countryCode.length() == 2)
return  countryCode.toLowerCase();


// General fallback to "us"
return "us";
}


@SuppressLint("PrivateApi")
private static String getCDMACountryIso() {
try {
// Try to get country code from SystemProperties private class
Class<?> systemProperties = Class.forName("android.os.SystemProperties");
Method get = systemProperties.getMethod("get", String.class);


// Get homeOperator that contain MCC + MNC
String homeOperator = ((String) get.invoke(systemProperties,
"ro.cdma.home.operator.numeric"));


// First three characters (MCC) from homeOperator represents the country code
int mcc = Integer.parseInt(homeOperator.substring(0, 3));


// Mapping just countries that actually use CDMA networks
switch (mcc) {
case 330: return "PR";
case 310: return "US";
case 311: return "US";
case 312: return "US";
case 316: return "US";
case 283: return "AM";
case 460: return "CN";
case 455: return "MO";
case 414: return "MM";
case 619: return "SL";
case 450: return "KR";
case 634: return "SD";
case 434: return "UZ";
case 232: return "AT";
case 204: return "NL";
case 262: return "DE";
case 247: return "LV";
case 255: return "UA";
}
}
catch (ClassNotFoundException ignored) {
}
catch (NoSuchMethodException ignored) {
}
catch (IllegalAccessException ignored) {
}
catch (InvocationTargetException ignored) {
}
catch (NullPointerException ignored) {
}


return null;
}

Also another idea is to try an API request like in this answer.

References are here and here.

For some devices, if the default language is set different (an Indian can set English (US)) then

context.getResources().getConfiguration().locale.getDisplayCountry();

will give the wrong value. So this method is not reliable.

Also, getNetworkCountryIso() method of TelephonyManager will not work on devices which don't have SIM card (Wi-Fi tablets).

If a device doesn't have a SIM card then we can use the time zone to get the country. For countries like India, this method will work.

Sample code used to check the country is India or not.

Use the below values in your constants file

(Constants.INDIA_TIME_ZONE_ID: "asia/calcutta", Constants.NETWORK_INDIA_CODE :"in")

And in your activity, add the following code:

private void checkCountry() {


TelephonyManager telMgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (telMgr == null)
return;


int simState = telMgr.getSimState();


switch (simState) {


// If a SIM card is not available then the
// country is found using the timezone ID
case TelephonyManager.SIM_STATE_ABSENT:
TimeZone tz = TimeZone.getDefault();
String timeZoneId = tz.getID();
if (timeZoneId.equalsIgnoreCase(Constants.INDIA_TIME_ZONE_ID)) {
// Do something
}
else {
// Do something
}
break;


// If a SIM card is available then the telephony
// manager network country information is used
case TelephonyManager.SIM_STATE_READY:


if (telMgr != null) {
String countryCodeValue = tm.getNetworkCountryIso();


// Check if the network country code is "in"
if (countryCodeValue.equalsIgnoreCase(Constants.NETWORK_INDIA_CODE)) {
// Do something
}
else {
// Do something
}
}
break;
}
}

There isn't any need to call any API. You can get the country code from your device where it is located. Just use this function:

 fun getUserCountry(context: Context): String? {
try {
val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val simCountry = tm.simCountryIso
if (simCountry != null && simCountry.length == 2) { // SIM country code is available
return simCountry.toLowerCase(Locale.US)
}
else if (tm.phoneType != TelephonyManager.PHONE_TYPE_CDMA) { // Device is not 3G (would be unreliable)
val networkCountry = tm.networkCountryIso
if (networkCountry != null && networkCountry.length == 2) { // network country code is available
return networkCountry.toLowerCase(Locale.US)
}
}
}
catch (e: Exception) {
}
return null
}

If you wish to get the country code without asking for any permission, you can choose a tricky way.

The method simply uses an API to get the country code, and there aren't any third-party libraries to depend on. We can create one for us.

Here I have used Google Cloud Functions to write an API and it is so effortless.

Step 1: Create a Google Cloud Account, and set up billing (the free tier is enough)

Step 2: Create a cloud function to get the geo location

Copy this basic function to the code editor of index.js:

const cors = require('cors')


function _geolocation(req, res) {
const data = {
country_code: req.headers["x-appengine-country"],
region: req.headers["x-appengine-region"],
city: req.headers["x-appengine-city"],
cityLatLong: req.headers["x-appengine-citylatlong"],
userIP: req.headers["x-appengine-user-ip"]
}


res.json(data)
};


exports.geolocation = (req, res) => {
const corsHandler = cors({ origin: true })


return corsHandler(req, res, function() {
return _geolocation(req, res);
});
};

Also we need to copy the package.json definition:

{
"name": "gfc-geolocation",
"version": "0.0.1",
"dependencies": {
"cors": "^2.8.4"
}
}

Step 3: finish, and get the URL similar to: "https://us-central1-geolocation-mods-sdde.cloudfunctions.net/geolocation"

Step 4: parse the JSON response and get the country code

The response will look like:

{
"country": "IN",
"region": "kl",
"city": "kochi",
"cityLatLong": "9.9312,76.2673",
"userIP": "xx.xx.xx.xx"
}

Thanks and credits go to the Medium article: Free IP-based Geolocation with Google Cloud Functions

To get country code, we can also perform an HTTP call (Here, I use a third-party link, you can also buy the services to get extra information in JSON https://ipinfo.io/ ). Here, when we call the API service, they trace our IP and provide information accordingly.

<uses-permission android:name="android.permission.INTERNET" />


public String makeServiceCall() {
String response = null;
String reqUrl = "https://ipinfo.io/country";
try {
URL url = new URL(reqUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// read the response
InputStream in = new BufferedInputStream(conn.getInputStream());
response = convertStreamToString(in);
} catch (MalformedURLException e) {
Log.e(TAG, "MalformedURLException: " + e.getMessage());
} catch (ProtocolException e) {
Log.e(TAG, "ProtocolException: " + e.getMessage());
} catch (IOException e) {
Log.e(TAG, "IOException: " + e.getMessage());
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.getMessage());
}
return response;
}