65.9K
CodeProject 正在变化。 阅读更多。
Home

将旧版 API 8 应用更新到 API 18

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (9投票s)

2013年12月31日

CPOL

5分钟阅读

viewsIcon

20879

downloadIcon

210

这是我将应用程序更新到新平台和 API 的经验。

预备说明

我应该说明,代码和 XML 格式在我看来并不正确。当我在提交向导视图中使用它时,一切看起来都还可以,但在提交后我看到了错误。希望这个问题能尽快得到解决并进行更新。

引言

这突出显示了我最近将一个旧的 Android 应用程序更新到一个新的平台和 API 级别的经历。我将讨论哪些进展顺利以及需要进行哪些更改。但按照惯例,这里是您感兴趣的文件可供下载。希望 CodeProject 的编辑和其他样式和内容纯粹主义者不会对我的文章提交格式过于苛责。我知道有些人认为应该只有代码。

背景

两年前,我写了我的第一个(也是唯一一个)Android 应用,并在 CodeProject 上发布,地址是 https://codeproject.org.cn/Articles/269318/An-Adventure-in-Porting-a-Java-Applet-to-Android-w

从那时起,我就没有更新过这个应用,它是在 API 级别 8 上为运行版本 2.3.4 的摩托罗拉手机编写的。

但在这个假期,我收到了一台 Galaxy tab 3 10.1,我决定掸去代码和我的脑细胞上的灰尘,将这个应用更新到 API 级别 18,以匹配这款平板电脑上的 4.2.2 版本。

移植步骤

第一步是在我的新 PC 上下载最新的 SDK,地址是 https://developer.android.com.cn/sdk/index.html

我运行了 SDK Manager.exe 并下载了默认推荐的 5 个更新。

旧的元数据

我决定删除原始项目中的 .metadata 文件夹,担心这些数据依赖于 SDK,并可能污染新项目。

打开旧项目文件

然后我打开 Eclipse,并指向我的原始项目文件夹。此时 Eclipse 会提示创建一个新项目。

但我取消了它,而是导入了原始项目。

0

我不会详细介绍导入项目,因为我假设您已经具备这方面的知识。此外,步骤也非常简单。

导入后检测到的第一个错误

导入后出现了一些错误,这并不奇怪,我并没有期望代码无需修改就能直接构建。

指定新目标

要修复“无法解析目标 'android-10'”的错误,我只需转到 Project->Properties->Android 并选择 Android 4.4.2 版本。

我更新了 AndroidManifest.xml 文件,声明目标是最新 API,但仍必须在原始版本 8 上运行。从

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 android:versionCode="1"
 android:versionName="1.1" package="com.korsbergtools.ImpactPhysics">
<uses-sdk android:minSdkVersion="8" />

to

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      android:versionCode="1"
      android:versionName="1.1" package="com.korsbergtools.ImpactPhysics">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" />

WebView 命名空间

WebView 声明中出现了一个错误,关于“意外的命名空间”。

<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/webviewhelp"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"></WebView>

我只是删除了命名空间,解决了那个问题。

<WebView
     android:id="@+id/webviewhelp"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"></WebView> 

然后出现了一个关于旧 ProGuard 文件的错误。我只是删除了它。

此时,应用程序实际上可以在旧手机上运行了!总的来说,将这个项目/代码迁移到新的 SDK 和 Eclipse 的过程非常简单。

运行 Lint

虽然应用程序仍然可以在手机上运行,但我还注意到了一些警告,并决定尝试运行“Lint”工具。我解决了一些警告,另一些则暂时忽略了。

字符串资源

一些警告建议不要使用硬编码字符串,而是替换为 @string 资源。幸运的是,Eclipse 有一个功能可以帮助重构这些。转到 Refactor->Android->Extract Android String... 它会将选定的字符串转换为 strings.xml 文件中的一个资源。这是重构前 options.xml 部分内容的截图。

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <TableLayout  android:id="@+id/LinearLayout1" android:orientation="vertical" 
              android:layout_width="match_parent" 
              android:layout_height="match_parent" 
              android:stretchColumns="1">
        <TableRow>
            <CheckBox android:id="@+id/checkBox7" android:text="Sound"></CheckBox>
            <Button  android:id="@+id/button2" android:text="Apply and exit"></Button>            
        </TableRow>

重构后

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
    <TableLayout  android:id="@+id/LinearLayout1" android:orientation="vertical" 
               android:layout_width="match_parent" 
               android:layout_height="wrap_content" 
               android:stretchColumns="1">
        <TableRow>
            <CheckBox android:id="@+id/checkBox7" 
               android:text="@string/sound"></CheckBox>
            <Button  android:id="@+id/button2" 
               android:text="@string/apply_and_exit"></Button>            
        </TableRow>
运行时对象分配

另一个警告在 ColorPickerDialog.java 中,它说“在绘图/布局操作期间避免对象分配。这是有问题的代码。

@Override
protected void onDraw(Canvas canvas) {
    float r = mScrWidth - mPaint.getStrokeWidth()*0.5f;

    canvas.translate(mScrWidth, mScrWidth);
    canvas.drawOval(new RectF(-r, -r, r, r), mPaint); //offending new on each onDraw
    canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);

所以我将其更改为以下代码

...
//note from lint to pre-allocate
        //this code is located in the declaration of the ColorPickerView class
private RectF myRectF = new RectF();
...
@Override
protected void onDraw(Canvas canvas) {
    float r = mScrWidth - mPaint.getStrokeWidth()*0.5f;

    canvas.translate(mScrWidth, mScrWidth);
    myRectF.left = -r;
    myRectF.top = -r;
    myRectF.right = r;
    myRectF.bottom = r; 

    canvas.drawOval(myRectF, mPaint);
    canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);
API 兼容性

有几个地方我使用了已弃用的 API。下面是一个例子,说明如何通过运行时检查版本并调用正确的代码来解决这个问题。另一种解决方案会涉及更多的代码,通过创建一个抽象基类,然后使用工厂模式在运行时实例化正确的子类。但是这个决定仍然基于对 Build 版本的相同检查。

这是 showStatus 的旧代码

void showStatus()
{
    //get ref to NotificationManager
    String ns = Context.NOTIFICATION_SERVICE;
    
    NotificationManager mNotificationManager = 
         (NotificationManager) pContext.getSystemService(ns);
    
    //instantiate it
    int icon = R.drawable.notification_icon;
    CharSequence tickerText = "balls = "+currCount;
    long when = System.currentTimeMillis();
    Notification notification = new Notification(icon, tickerText, when);
    
    //Define the notification's message and PendingIntent: 
    Context context = pContext.getApplicationContext();
    CharSequence contentTitle = "ImpactPhysics";
    CharSequence contentText = "Ball status";
    Intent notificationIntent = new Intent();
    PendingIntent contentIntent = 
         PendingIntent.getActivity(pContext, 0, notificationIntent, 0);

    
    notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
    
    //Pass the Notification to the NotificationManager: 
    mNotificationManager.notify(HELLO_ID, notification);
}

这是新代码

void showStatus()
{
    //get ref to NotificationManager
    String ns = Context.NOTIFICATION_SERVICE;
    
    NotificationManager mNotificationManager = 
          (NotificationManager) pContext.getSystemService(ns);
    Notification notification = null;
    Context context = pContext.getApplicationContext();
    

    // with android support library, we should be able to use new code
    // on old platforms 
    
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
    {
        //instantiate it
        int icon = R.drawable.notification_icon;
        long when = System.currentTimeMillis();

        //Define the notification's message and PendingIntent: 
        CharSequence contentTitle = "ImpactPhysics";
        CharSequence contentText = "Ball status";
        Intent notificationIntent = new Intent();
        PendingIntent contentIntent = PendingIntent.getActivity(pContext, 0, notificationIntent, 0);
        
        CharSequence tickerText = "balls = "+currCount;
        notification = new Notification(icon, tickerText, when);
                notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
    }
    else
    {
      CharSequence countStr = "" + currCount;
      notification = new Notification.Builder(context)
         .setContentTitle("Number of balls = ")
         .setContentText(countStr)
         .build();   
    }
    
    //Pass the Notification to the NotificationManager: 
    mNotificationManager.notify(HELLO_ID, notification);
}    
//showStatus

Lint 发现但未处理的项目

Lint 分析中还发现了其他几个项目,我选择暂时忽略它们。

图标和图形

大多数警告都与图标文件有关。文件要么是重复的(复制/粘贴)要么是丢失的。由于我没有任何绘制精美图标的技能或工具,我选择暂时接受这些警告。一个真正的应用程序会希望根据 Android 设计指南来处理这些问题,而我完全承认我从未读过这些指南。

ShowDialog

Lint 发现的另一件事是我使用了已弃用的 showDialog() API。推荐的新方法是 DialogFragment。但我研究了所需代码的更改,并决定跳过它。暂时我将继续使用已弃用的方法。也许以后我会修复它并发布更新。

是时候转向新硬件平台了

同一个 apk 文件实际上在新的目标设备(三星 Galaxy tab 3 10.1)上运行而没有崩溃。但是它工作得不太正确。这个应用程序使用加速度计来提供一种“倾斜球台”效果。但是在平板电脑上,方向完全是错的。结果发现解决方案很简单。我需要读取设备的旋转属性,并根据方向处理 X 和 Y 的读数。

这是旧代码的主要部分。

public void onSensorChanged(SensorEvent event) {
    long curTime = System.currentTimeMillis();
    boolean shakeDetected = false;
    // only allow one update every 100ms.
    if ((curTime - lastUpdate) > 100) 
    {
        long diffTime = (curTime - lastUpdate);
        lastUpdate = curTime;
 
        x = event.values[0];
        y = event.values[1];
        z = event.values[2];
        // process x,y,z... 
这是根据旋转处理 x,y 的更改
// setting mRotation is done elsewhere...
WindowManager w = getWindowManager();
mRotation = w.getDefaultDisplay().getRotation();

    public void onSensorChanged(SensorEvent event) {
	    long curTime = System.currentTimeMillis();
	    boolean shakeDetected = false;
	    // only allow one update every 100ms.
	    if ((curTime - lastUpdate) > 100) 
	    {
			long diffTime = (curTime - lastUpdate);
			lastUpdate = curTime;
	 
			switch (mRotation)
			{
			case Surface.ROTATION_0:
				x = event.values[0];
				y = event.values[1];				
				break;
				
			case Surface.ROTATION_90:
				y = event.values[0];
				x = event.values[1];				
				break;	
				
			case Surface.ROTATION_180:
				x = -event.values[0];
				y = -event.values[1];				
				break;	
				
			case Surface.ROTATION_270:
				y = -event.values[0]; //TODO swap x,y on tablet? and negate y?
				x = event.values[1];				
				break;	
			}

			z = event.values[2];
                        // process x,y,z...

结论

总的来说,从旧版本 2.3.4 到新版本 4.2.2 API,以及处理从小型手机到大型平板电脑的尺寸规格,代码和项目设置大多保持不变。在我研究需要做什么的过程中,我发现以下网站非常有帮助 https://developer.android.com.cn

历史

  • 版本 1.3,2013 年 12 月
© . All rights reserved.